Big Updated, added some tests, added comments for all functions, I forgot the rest
This commit is contained in:
parent
d77271e4b7
commit
90a1059cc8
59 changed files with 1816 additions and 203 deletions
|
|
@ -10,11 +10,11 @@
|
|||
"scripts": {
|
||||
"format": "bunx prettier --write .",
|
||||
"lint": "eslint src",
|
||||
"build": "tsc -p tsconfig.json --noEmit"
|
||||
"test": "bun test",
|
||||
"build": "bun run test && tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/curves": "^2.0.1",
|
||||
"@tensamin/ui": "workspace:*",
|
||||
"comlink": "^4.4.2",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
|
|
|
|||
11
packages/crypto/src/bun-test.d.ts
vendored
Normal file
11
packages/crypto/src/bun-test.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
declare module "bun:test" {
|
||||
export const describe: (...args: unknown[]) => unknown;
|
||||
export const test: (...args: unknown[]) => unknown;
|
||||
export const it: (...args: unknown[]) => unknown;
|
||||
export const expect: (value: unknown) => {
|
||||
toBe: (expected: unknown) => void;
|
||||
toEqual: (expected: unknown) => void;
|
||||
toContain: (expected: unknown) => void;
|
||||
toThrow: (expected?: unknown) => void;
|
||||
};
|
||||
}
|
||||
46
packages/crypto/src/context.test.ts
Normal file
46
packages/crypto/src/context.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { describe, expect, test } from "bun:test";
|
||||
import { createCryptoActions } from "./context";
|
||||
|
||||
/**
|
||||
* Creates a rejected API getter used to verify initialization guards.
|
||||
* @returns Null API reference.
|
||||
*/
|
||||
function getUninitializedApi(): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
describe("createCryptoActions", () => {
|
||||
test("throws when API is not initialized", async () => {
|
||||
const actions = createCryptoActions(getUninitializedApi);
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
await actions.encrypt("ab", "plain");
|
||||
} catch (error) {
|
||||
failed = (error as Error).message.includes("API not initialized");
|
||||
}
|
||||
|
||||
expect(failed).toBe(true);
|
||||
});
|
||||
|
||||
test("delegates encrypt/decrypt/getSharedSecret to API reference", async () => {
|
||||
const api = {
|
||||
encrypt: async (secret: string, plaintext: string): Promise<string> =>
|
||||
`${secret}:${plaintext}`,
|
||||
decrypt: async (secret: string, ciphertext: string): Promise<string> =>
|
||||
`${secret}|${ciphertext}`,
|
||||
getSharedSecret: async (
|
||||
ownPrivateKey: string,
|
||||
ownPublicKey: string,
|
||||
otherPublicKey: string,
|
||||
): Promise<string> =>
|
||||
`${ownPrivateKey}.${ownPublicKey}.${otherPublicKey}`,
|
||||
};
|
||||
|
||||
const actions = createCryptoActions(() => api);
|
||||
|
||||
expect(await actions.encrypt("s", "p")).toBe("s:p");
|
||||
expect(await actions.decrypt("s", "c")).toBe("s|c");
|
||||
expect(await actions.getSharedSecret("a", "b", "c")).toBe("a.b.c");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,39 @@
|
|||
import * as React from "react";
|
||||
import * as Comlink from "comlink";
|
||||
import Loading from "@tensamin/ui/screens/loading";
|
||||
|
||||
export const context = React.createContext<contextType | undefined>(undefined);
|
||||
type CryptoContextType = {
|
||||
decrypt: (secret: string, ciphertext: string) => Promise<string>;
|
||||
encrypt: (secret: string, plaintext: string) => Promise<string>;
|
||||
getSharedSecret: (
|
||||
ownPrivateKey: string,
|
||||
ownPublicKey: string,
|
||||
otherPublicKey: string,
|
||||
) => Promise<string>;
|
||||
};
|
||||
|
||||
type ApiRef = {
|
||||
encrypt: (secret: string, plaintext: string) => Promise<string>;
|
||||
decrypt: (secret: string, ciphertext: string) => Promise<string>;
|
||||
getSharedSecret: (
|
||||
ownPrivateKey: string,
|
||||
ownPublicKey: string,
|
||||
otherPublicKey: string,
|
||||
) => Promise<string>;
|
||||
};
|
||||
|
||||
export const context = React.createContext<CryptoContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* Provides cryptographic actions backed by a worker without coupling to UI state.
|
||||
* @param props Component props with children.
|
||||
* @returns Crypto context provider JSX.
|
||||
*/
|
||||
export default function Provider(props: { children: React.ReactNode }) {
|
||||
const apiRef = React.useRef<ApiRef | null>(null);
|
||||
const [isWorkerReady, setIsWorkerReady] = React.useState(false);
|
||||
|
||||
const { encrypt, decrypt, get_shared_secret } = React.useMemo(
|
||||
const value = React.useMemo(
|
||||
() => createCryptoActions(() => apiRef.current),
|
||||
[],
|
||||
);
|
||||
|
|
@ -18,83 +43,84 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
type: "module",
|
||||
});
|
||||
|
||||
apiRef.current = Comlink.wrap(worker);
|
||||
setIsWorkerReady(true);
|
||||
apiRef.current = Comlink.wrap<ApiRef>(worker);
|
||||
|
||||
return () => {
|
||||
apiRef.current = null;
|
||||
worker.terminate();
|
||||
setIsWorkerReady(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!isWorkerReady) {
|
||||
return <Loading progress={10} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<context.Provider value={{ encrypt, decrypt, get_shared_secret }}>
|
||||
{props.children}
|
||||
</context.Provider>
|
||||
);
|
||||
return <context.Provider value={value}>{props.children}</context.Provider>;
|
||||
}
|
||||
|
||||
type contextType = {
|
||||
decrypt: (secret: string, data: string) => Promise<string>;
|
||||
encrypt: (secret: string, data: string) => Promise<string>;
|
||||
get_shared_secret: (
|
||||
/**
|
||||
* Creates crypto action functions that safely delegate to the worker API.
|
||||
* @param getApiRef Function that returns worker API reference when initialized.
|
||||
* @returns Typed crypto action functions.
|
||||
*/
|
||||
export function createCryptoActions(
|
||||
getApiRef: () => ApiRef | null,
|
||||
): CryptoContextType {
|
||||
/**
|
||||
* Encrypts plaintext by delegating to the crypto worker API.
|
||||
* @param secret Hex-encoded shared secret.
|
||||
* @param plaintext Plaintext to encrypt.
|
||||
* @returns Encrypted ciphertext.
|
||||
*/
|
||||
const encrypt = async (
|
||||
secret: string,
|
||||
plaintext: string,
|
||||
): Promise<string> => {
|
||||
const apiRef = getApiRef();
|
||||
if (!apiRef) throw new Error("API not initialized");
|
||||
return await apiRef.encrypt(secret, plaintext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypts ciphertext by delegating to the crypto worker API.
|
||||
* @param secret Hex-encoded shared secret.
|
||||
* @param ciphertext Ciphertext to decrypt.
|
||||
* @returns Decrypted plaintext.
|
||||
*/
|
||||
const decrypt = async (
|
||||
secret: string,
|
||||
ciphertext: string,
|
||||
): Promise<string> => {
|
||||
const apiRef = getApiRef();
|
||||
if (!apiRef) throw new Error("API not initialized");
|
||||
return await apiRef.decrypt(secret, ciphertext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Derives a shared secret from local and peer key material via the worker API.
|
||||
* @param ownPrivateKey Local private key.
|
||||
* @param ownPublicKey Local public key.
|
||||
* @param otherPublicKey Peer public key.
|
||||
* @returns Hex-encoded shared secret.
|
||||
*/
|
||||
const getSharedSecret = async (
|
||||
ownPrivateKey: string,
|
||||
ownPublicKey: string,
|
||||
otherPublicKey: string,
|
||||
) => Promise<string>;
|
||||
};
|
||||
|
||||
type ApiRef = {
|
||||
encrypt: (secret: string, message: string) => Promise<string>;
|
||||
decrypt: (secret: string, encryptedMessage: string) => Promise<string>;
|
||||
get_shared_secret: (
|
||||
own_private_key: string,
|
||||
own_public_key: string,
|
||||
other_public_key: string,
|
||||
) => Promise<string>;
|
||||
};
|
||||
|
||||
export function createCryptoActions(
|
||||
getApiRef: () => ApiRef | null,
|
||||
): contextType {
|
||||
const encrypt = async (secret: string, message: string): Promise<string> => {
|
||||
const apiRef = getApiRef();
|
||||
if (!apiRef) throw new Error("API not initialized");
|
||||
return await apiRef.encrypt(secret, message);
|
||||
};
|
||||
|
||||
const decrypt = async (
|
||||
secret: string,
|
||||
encryptedMessage: string,
|
||||
): Promise<string> => {
|
||||
const apiRef = getApiRef();
|
||||
if (!apiRef) throw new Error("API not initialized");
|
||||
return await apiRef.decrypt(secret, encryptedMessage);
|
||||
};
|
||||
|
||||
const get_shared_secret = async (
|
||||
own_private_key: string,
|
||||
own_public_key: string,
|
||||
other_public_key: string,
|
||||
): Promise<string> => {
|
||||
const apiRef = getApiRef();
|
||||
if (!apiRef) throw new Error("API not initialized");
|
||||
return await apiRef.get_shared_secret(
|
||||
own_private_key,
|
||||
own_public_key,
|
||||
other_public_key,
|
||||
return await apiRef.getSharedSecret(
|
||||
ownPrivateKey,
|
||||
ownPublicKey,
|
||||
otherPublicKey,
|
||||
);
|
||||
};
|
||||
|
||||
return { encrypt, decrypt, get_shared_secret };
|
||||
return { encrypt, decrypt, getSharedSecret };
|
||||
}
|
||||
|
||||
export function useCrypto(): contextType {
|
||||
/**
|
||||
* Returns the crypto actions from the nearest provider.
|
||||
* Throws when used outside of the crypto provider tree.
|
||||
*/
|
||||
export function useCrypto(): CryptoContextType {
|
||||
const ctx = React.useContext(context);
|
||||
if (!ctx) {
|
||||
throw new Error("useCrypto must be used within a CryptoProvider");
|
||||
|
|
|
|||
88
packages/crypto/src/worker.test.ts
Normal file
88
packages/crypto/src/worker.test.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { describe, expect, test } from "bun:test";
|
||||
import { x448 } from "@noble/curves/ed448.js";
|
||||
import { decrypt, encrypt, getSharedSecret } from "./worker";
|
||||
|
||||
/**
|
||||
* Encodes bytes to URL-safe base64 without padding.
|
||||
* @param value Input bytes.
|
||||
* @returns Base64url string.
|
||||
*/
|
||||
function bytesToB64u(value: Uint8Array): string {
|
||||
const b64 = Buffer.from(value).toString("base64");
|
||||
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes to lowercase hex.
|
||||
* @param value Input bytes.
|
||||
* @returns Hex string.
|
||||
*/
|
||||
function bytesToHex(value: Uint8Array): string {
|
||||
return Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join(
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates deterministic 56-byte private key material for tests.
|
||||
* @param seed Offset seed used to vary generated bytes.
|
||||
* @returns Deterministic private key bytes.
|
||||
*/
|
||||
function createPrivateKey(seed: number): Uint8Array {
|
||||
const output = new Uint8Array(56);
|
||||
|
||||
for (let index = 0; index < output.length; index += 1) {
|
||||
output[index] = (seed + index) % 255;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
describe("crypto worker", () => {
|
||||
test("encrypt/decrypt round-trip returns original plaintext", async () => {
|
||||
const secret = "a1".repeat(56);
|
||||
const plaintext = "hello encrypted world";
|
||||
|
||||
const ciphertext = await encrypt(secret, plaintext);
|
||||
const decrypted = await decrypt(secret, ciphertext);
|
||||
|
||||
expect(decrypted).toBe(plaintext);
|
||||
});
|
||||
|
||||
test("decrypt fails with wrong shared secret", async () => {
|
||||
const secret = "0f".repeat(56);
|
||||
const wrongSecret = "f0".repeat(56);
|
||||
const plaintext = "sensitive";
|
||||
|
||||
const ciphertext = await encrypt(secret, plaintext);
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
await decrypt(wrongSecret, ciphertext);
|
||||
} catch {
|
||||
failed = true;
|
||||
}
|
||||
|
||||
expect(failed).toBe(true);
|
||||
});
|
||||
|
||||
test("getSharedSecret matches noble x448 derivation", async () => {
|
||||
const ownPrivateBytes = createPrivateKey(7);
|
||||
const peerPrivateBytes = createPrivateKey(23);
|
||||
|
||||
const ownPublicBytes = x448.getPublicKey(ownPrivateBytes);
|
||||
const peerPublicBytes = x448.getPublicKey(peerPrivateBytes);
|
||||
|
||||
const expected = bytesToHex(
|
||||
new Uint8Array(x448.getSharedSecret(ownPrivateBytes, peerPublicBytes)),
|
||||
);
|
||||
|
||||
const actual = await getSharedSecret(
|
||||
bytesToB64u(ownPrivateBytes),
|
||||
bytesToB64u(ownPublicBytes),
|
||||
bytesToB64u(peerPublicBytes),
|
||||
);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
|
@ -12,12 +12,18 @@ type JWK = {
|
|||
const textEncoder = new TextEncoder();
|
||||
const crypto = globalThis.crypto;
|
||||
|
||||
/**
|
||||
* Encrypts plaintext with a symmetric key derived from a hex shared secret.
|
||||
* @param secret Hex-encoded shared secret.
|
||||
* @param plaintext UTF-8 plaintext to encrypt.
|
||||
* @returns Base64-encoded ciphertext.
|
||||
*/
|
||||
export async function encrypt(
|
||||
password: string,
|
||||
input: string,
|
||||
secret: string,
|
||||
plaintext: string,
|
||||
): Promise<string> {
|
||||
const sharedSecret = new Uint8Array(
|
||||
password.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)),
|
||||
secret.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)),
|
||||
);
|
||||
|
||||
const hkdfKey = await crypto.subtle.importKey(
|
||||
|
|
@ -54,21 +60,29 @@ export async function encrypt(
|
|||
const encryptedBuffer = await crypto.subtle.encrypt(
|
||||
{ name: "AES-GCM", iv: nonce },
|
||||
aesKey,
|
||||
textEncoder.encode(input),
|
||||
textEncoder.encode(plaintext),
|
||||
);
|
||||
|
||||
return btoa(String.fromCharCode(...new Uint8Array(encryptedBuffer)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts base64 ciphertext with a symmetric key derived from a hex shared secret.
|
||||
* @param secret Hex-encoded shared secret.
|
||||
* @param ciphertext Base64 ciphertext to decrypt.
|
||||
* @returns Decrypted UTF-8 plaintext.
|
||||
*/
|
||||
export async function decrypt(
|
||||
password: string,
|
||||
input: Base64URLString | string,
|
||||
secret: string,
|
||||
ciphertext: Base64URLString | string,
|
||||
): Promise<string> {
|
||||
const sharedSecret = new Uint8Array(
|
||||
password.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)),
|
||||
secret.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)),
|
||||
);
|
||||
|
||||
const ciphertext = Uint8Array.from(atob(input), (c) => c.charCodeAt(0));
|
||||
const ciphertextBytes = Uint8Array.from(atob(ciphertext), (c) =>
|
||||
c.charCodeAt(0),
|
||||
);
|
||||
|
||||
const hkdfKey = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
|
|
@ -107,28 +121,45 @@ export async function decrypt(
|
|||
iv: nonce,
|
||||
},
|
||||
aesKey,
|
||||
ciphertext,
|
||||
ciphertextBytes,
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(decryptedBuffer);
|
||||
}
|
||||
|
||||
export async function get_shared_secret(
|
||||
own_private_key: string,
|
||||
own_public_key: string,
|
||||
other_public_key: string,
|
||||
/**
|
||||
* Computes an X448 shared secret from local and peer key material.
|
||||
* @param ownPrivateKey Local private key in raw/base64/base64url or PKCS#8-wrapped form.
|
||||
* @param ownPublicKey Local public key in raw/base64/base64url or SPKI-wrapped form.
|
||||
* @param otherPublicKey Peer public key in raw/base64/base64url or SPKI-wrapped form.
|
||||
* @returns Hex-encoded shared secret, or a failure message when key material is missing/invalid.
|
||||
*/
|
||||
export async function getSharedSecret(
|
||||
ownPrivateKey: string,
|
||||
ownPublicKey: string,
|
||||
otherPublicKey: string,
|
||||
): Promise<string> {
|
||||
const other_jwk: JWK = { kty: "OKP", crv: "X448", x: other_public_key };
|
||||
const own_jwk: JWK = {
|
||||
const otherJwk: JWK = { kty: "OKP", crv: "X448", x: otherPublicKey };
|
||||
const ownJwk: JWK = {
|
||||
kty: "OKP",
|
||||
crv: "X448",
|
||||
x: own_public_key,
|
||||
d: own_private_key,
|
||||
x: ownPublicKey,
|
||||
d: ownPrivateKey,
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts bytes to a lowercase hex string.
|
||||
* @param u8 Byte array.
|
||||
* @returns Hex string.
|
||||
*/
|
||||
const bytesToHex = (u8: Uint8Array): string =>
|
||||
Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join("");
|
||||
|
||||
/**
|
||||
* Decodes standard base64 text into bytes.
|
||||
* @param s Base64 string.
|
||||
* @returns Decoded bytes.
|
||||
*/
|
||||
const b64ToBytes = (s: Base64URLString): Uint8Array => {
|
||||
const bin = atob(s);
|
||||
const out = new Uint8Array(bin.length);
|
||||
|
|
@ -136,20 +167,41 @@ export async function get_shared_secret(
|
|||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes URL-safe base64 text into bytes.
|
||||
* @param s Base64url string.
|
||||
* @returns Decoded bytes.
|
||||
*/
|
||||
const b64uToBytes = (s: Base64URLString): Uint8Array => {
|
||||
const b64 =
|
||||
s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4);
|
||||
return b64ToBytes(b64);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes bytes as URL-safe base64 without padding.
|
||||
* @param u8 Byte array.
|
||||
* @returns Base64url string.
|
||||
*/
|
||||
const bytesToB64u = (u8: Uint8Array): string => {
|
||||
const b64 = btoa(String.fromCharCode(...u8));
|
||||
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes either base64 or base64url text into bytes.
|
||||
* @param s Base64/base64url string.
|
||||
* @returns Decoded bytes.
|
||||
*/
|
||||
const decodeBase64Auto = (s: string): Uint8Array =>
|
||||
/[-_]/.test(s) ? b64uToBytes(s) : b64ToBytes(s);
|
||||
|
||||
/**
|
||||
* Reads a DER TLV item from the provided offset.
|
||||
* @param view DER-encoded bytes.
|
||||
* @param off Start offset.
|
||||
* @returns Parsed TLV metadata with tag, length, and boundaries.
|
||||
*/
|
||||
const readTLV = (view: Uint8Array, off: number) => {
|
||||
const tag = view[off++];
|
||||
if (off >= view.length) throw new Error("DER: truncated");
|
||||
|
|
@ -167,6 +219,12 @@ export async function get_shared_secret(
|
|||
return { tag, len, start, end };
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that a DER OID matches X448.
|
||||
* @param view DER-encoded bytes.
|
||||
* @param start Offset of the OID TLV.
|
||||
* @returns True when the OID is X448.
|
||||
*/
|
||||
const ensureOidX448 = (view: Uint8Array, start: number): boolean => {
|
||||
const oid = readTLV(view, start);
|
||||
if (oid.tag !== 0x06) return false;
|
||||
|
|
@ -179,6 +237,11 @@ export async function get_shared_secret(
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts raw 56-byte X448 public key material from SPKI bytes.
|
||||
* @param spkiBytes DER-encoded SPKI bytes.
|
||||
* @returns Raw X448 public key bytes.
|
||||
*/
|
||||
const extractRawX448FromSPKI = (spkiBytes: Uint8Array): Uint8Array => {
|
||||
const view = spkiBytes;
|
||||
const outer = readTLV(view, 0);
|
||||
|
|
@ -196,6 +259,11 @@ export async function get_shared_secret(
|
|||
return raw;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts raw 56-byte X448 private key material from PKCS#8 bytes.
|
||||
* @param pkcs8Bytes DER-encoded PKCS#8 bytes.
|
||||
* @returns Raw X448 private key bytes.
|
||||
*/
|
||||
const extractRawX448FromPKCS8 = (pkcs8Bytes: Uint8Array): Uint8Array => {
|
||||
const view = pkcs8Bytes;
|
||||
const outer = readTLV(view, 0);
|
||||
|
|
@ -230,6 +298,12 @@ export async function get_shared_secret(
|
|||
return raw;
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes X448 JWK fields into raw base64url key material.
|
||||
* @param jwk Candidate JWK.
|
||||
* @param label Error label for diagnostics.
|
||||
* @returns Normalized JWK suitable for WebCrypto import.
|
||||
*/
|
||||
const normalizeOkpX448Jwk = (jwk: JWK, label: string): JWK => {
|
||||
if (!jwk || jwk.kty !== "OKP" || jwk.crv !== "X448") {
|
||||
throw new Error(`${label}: expected OKP JWK with crv "X448"`);
|
||||
|
|
@ -271,6 +345,10 @@ export async function get_shared_secret(
|
|||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns WebCrypto subtle API when available.
|
||||
* @returns SubtleCrypto instance or undefined.
|
||||
*/
|
||||
const getSubtle = () => globalThis.crypto?.subtle;
|
||||
|
||||
{
|
||||
|
|
@ -305,8 +383,8 @@ export async function get_shared_secret(
|
|||
*/
|
||||
}
|
||||
|
||||
const myJwk: JWK = normalizeOkpX448Jwk(own_jwk, "own_jwk");
|
||||
const peerJwk: JWK = normalizeOkpX448Jwk(other_jwk, "other_jwk");
|
||||
const myJwk: JWK = normalizeOkpX448Jwk(ownJwk, "own_jwk");
|
||||
const peerJwk: JWK = normalizeOkpX448Jwk(otherJwk, "other_jwk");
|
||||
|
||||
const subtle = getSubtle();
|
||||
//const infoStr = `ECDH-X448-AES-GCM-v1|my=${myJwk.x}|peer=${peerJwk.x}`;
|
||||
|
|
@ -357,8 +435,33 @@ export async function get_shared_secret(
|
|||
return bytesToHex(sharedSecret);
|
||||
}
|
||||
|
||||
Comlink.expose({
|
||||
encrypt,
|
||||
decrypt,
|
||||
get_shared_secret,
|
||||
});
|
||||
/**
|
||||
* @deprecated Use getSharedSecret instead.
|
||||
* @param ownPrivateKey Local private key.
|
||||
* @param ownPublicKey Local public key.
|
||||
* @param otherPublicKey Peer public key.
|
||||
* @returns Shared secret derived by getSharedSecret.
|
||||
*/
|
||||
export async function get_shared_secret(
|
||||
ownPrivateKey: string,
|
||||
ownPublicKey: string,
|
||||
otherPublicKey: string,
|
||||
): Promise<string> {
|
||||
return await getSharedSecret(ownPrivateKey, ownPublicKey, otherPublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current runtime context is a worker global scope.
|
||||
* @returns True when executed inside a worker-like runtime.
|
||||
*/
|
||||
function isWorkerRuntime(): boolean {
|
||||
return "postMessage" in globalThis && "importScripts" in globalThis;
|
||||
}
|
||||
|
||||
if (isWorkerRuntime()) {
|
||||
Comlink.expose({
|
||||
encrypt,
|
||||
decrypt,
|
||||
getSharedSecret,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue