import { describe, expect, it } from 'vitest' import { ContractDecodeError, NONCE_BYTES, decodeEnvelope, encodeEnvelope, nonceForSeq, type E2EEnvelope, } from '../src/index.js' const sample = (over: Partial = {}): E2EEnvelope => ({ seq: 1n, nonce: nonceForSeq(1n, 'aes-256-gcm'), ciphertext: Uint8Array.of(0xde, 0xad, 0xbe, 0xef), tag: new Uint8Array(16).fill(0x11), ...over, }) describe('§4.4 deterministic nonce = f(seq)', () => { it('KAT: seq 0x0102 in the last 8 bytes of a 12-byte GCM nonce', () => { expect([...nonceForSeq(0x0102n, 'aes-256-gcm')]).toEqual([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x02, ]) }) it('produces the correct width per alg', () => { expect(nonceForSeq(1n, 'aes-256-gcm').length).toBe(NONCE_BYTES['aes-256-gcm']) expect(nonceForSeq(1n, 'xchacha20-poly1305').length).toBe(NONCE_BYTES['xchacha20-poly1305']) }) it('is deterministic and collision-free across distinct seq', () => { const a = nonceForSeq(5n, 'xchacha20-poly1305') const b = nonceForSeq(5n, 'xchacha20-poly1305') const c = nonceForSeq(6n, 'xchacha20-poly1305') expect([...a]).toEqual([...b]) expect([...a]).not.toEqual([...c]) }) }) describe('§4.4 E2EEnvelope binary codec', () => { it('KAT: encodes a known small envelope to a fixed byte vector', () => { const env: E2EEnvelope = { seq: 1n, nonce: Uint8Array.of(1, 2, 3), ciphertext: Uint8Array.of(0xaa), tag: Uint8Array.of(0xbb), } expect([...encodeEnvelope(env)]).toEqual([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // seq 0x03, // nonceLen 0x00, 0x00, 0x00, 0x01, // ciphertextLen 0x01, // tagLen 0x01, 0x02, 0x03, // nonce 0xaa, // ciphertext 0xbb, // tag ]) }) it('round-trips a realistic envelope (identity)', () => { const env = sample() const decoded = decodeEnvelope(encodeEnvelope(env)) expect(decoded.seq).toBe(env.seq) expect([...decoded.nonce]).toEqual([...env.nonce]) expect([...decoded.ciphertext]).toEqual([...env.ciphertext]) expect([...decoded.tag]).toEqual([...env.tag]) }) it('preserves a large 64-bit seq', () => { const env = sample({ seq: 0xfffffffffffffff0n }) expect(decodeEnvelope(encodeEnvelope(env)).seq).toBe(0xfffffffffffffff0n) }) it('rejects a truncated envelope header', () => { expect(() => decodeEnvelope(Uint8Array.of(0, 1, 2))).toThrow(ContractDecodeError) }) it('rejects a length mismatch (trailing/short bytes)', () => { const bytes = encodeEnvelope(sample()) expect(() => decodeEnvelope(bytes.subarray(0, bytes.length - 1))).toThrow(/length mismatch/) }) })