// Phase A — offline conformance for the atproto-base signer (lib/signer.js).
//
// signer.js reimplements MST+commit origination SEPARATELY from atproto-repo's createRepo (so the
// swarm can continue-from-head + emit the record blocks). createRepo is already conformance-proven
// in atproto-repo/test (byte-exact vs the atmst reference oracle + the golden bridgetest repo, and
// verifies against the did:key). So the strongest offline check is EQUIVALENCE: for the same
// records/did/rev the two paths must produce byte-identical dataCid/commitCid/signature. Plus an
// independent signature verify against the DID key, and the prev (continue-from-head) path that
// createRepo does not cover. The goat external-oracle gate is scripts/goat-cross-check.mjs.
import '../lib/bare-globals.js'
import test from 'brittle'
import b4a from 'b4a'
import * as dagCbor from '@ipld/dag-cbor'
import { materialize } from '../lib/signer.js'
import { createRepo } from 'atproto-repo/lib/repo.js'
import { verifyCommit } from 'atproto-repo/lib/commit.js'
import { cidForBytesSync } from 'atproto-repo/lib/cid.js'
import { deriveAtprotoKey } from 'atproto-identity'
import { decodeDidKey } from 'atproto-identity/lib/did-key.js'
const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
const DID = 'did:plc:yu4kgcktm7qtrwo5ofyiaf4c' // the did:plc for this mnemonic (see atproto-repo commit-car.test.js)
const REV = '3mmwu7vak2222' // a valid 13-char TID (atproto requires rev be a TID; goat rejects short revs)
const RECORDS = [
{ collection: 'app.bsky.feed.post', rkey: '3aaa', record: { $type: 'app.bsky.feed.post', text: 'first', createdAt: '2026-07-01T00:00:00.000Z' } },
{ collection: 'app.bsky.feed.post', rkey: '3bbb', record: { $type: 'app.bsky.feed.post', text: 'second', createdAt: '2026-07-01T00:00:01.000Z' } },
{ collection: 'app.bsky.actor.profile', rkey: 'self', record: { $type: 'app.bsky.actor.profile', displayName: 'test' } }
]
const key = deriveAtprotoKey(MNEMONIC)
test('signer.materialize is byte-exact vs the conformance-proven createRepo (genesis)', async (t) => {
const viaSigner = materialize(RECORDS, { did: DID, rev: REV, prev: null, privateKey: key.privateKey })
const viaCreateRepo = await createRepo({ records: RECORDS, did: DID, rev: REV, privateKey: key.privateKey })
t.ok(viaSigner.dataCid.equals(viaCreateRepo.dataCid), 'MST data root matches createRepo (the atmst-proven path)')
t.ok(viaSigner.commitCid.equals(viaCreateRepo.commitCid), 'commit CID matches createRepo')
t.ok(b4a.equals(viaSigner.signedCommit.sig, viaCreateRepo.signedCommit.sig), 'signature bytes identical (RFC6979-deterministic)')
})
test('signer record blocks are byte-exact canonical DAG-CBOR (record CIDs preserved)', (t) => {
const { recordBlocks } = materialize(RECORDS, { did: DID, rev: REV, privateKey: key.privateKey })
t.is(recordBlocks.length, RECORDS.length, 'one block per record')
for (let i = 0; i < RECORDS.length; i++) {
const bytes = dagCbor.encode(RECORDS[i].record)
t.ok(recordBlocks[i].cid.equals(cidForBytesSync(bytes)), `record ${i} CID = cid(dag-cbor(record))`)
const rt = dagCbor.encode(dagCbor.decode(recordBlocks[i].bytes))
t.ok(b4a.equals(b4a.from(rt), b4a.from(recordBlocks[i].bytes)), `record ${i} bytes round-trip stable`)
}
})
test('signer commit verifies against the DID key (independent verify + tamper)', (t) => {
const { signedCommit } = materialize(RECORDS, { did: DID, rev: REV, privateKey: key.privateKey })
const { pubkey, curve } = decodeDidKey(key.didKey)
t.is(curve, 'secp256k1', 'the #atproto key is secp256k1')
t.ok(signedCommit.sig instanceof Uint8Array && signedCommit.sig.length === 64, 'sig is a 64-byte (r,s) low-S sig')
t.ok(verifyCommit(signedCommit, pubkey), 'signature verifies against the did:key')
t.ok(!verifyCommit({ ...signedCommit, rev: signedCommit.rev + 'x' }, pubkey), 'tampered commit fails verification')
})
test('continue-from-head: prev is carried and the chain verifies (signer-specific path)', (t) => {
const genesis = materialize(RECORDS, { did: DID, rev: '3mmwu7vak2222', prev: null, privateKey: key.privateKey })
const grown = RECORDS.concat([{ collection: 'app.bsky.feed.post', rkey: '3mmwu7vak2422', record: { $type: 'app.bsky.feed.post', text: 'third', createdAt: '2026-07-01T00:00:02.000Z' } }])
const next = materialize(grown, { did: DID, rev: '3mmwu7vak2322', prev: genesis.commitCid, privateKey: key.privateKey })
const { pubkey } = decodeDidKey(key.didKey)
t.ok(next.signedCommit.prev && next.signedCommit.prev.equals(genesis.commitCid), 'next commit.prev = genesis commit CID')
t.ok(verifyCommit(next.signedCommit, pubkey), 'continued commit verifies against the did:key')
t.ok(!next.dataCid.equals(genesis.dataCid), 'the new record changed the data root')
})