// Invoice documents hold client names and line items, which do not belong on a// public chain. Each is encrypted here with its own key. The server stores only// ciphertext; the chain stores its SHA-256, so any tampering is visible; and the// key travels in the part of the link after #, which browsers never send anywhere.import { b64decode, b64encode, concat, hex, sha256 } from './near.js';const te = new TextEncoder();const td = new TextDecoder();async function seal(keyBytes, plain) { const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['encrypt']); const iv = crypto.getRandomValues(new Uint8Array(12)); return concat(iv, new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plain)));}async function open(keyBytes, sealed) { if (sealed.length < 28) throw new Error('Damaged document'); const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['decrypt']); return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: sealed.slice(0, 12) }, key, sealed.slice(12)));}export const toUrlKey = (b) => b64encode(b).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');export const fromUrlKey = (s) => { if (!/^[A-Za-z0-9_-]{43}$/.test(s)) throw new Error('Bad link key'); return b64decode(s.replace(/-/g, '+').replace(/_/g, '/') + '=');};/** Encrypt a document. `wrap` lets the merchant reopen it from any device. */export async function sealDocument(doc, docKey) { const contentKey = crypto.getRandomValues(new Uint8Array(32)); const sealed = await seal(contentKey, te.encode(JSON.stringify(doc))); return { ct: b64encode(sealed), wrap: b64encode(await seal(docKey, contentKey)), hash: hex(await sha256(sealed)), contentKey, };}/** Throws if the ciphertext is not the one the chain committed to. */export async function openDocument(ctB64, contentKey, expectedHash) { const sealed = b64decode(ctB64); if (expectedHash && hex(await sha256(sealed)) !== expectedHash) { throw new Error('This document does not match the invoice on-chain'); } return JSON.parse(td.decode(await open(contentKey, sealed)));}export async function unwrapContentKey(wrapB64, docKey) { return open(docKey, b64decode(wrapB64));}