import '../lib/bare-globals.js'
// Signer.materialize: a linearized record set → ONE signed atproto commit. Proves it signs with
// the (shared) key, commits to exactly the record set (data CID = MST root), is order-independent
// and RFC6979-deterministic (any lease-holder → byte-identical output), and links via prev.
import test from 'brittle'
import { secp256k1 } from '@noble/curves/secp256k1'
import { verifyCommit } from 'atproto-repo/lib/commit.js'
import { materialize, buildCommitWrite } from '../lib/signer.js'
import * as dagCbor from '@ipld/dag-cbor'
import { cidForBytesSync } from 'atproto-repo/lib/cid.js'
const PRIV = new Uint8Array(Buffer.from('9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c', 'hex'))
const PUB = secp256k1.getPublicKey(PRIV, true)
const DID = 'did:plc:test22222222222222222222'
const POST = 'app.bsky.feed.post'
const recs = [
{ collection: POST, rkey: '3b', record: { $type: POST, text: 'b', createdAt: '2026-07-04T00:00:00.000Z' } },
{ collection: POST, rkey: '3a', record: { $type: POST, text: 'a', createdAt: '2026-07-04T00:00:00.000Z' } },
{ collection: 'app.bsky.actor.profile', rkey: 'self', record: { $type: 'app.bsky.actor.profile', displayName: 'x' } }
]
test('materialize signs a commit over exactly the record set', (t) => {
const out = materialize(recs, { did: DID, rev: '3lll000000000', privateKey: PRIV })
t.ok(verifyCommit(out.signedCommit, PUB), 'commit verifies against the shared key')
t.is(out.signedCommit.did, DID, 'did stamped into the commit')
t.is(out.signedCommit.prev, null, 'genesis-of-chain: prev null')
t.ok(out.signedCommit.data.equals(out.dataCid), 'commit.data == MST root (commits to these records)')
t.is(out.recordBlocks.length, 3, 'one canonical block per record')
})
test('order-independent: same records, different iteration order → same data root', (t) => {
const a = materialize(recs, { did: DID, rev: '3lll000000000', privateKey: PRIV })
const shuffled = [recs[2], recs[0], recs[1]]
const b = materialize(shuffled, { did: DID, rev: '3lll000000000', privateKey: PRIV })
t.ok(a.dataCid.equals(b.dataCid), 'MST root independent of view order')
t.ok(a.commitCid.equals(b.commitCid), 'same commit CID')
})
test('RFC6979-deterministic: same view+key+rev+prev → byte-identical commit', (t) => {
const a = materialize(recs, { did: DID, rev: '3lll000000000', prev: null, privateKey: PRIV })
const b = materialize(recs, { did: DID, rev: '3lll000000000', prev: null, privateKey: PRIV })
t.alike(a.signedCommit.sig, b.signedCommit.sig, 'identical signature (any lease-holder reproduces it)')
t.ok(a.commitCid.equals(b.commitCid), 'identical commit CID')
})
test('continue-from-head: prev links the chain', (t) => {
const head = materialize(recs, { did: DID, rev: '3lll000000000', privateKey: PRIV })
const next = materialize([...recs, { collection: POST, rkey: '3c', record: { $type: POST, text: 'c', createdAt: '2026-07-04T00:00:01.000Z' } }],
{ did: DID, rev: '3lll000000001', prev: head.commitCid, privateKey: PRIV })
t.ok(next.signedCommit.prev.equals(head.commitCid), 'next commit prev == head commit CID')
t.absent(next.dataCid.equals(head.dataCid), 'adding a record changed the data root')
t.ok(verifyCommit(next.signedCommit, PUB), 'next commit verifies')
})
test('guards', (t) => {
t.exception(() => materialize(recs, { rev: 'x', privateKey: PRIV }), /required/, 'missing did rejected')
t.exception(() => materialize(recs, { did: DID, privateKey: PRIV }), /required/, 'missing rev rejected')
})
test('buildCommitWrite: a CID-only `before` gives the byte-identical commit (fork-heal input)', (t) => {
const before = recs.slice(0, 2)
const after = [{ ...recs[0], record: { ...recs[0].record, text: 'b2' } }, recs[2]]
const meta = { did: DID, rev: '3mxtest000022', prev: null, privateKey: PRIV }
const full = buildCommitWrite(before, after, meta)
const cidOnly = before.map(({ collection, rkey, record }) => ({ collection, rkey, cid: cidForBytesSync(dagCbor.encode(record)).toString() }))
const lean = buildCommitWrite(cidOnly, after, meta)
t.ok(lean.commitCid.equals(full.commitCid), 'same signed commit')
t.ok(lean.prevData.equals(full.prevData), 'same prevData')
t.alike(lean.ops.map((o) => o.action + ' ' + o.path), full.ops.map((o) => o.action + ' ' + o.path), 'same ops')
})