/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2024-2025 Tony Garnock-Jones import { Bytes, Value, encode, decode, isEmbedded, isSequence } from "@preserves/core"; import { SaltyCrypto, Ref } from "@syndicate-lang/core"; const aead = SaltyCrypto.ChaCha20Poly1305_RFC8439; export type Sealed = [Bytes, number, number, number]; export function makeSeal() { const key = new DataView(SaltyCrypto.randomBytes(aead.KEYBYTES).buffer); const n = new SaltyCrypto.Nonce(); return { seal(v: Value): Promise { return new Promise(k => { at this.sealer { send message [v, create ({ message(reply) { k(reply as Sealed); } })]; } }); }, unseal(sealed: Sealed): Promise<{ ok: true, value: Value } | { ok: false }> { return new Promise(k => { at this.unsealer { send message [sealed, create ({ message(reply) { k(reply as any); } })]; } }); }, sealer: create ({ message(req) { if (isSequence(req) && req.length === 2 && isEmbedded(req[1])) { try { const [term, k] = req; const m = encode(term); const c = aead.encrypt(m._view, key, n); at k { send message [Bytes.from(c), n.lo, n.hi, n.extra]; } n.increment(); } catch (_e) { console.error('Failed sealing: ' + _e); } } } }), unsealer: create ({ message(req) { if ( isSequence(req) && req.length === 2 && isEmbedded(req[1]) && isSequence(req[0]) && req[0].length === 4 && Bytes.isBytes(req[0][0]) && typeof req[0][1] === 'number' && typeof req[0][2] === 'number' && typeof req[0][3] === 'number' ) { const [[c, lo, hi, extra], k] = req; try { const n = new SaltyCrypto.Nonce(lo, hi, extra); const m = aead.decrypt(c._view, key, n); at k { send message { ok: true, value: decode(Bytes.from(m)) }; } } catch (e) { at k { send message { ok: false }; } } } }, }), }; }