import * as ed from '@noble/ed25519'; import { sha512 } from '@noble/hashes/sha2'; import { gzip, ungzip } from 'pako'; ed.etc.sha512Sync = sha512; const SALT = 'SONRI-KEY-SALT'; export type KeyVault = Awaited>; // Helper: convert string to Uint8Array export function strToUint8(str: string) { return new TextEncoder().encode(str); } export function uint8ToStr(uint8: Uint8Array) { return new TextDecoder().decode(uint8); } export function uint8ToHex(uint8: Uint8Array) { return Array.from(uint8) .map(byte => byte.toString(16).padStart(2, '0')) .join(''); } export function hexToUint8(hex: string): Uint8Array { if (hex.length % 2 !== 0) { throw new Error('Hex string must have an even length'); } const length = hex.length / 2; const uint8 = new Uint8Array(length); for (let i = 0; i < length; i++) { uint8[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); } return uint8; } function uint8ToBase64(uint8: Uint8Array) { return atob(uint8ToStr(uint8)); } function base64ToUint8(base64: string) { return strToUint8(btoa(base64)); } export async function deriveKeyVault(secret: string) { console.log(crypto); let hash = strToUint8(secret), last = hash; for (let i = 0; i < 10000; i++) { const hashInput = Uint8Array.from([...strToUint8(SALT), ...hash]); const hashBuffer = await crypto.subtle.digest('SHA-512', hashInput); last = hash; hash = new Uint8Array(hashBuffer); } const symmKey = last.slice(0, 32); const privKey = hash.slice(0, 32); const pubKey = ed.getPublicKey(privKey); return { pubKey, privKey, symmKey, }; } export function encodeKeyVault(keyVault: KeyVault) { const jsonStr = JSON.stringify(Object.fromEntries(Object.entries(keyVault).map(([key, value]) => { return [key, uint8ToHex(value)]; }))); return uint8ToHex(gzip(strToUint8(jsonStr))); } export function decodeKeyVault(encoded: string) { const jsonStr = uint8ToStr(ungzip(hexToUint8(encoded))); return Object.fromEntries(Object.entries(JSON.parse(jsonStr)).map(([key, value]) => { return [key, hexToUint8(value as string)]; })) as KeyVault; } export async function encrypt(symmKey: Uint8Array, data: Uint8Array) { const key = await crypto.subtle.importKey( 'raw', symmKey, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'], ); const iv = crypto.getRandomValues(new Uint8Array(12)); const encryptedBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, data ); return Uint8Array.from([...iv, ...new Uint8Array(encryptedBuffer)]); } export async function decrypt(symmKey: Uint8Array, cipher: Uint8Array) { const key = await crypto.subtle.importKey( 'raw', symmKey, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'], ); const iv = cipher.slice(0, 12); cipher = cipher.slice(12); const dataBuffer = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, key, cipher ); return new Uint8Array(dataBuffer); } export async function sign(privKey: Uint8Array, data: Uint8Array): Promise { return await ed.signAsync(data, privKey) as Uint8Array; } export async function verify(pubKey: Uint8Array, signature: Uint8Array, data: Uint8Array): Promise { try { return await ed.verifyAsync(signature, data, pubKey); } catch (_) { return false; } }