OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / deterministic-signer.test.js

115 lines · javascript ref:
// 0c — DETERMINISTIC SIGNING: two co-owners racing on the SAME tip produce the SAME commit.
//
// This is the assertion that makes the signer election REMOVABLE. Today sign() gates on
// holdsSigner() so exactly one device signs (signer-failover.test.js), because a wall-clock rev
// made two signers produce DIFFERENT commits — a fork. With rev derived deterministically from the
// current head (swarm-device _nextRev -> atproto-store nextRev), two co-owners continuing the same
// chain over the same finalized record set compute BYTE-IDENTICAL signed commits: concurrent
// signing is idempotent, not forking.
//
// The race is simulated with force:true on the second signer. force bypasses the forward-only gate,
// which would otherwise make B adopt A's just-written frame and no-op — and "B has not replicated
// A's frame yet" is precisely the concurrent case we need to exercise.
//
// NOTE on scope: the SIGNED commit is byte-identical (commitCid, sig, rev, prev). The firehose
// entry's `seq` is NOT — it is derived from the log length at write time, so a racing writer stamps
// a different seq for the same rev. That field is firehose-server bookkeeping, outside the signed
// payload, and serve-membrane projects by array index rather than by seq. Asserted explicitly below
// so the distinction is recorded rather than assumed.
//
// Run: `bare test/deterministic-signer.test.js`
import '../lib/bare-globals.js'
import test from 'brittle'
import b4a from 'b4a'
import { secp256k1 } from '@noble/curves/secp256k1'
import { verifyCommit } from 'atproto-repo/lib/commit.js'
import { SwarmDevice } from '../lib/swarm-device.js'
import { frameKey } from 'atproto-room/lib/commit-line.js'

const PRIV = new Uint8Array(Buffer.from('9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c', 'hex'))
const PUB = secp256k1.getPublicKey(PRIV, true)
const DID = 'did:plc:determinism2222222222222'
const POST = 'app.bsky.feed.post'

// ONE shared Map = the converged co-owned view behind both devices (same shape as signer-failover).
function sharedView () {
  const m = new Map()
  const base = {
    async set (k, v) { m.set(k, v) },
    async del (k) { m.delete(k) },
    async get (k) { return m.has(k) ? m.get(k) : null },
    async getNow (k) { return m.has(k) ? m.get(k) : null },
    async list (p = '') { const o = {}; for (const [k, v] of m) if (k.startsWith(p)) o[k] = v; return o },
    async listNow (p = '') { const o = {}; for (const [k, v] of m) if (k.startsWith(p)) o[k] = v; return o }
  }
  return { base, map: m }
}
// Election-free posture: BOTH co-owners believe they may sign.
const bothSign = (base) => ({ ...base, holdsSigner: () => true })
const frames = (map) => [...map.keys()].filter((k) => k.startsWith('__commit__/'))

test('two co-owners signing the SAME transition produce byte-identical commits (election removable)', async (t) => {
  const { base, map } = sharedView()
  const devA = new SwarmDevice(bothSign(base), { did: DID, privateKey: PRIV, self: 'A', firehose: true })
  const devB = new SwarmDevice(bothSign(base), { did: DID, privateKey: PRIV, self: 'B', firehose: true })

  // ── genesis: A authors commit 1; B replicates it and lands on the SAME tip ──
  await devA.putRecord(POST, '3a', { $type: POST, text: 'a', createdAt: '2026-07-24T00:00:00.000Z' })
  const h1 = await devA.sign()
  t.ok(h1 && h1.commitCid, 'A signs commit 1')
  const restored = await devB.restore()
  t.ok(restored && restored.commitCid.equals(h1.commitCid), 'B restores to the SAME tip from the shared __commit__ log')

  // ── a new intent lands in the converged view; both co-owners are anchored at commit 1 ──
  await devA.putRecord(POST, '3b', { $type: POST, text: 'b', createdAt: '2026-07-24T00:00:01.000Z' })
  t.is(frames(map).length, 1, 'one frame in the log before the race')

  // ── THE RACE: both compute the next commit from the same tip + same finalized record set ──
  const h2a = await devA.sign()
  t.ok(h2a && h2a.commitCid, 'A signs commit 2')
  const h2b = await devB.sign({ force: true }) // B had not seen A's frame — the concurrent case
  t.ok(h2b && h2b.commitCid, 'B independently signs the same transition')

  // ── the property: identical inputs -> identical signed bytes (RFC6979 + deterministic rev) ──
  t.is(h2a.rev, h2b.rev, 'same rev — derived from the shared tip, not the wall clock')
  t.ok(h2a.commitCid.equals(h2b.commitCid), 'same commit CID — the two co-owners agree on the commit')
  t.is(b4a.compare(b4a.from(h2a.signedCommit.sig), b4a.from(h2b.signedCommit.sig)), 0, 'byte-identical signature (RFC6979 deterministic)')
  t.ok(h2a.signedCommit.prev.equals(h1.commitCid), 'A continues the chain from commit 1')
  t.ok(h2b.signedCommit.prev.equals(h1.commitCid), 'B continues the SAME chain from commit 1 — no fork')
  t.ok(verifyCommit(h2a.signedCommit, PUB), 'A commit 2 verifies against the shared key')
  t.ok(verifyCommit(h2b.signedCommit, PUB), 'B commit 2 verifies against the shared key')

  // ── idempotence: the frames collide on ONE key, so concurrent signers collapse to ONE frame.
  // This is the election-free replacement for signer-failover's "exactly ONE commit — no
  // double-sign": still exactly one, but because they AGREE, not because one was suppressed.
  const fs = frames(map)
  t.is(fs.length, 2, 'concurrent signers produced ONE new frame, not two — idempotent, no fork')
  // Keyed by rev AND commit CID (frameKey): identical commits still share one key — that IS this
  // idempotence — while a partition's different commits at one rev no longer overwrite each other.
  const key = frameKey(h2a.rev, h2a.commitCid)
  t.ok(fs.includes(key), 'the surviving frame is keyed by the agreed rev + commit')
  const survivor = map.get(key)
  t.is(survivor.commitCid, h2a.commitCid.toString(), 'the surviving frame carries the agreed commit')
  t.is(survivor.commit, survivor.commit, 'frame commit block present')

  // ── scope note, asserted rather than assumed: `seq` is NOT part of the agreement ──
  t.ok(typeof survivor.seq === 'number', 'the frame carries a seq (firehose-server bookkeeping)')
})

test('rev advances monotonically across a deterministic 3-commit chain', async (t) => {
  const { base, map } = sharedView()
  const dev = new SwarmDevice(bothSign(base), { did: DID, privateKey: PRIV, self: 'A', firehose: true })

  const revs = []
  for (const rkey of ['3a', '3b', '3c']) {
    await dev.putRecord(POST, rkey, { $type: POST, text: rkey, createdAt: '2026-07-24T00:00:00.000Z' })
    const h = await dev.sign()
    t.ok(h, `commit for ${rkey}`)
    revs.push(h.rev)
  }
  t.is(frames(map).length, 3, 'three distinct frames')
  t.ok(revs[1] > revs[0] && revs[2] > revs[1], 'rev strictly increasing (relay forward-only cursor accepts)')
  t.is(new Set(revs).size, 3, 'no rev collisions')
  t.ok(revs.every((r) => r.length === 13), 'every rev is a 13-char atproto TID')
})