// Derivation + signing, all offline. Covers:
// - did:key codec validated against the golden P-256 values (round-trip)
// - deterministic secp256k1 derivation (same mnemonic -> same key/did)
// - genesis sign -> verify round-trip, and tamper-detection
// - full mnemonic -> did:plc flow is stable
import test from 'brittle'
import { decodeDidKey, encodeDidKey } from '../lib/did-key.js'
import {
deriveAtprotoKey, buildGenesisOp, signOp, verifyOp,
createAtprotoIdentity, unsignedOp, didForCreateOp
} from '../lib/atproto-identity.js'
const b4a = (a, b) => Buffer.from(a).equals(Buffer.from(b))
// A fixed, well-known BIP39 test mnemonic (NOT a real identity).
const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
test('did:key codec round-trips golden P-256 values', (t) => {
// did:key codec — golden P-256 values from millipds must round-trip byte-exact.
for (const golden of [
'did:key:zDnaeWUTD9gF8QMQ9WDh5t12ojkohiKGWUCrCnAGvu1CSJpyj',
'did:key:zDnaepXNQybJv5o8DM5AdmbpnedfApGv58q2UiDyhMgP5SFHx'
]) {
const { curve, pubkey } = decodeDidKey(golden)
t.is(encodeDidKey(curve, pubkey), golden, `did:key P-256 round-trips (${golden.slice(0, 20)}…)`)
}
})
test('deterministic secp256k1 derivation', (t) => {
const k1 = deriveAtprotoKey(MNEMONIC)
const k2 = deriveAtprotoKey(MNEMONIC)
t.ok(b4a(k1.publicKey, k2.publicKey), 'same mnemonic -> same secp256k1 pubkey')
t.ok(k1.didKey.startsWith('did:key:zQ3s'), 'derives a secp256k1 did:key (zQ3s…)')
t.ok(b4a(decodeDidKey(k1.didKey).pubkey, k1.publicKey), 'did:key decodes back to the same pubkey')
t.ok(!b4a(deriveAtprotoKey(MNEMONIC, "m/44'/0'/0'/0/1").publicKey, k1.publicKey), 'different path -> different key')
})
test('genesis sign -> verify round-trip + tamper detection', (t) => {
const k1 = deriveAtprotoKey(MNEMONIC)
const unsigned = buildGenesisOp({ signingDidKey: k1.didKey, handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
const sig = signOp(unsigned, k1.privateKey)
t.ok(verifyOp(unsigned, sig, k1.publicKey), 'own signature verifies')
const tampered = { ...unsigned, alsoKnownAs: ['at://mallory.7rnx.net'] }
t.ok(!verifyOp(tampered, sig, k1.publicKey), 'tampered op fails verification')
})
test('full flow is deterministic and self-consistent', (t) => {
const idA = createAtprotoIdentity(MNEMONIC, { handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
const idB = createAtprotoIdentity(MNEMONIC, { handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
t.is(idA.did, idB.did, 'same mnemonic+params -> same did:plc')
t.ok(/^did:plc:[a-z2-7]{24}$/.test(idA.did), 'did:plc has the expected shape')
t.is(didForCreateOp(idA.signedOp), idA.did, 'did recomputes from the signed op')
t.ok(verifyOp(unsignedOp(idA.signedOp), idA.signedOp.sig, idA.publicKey), 'signed op verifies end-to-end')
t.comment(`derived did: ${idA.did} handle: alice.7rnx.net`)
})