// The line-item fingerprint. Client names and line items are encrypted in the browser; // only SHA-256 of the ciphertext goes on-chain (create_invoice's doc_hash). These tests // show the fingerprint is computed the way the contract and the Worker expect, that the // stored document reveals nothing, and that any change to it is caught when it is opened. import test from 'node:test'; import assert from 'node:assert/strict'; import { b64decode, b64encode, hex, sha256 } from '../web/lib/near.js'; import { fromUrlKey, openDocument, sealDocument, toUrlKey, unwrapContentKey } from '../web/lib/doc.js'; const doc = { v: 1, from: { name: 'Demo Studio' }, to: { name: 'Acme Ltd' }, items: [{ d: 'Logo design', q: '1', p: '100' }], taxBps: 2000 }; const docKey = new Uint8Array(32).fill(7); test('the fingerprint is SHA-256 of the ciphertext, in the 64-hex form create_invoice takes', async () => { const sealed = await sealDocument(doc, docKey); assert.match(sealed.hash, /^[0-9a-f]{64}$/); assert.equal(sealed.hash, hex(await sha256(b64decode(sealed.ct)))); }); test('the stored document holds no client name or line item', async () => { const sealed = await sealDocument(doc, docKey); const bytes = Buffer.from(b64decode(sealed.ct)).toString('latin1'); for (const secret of ['Acme', 'Demo Studio', 'Logo design']) assert.ok(!bytes.includes(secret), secret); }); test('the client opens it with the key from the link, and the fingerprint matches', async () => { const sealed = await sealDocument(doc, docKey); const key = fromUrlKey(toUrlKey(sealed.contentKey)); assert.deepEqual(await openDocument(sealed.ct, key, sealed.hash), doc); }); test('a document changed after issue is refused, even when it decrypts', async () => { const original = await sealDocument(doc, docKey); // The server swaps in a different document sealed with the same content key. const forged = await sealDocument({ ...doc, to: { name: 'Mallory' } }, docKey); const swapped = await unwrapContentKey(forged.wrap, docKey); await assert.rejects(openDocument(forged.ct, swapped, original.hash), /does not match the invoice on-chain/); }); test('a single flipped byte is refused', async () => { const sealed = await sealDocument(doc, docKey); const bytes = b64decode(sealed.ct); bytes[bytes.length - 1] ^= 1; await assert.rejects(openDocument(b64encode(bytes), sealed.contentKey, sealed.hash), /does not match the invoice on-chain/); }); test('the merchant reopens it from any device with the key derived from their account', async () => { const sealed = await sealDocument(doc, docKey); const key = await unwrapContentKey(sealed.wrap, docKey); assert.deepEqual(await openDocument(sealed.ct, key, sealed.hash), doc); await assert.rejects(unwrapContentKey(sealed.wrap, new Uint8Array(32).fill(8))); }); test('link keys are strict', () => { assert.throws(() => fromUrlKey('short'), /Bad link key/); assert.throws(() => fromUrlKey(`${'A'.repeat(42)}=`), /Bad link key/); });