import test from 'brittle'
import b4a from 'b4a'
import { verifyEnvelope, signTestEnvelope, EnvelopeError } from '../lib/auth/envelope.js'
import { ReplayCache } from '../lib/auth/replay-cache.js'
import { createKeetVerifier, createTestChain } from '../lib/auth/keet-verifier.js'
// Phase 2b tests — keet device-chain verification wired into envelopes.
//
// createTestChain() spins up a fresh keet identity + one attested device
// (via IdentityKey.bootstrap), returning both an anproto keypair string
// for signing envelopes AND the base64-encoded chain proof for the
// envelope.chain field.
test('keet: chain-bearing envelope resolves to identity root, not device', async (t) => {
const { anKeypair, chainB64, rootHex, deviceHex } = await createTestChain()
const { payload, sig } = await signTestEnvelope({
keypair: anKeypair,
method: 'whoami',
params: {}
})
const verified = await verifyEnvelope(
{ payload, sig, chain: chainB64 },
{ replayCache: new ReplayCache(), keetVerifier: createKeetVerifier() }
)
t.is(verified.method, 'keet', 'method is keet when chain resolves')
t.is(verified.rootPubkey, rootHex, 'rootPubkey is the chain identity root')
t.is(verified.devicePubkey, deviceHex, 'devicePubkey is the signing device')
t.not(verified.rootPubkey, verified.devicePubkey, 'root differs from device')
})
test('keet: chain for a different device is rejected', async (t) => {
const chainA = await createTestChain()
const chainB = await createTestChain()
// Sign envelope with chainA's device, but attach chainB's chain. The
// chain vouches for chainB's device — the signer is chainA's device —
// binding check should fail.
const { payload, sig } = await signTestEnvelope({
keypair: chainA.anKeypair,
method: 'x',
params: {}
})
try {
await verifyEnvelope(
{ payload, sig, chain: chainB.chainB64 },
{ replayCache: new ReplayCache(), keetVerifier: createKeetVerifier() }
)
t.fail('should have thrown: chain/device mismatch')
} catch (err) {
t.ok(err instanceof EnvelopeError, 'EnvelopeError thrown')
t.is(err.code, 'CHAIN_INVALID', 'CHAIN_INVALID for device mismatch')
t.ok(/does not match/.test(err.message), 'error mentions mismatch')
}
})
test('keet: malformed chain bytes are rejected', async (t) => {
const { anKeypair } = await createTestChain()
const { payload, sig } = await signTestEnvelope({
keypair: anKeypair, method: 'x', params: {}
})
try {
await verifyEnvelope(
{ payload, sig, chain: 'not-a-real-chain' },
{ replayCache: new ReplayCache(), keetVerifier: createKeetVerifier() }
)
t.fail('should have thrown')
} catch (err) {
t.ok(err instanceof EnvelopeError, 'EnvelopeError thrown')
t.is(err.code, 'CHAIN_INVALID', 'CHAIN_INVALID for garbage chain')
}
})
test('keet: chain-bearing envelope with empty chain string is rejected', async (t) => {
const { anKeypair } = await createTestChain()
const { payload, sig } = await signTestEnvelope({
keypair: anKeypair, method: 'x', params: {}
})
try {
await verifyEnvelope(
{ payload, sig, chain: '' },
{ replayCache: new ReplayCache(), keetVerifier: createKeetVerifier() }
)
// Empty string is falsy, so envelope.js takes the non-chain path.
// Caller sees a normal self-rooted result. This test just pins that
// behavior so we notice if it changes.
t.pass('empty chain treated as absent (self-rooted path)')
} catch (err) {
t.fail('empty chain should not throw — it is the self-rooted path: ' + err.message)
}
})
test('keet: two different envelopes signed by the same device both resolve to same root', async (t) => {
const { anKeypair, chainB64, rootHex } = await createTestChain()
const a = await signTestEnvelope({ keypair: anKeypair, method: 'one', params: {} })
const b = await signTestEnvelope({ keypair: anKeypair, method: 'two', params: {} })
const replayCache = new ReplayCache()
const keetVerifier = createKeetVerifier()
const resA = await verifyEnvelope({ ...a, chain: chainB64 }, { replayCache, keetVerifier })
const resB = await verifyEnvelope({ ...b, chain: chainB64 }, { replayCache, keetVerifier })
t.is(resA.rootPubkey, rootHex, 'A → identity root')
t.is(resB.rootPubkey, rootHex, 'B → identity root')
t.is(resA.rootPubkey, resB.rootPubkey, 'both envelopes share the same root')
t.is(resA.devicePubkey, resB.devicePubkey, 'both envelopes from same device')
})
test('keet: chain for attested envelope decodes to a non-empty buffer', async (t) => {
const { chainB64 } = await createTestChain()
const buf = b4a.from(chainB64, 'base64')
t.ok(buf.length > 32, 'chain buffer is non-trivial (> 32 bytes)')
})