import { describe, it, expect, beforeEach } from 'vitest' import { signDeviceAuthProof, verifyDeviceProof, deviceProofAccount, } from '../src/capability/device-proof.js' import { setupP5SigningKey, makeEphemeral, principal } from './_helpers.js' import { randomBytes } from 'node:crypto' const NOW = 1_700_000_000 async function bindingA() { return { clientEphPub: new Uint8Array(randomBytes(32)), clientNonce: new Uint8Array(randomBytes(24)), } } describe('device-auth proof (§4.4 / §6b)', () => { let signingKey: CryptoKey beforeEach(async () => { ;({ signingKey } = await setupP5SigningKey()) }) it('verifies a proof bound to THIS handshake (accountId asserted)', async () => { const binding = await bindingA() const proof = await signDeviceAuthProof(principal('acct-A'), binding, signingKey, NOW) expect(await verifyDeviceProof(proof, binding, NOW)).toBe(true) expect(deviceProofAccount(proof)).toBe('acct-A') }) it('rejects a proof replayed into a DIFFERENT handshake (different clientEphPub)', async () => { const bindingA1 = await bindingA() const proof = await signDeviceAuthProof(principal('acct-A'), bindingA1, signingKey, NOW) const bindingB = await bindingA() // fresh ephemeral / nonce expect(await verifyDeviceProof(proof, bindingB, NOW)).toBe(false) }) it('rejects a proof signed by a non-P5 key (unbound/forged bearer)', async () => { const binding = await bindingA() const other = await makeEphemeral() const proof = await signDeviceAuthProof(principal('acct-A'), binding, other.privateKey, NOW) // verify key is still P5's key from setup; other.privateKey is not P5's signer expect(await verifyDeviceProof(proof, binding, NOW)).toBe(false) }) it('rejects a tampered proof', async () => { const binding = await bindingA() const proof = await signDeviceAuthProof(principal('acct-A'), binding, signingKey, NOW) const tampered = proof.slice(0, -2) + (proof.endsWith('AA') ? 'BB' : 'AA') expect(await verifyDeviceProof(tampered, binding, NOW)).toBe(false) }) it('rejects a stale proof (outside freshness window)', async () => { const binding = await bindingA() const proof = await signDeviceAuthProof(principal('acct-A'), binding, signingKey, NOW) expect(await verifyDeviceProof(proof, binding, NOW + 10_000)).toBe(false) }) })