client/packages/crypto/src/context.tsx
Alois 2cd326d0b1
Some checks failed
/ build-web (push) Failing after 4m36s
/ build-desktop (linux) (push) Failing after 4m47s
/ build-mobile (push) Failing after 6m34s
/ release (push) Has been skipped
(feat): finish chat crypto migration
(wip): prep for full ECDH repo migration
2026-07-06 19:23:37 +02:00

75 lines
2.3 KiB
TypeScript

import { createContext, useContext } from "react";
import { base64ToBytes, crypto } from "mtp";
type CryptoContextType = {
decrypt: (
secret: string,
input: Uint8Array<ArrayBuffer>,
) => Promise<Uint8Array<ArrayBuffer>>;
decryptText: (secret: string, ciphertext: string) => Promise<string>;
encrypt: (
secret: string,
input: Uint8Array<ArrayBuffer>,
) => Promise<Uint8Array<ArrayBuffer>>;
encryptText: (secret: string, plaintext: string) => Promise<string>;
};
export const context = createContext<CryptoContextType | undefined>(undefined);
export function createCryptoActions(
getApi: () => CryptoContextType | null | undefined,
): CryptoContextType {
const requireApi = () => {
const api = getApi();
if (!api) {
throw new Error("Crypto API not initialized");
}
return api;
};
return {
decrypt: (secret, input) => requireApi().decrypt(secret, input),
decryptText: (secret, ciphertext) =>
requireApi().decryptText(secret, ciphertext),
encrypt: (secret, input) => requireApi().encrypt(secret, input),
encryptText: (secret, plaintext) =>
requireApi().encryptText(secret, plaintext),
};
}
function ownedBytes(bytes: Uint8Array): Uint8Array<ArrayBuffer> {
const out = new Uint8Array(bytes.byteLength);
out.set(bytes);
return out;
}
function secretKeyFromString(secret: string): Uint8Array {
return crypto.deriveEncryptionKey(
base64ToBytes(secret),
new Uint8Array(0),
new TextEncoder().encode("tensamin:shared-secret-text"),
);
}
export default function Provider(props: { children: React.ReactNode }) {
const actions = createCryptoActions(() => ({
decrypt: async (secret, input) =>
ownedBytes(await crypto.decrypt(secretKeyFromString(secret), input)),
decryptText: (secret, ciphertext) =>
crypto.decryptText(secretKeyFromString(secret), ciphertext),
encrypt: async (secret, input) =>
ownedBytes(await crypto.encrypt(secretKeyFromString(secret), input)),
encryptText: (secret, plaintext) =>
crypto.encryptText(secretKeyFromString(secret), plaintext),
}));
return <context.Provider value={actions}>{props.children}</context.Provider>;
}
export function useCrypto(): CryptoContextType {
const ctx = useContext(context);
if (!ctx) {
throw new Error("useCrypto must be used within a CryptoProvider");
}
return ctx;
}