P3.6.1 — VRF Stub (Leader Election) — Verification

Step 5 of the 5-step chain. The implementation (Step 4) landed at SHA 1ea60a04. This document captures the test evidence binding the deliverable to contract §9 (AC#1–AC#23).

§1. Commits — 5-step chain

Step Output SHA
1 — audit docs/audits/p3-6-1-vrf-stub-audit.md ffc38127
2 — contract docs/contracts/p3-6-1-vrf-stub-contract.md 80266e15
3 — packet docs/packets/p3-6-1-vrf-stub-packet.md 6743271c
4 — implement src/domains/consensus/vrf-stub.ts + test file 1ea60a04
5 — verify this file (this commit)

Branch: feature/p3-6-1-vrf-stub Worktree: .worktrees/claude/p3-6-1-vrf-stub Base: origin/main @ 498e6ea5 (R89 θ Wave 2 close)

§2. Gate results

All three gates pass on 1ea60a04 (the implementation commit):

$ npm run build
> tsc
> node scripts/copy-migrations.mjs
copy-migrations: copied 8 migration(s) ... -> dist/db/migrations
# exit 0
$ npm run lint
> eslint src
# exit 0 — zero errors, zero new warnings
$ npm test
Test Suites: 61 passed, 61 total
Tests:       2861 passed, 2861 total
Snapshots:   0 total
Time:        31.672 s

Test delta

Surface Count
Baseline at 498e6ea5 (Wave 2 close, pre-this-slice) 2820
This slice (vrf-stub.test.ts) +41
Total at 1ea60a04 2861

(Baseline 2820 = 2861 − 41 derived from the suite total minus the exclusive contribution of the new test file. The 2744 number quoted in the dispatch packet predates Waves 1 + 2; the worktree’s actual starting point includes their +76 tests.)

Per-slice isolation run

vrf-stub.test.ts runs cleanly in isolation:

$ node --experimental-vm-modules node_modules/jest/bin/jest.js \
       src/__tests__/domains/consensus/vrf-stub.test.ts
Test Suites: 1 passed, 1 total
Tests:       41 passed, 41 total

Coverage of src/domains/consensus/vrf-stub.ts is 100% stmts / 100% funcs / 100% lines / 94.4% branches — the one uncovered branch is the ?? '' fallback at the end of selectLeader, which is unreachable by construction (the bigint modulo guarantees the index exists) and exists only to satisfy TS narrowing.

§3. AC traceability — AC#1 through AC#23

AC Description Test path Result
AC#1 output and proof are both 32 bytes vrfEval — shape > output and proof are both 32 bytes for fixed args PASS
AC#2 determinism over 10000× vrfEval — determinism > 10000 calls on identical args produce byte-identical outputs PASS (234 ms)
AC#3 collision-free over 10000 distinct inputs vrfEval — collision-free > 10000 distinct (seed, input) pairs under K1 yield 10000 distinct outputs PASS (Set.size === 10000)
AC#4 key sensitivity vrfEval — key sensitivity > distinct privKeys ... PASS
AC#5 rejects non-Buffer seed vrfEval — input validation > rejects non-Buffer seed with VrfError PASS
AC#6 rejects non-Buffer input ... > rejects non-Buffer input with VrfError PASS
AC#7 rejects non-Buffer privKey ... > rejects non-Buffer privKey with VrfError PASS
AC#8 rejects empty privKey ... > rejects empty privKey with VrfError PASS
AC#9 accepts zero-length seed/input vrfEval — shape > accepts zero-length seed + accepts zero-length input PASS (2 tests)
AC#10 verify roundtrip — correct key vrfVerify — roundtrip > eval then verify with same key returns true PASS
AC#11 verify rejects wrong key vrfVerify — rejection paths > wrong pubKey returns false PASS
AC#12 verify rejects tampered output ... > tampered output (bit flip) returns false PASS
AC#13 verify rejects tampered proof ... > tampered proof (bit flip) returns false PASS
AC#14 verify rejects tampered seed ... > tampered seed returns false PASS
AC#15 verify defensive — non-Buffer args ... > non-Buffer arguments return false without throwing PASS (each position swapped)
AC#16 selectLeader determinism selectLeader — determinism + single-arbiter > determinism: 1000 iterations on identical args agree PASS
AC#17 selectLeader single-arbiter ... > single-arbiter committee returns the sole arbiter PASS
AC#18 selectLeader uniformity smoke selectLeader — uniformity smoke > 1000 round_ids over 4 arbiters: chi-squared below 16.27 (99% CI) PASS (loose 99.9% CI for df=3)
AC#19 selectLeader variable sizes selectLeader — variable arbiter sizes > returns a valid arbiter for n = %i (parameterized: 1, 2, 3, 4, 7, 10, 13, 100) PASS (8 cases)
AC#20 selectLeader empty ... > empty arbiters list throws VrfError PASS
AC#21 VrfProvider interface VrfProvider interface > defaultVrf is a HmacVrfProvider instance ... + vrfEval delegates byte-identically through defaultVrf + HmacVrfProvider satisfies the VrfProvider interface shape PASS (3 tests)
AC#22 module-corpus token scan vrf-stub.ts — module-corpus invariants > no Math.* / no Date.* / no dotted crypto.X / no Number( / no floating-point literal PASS (5 tests)
AC#23 header RFC 9381 + externally verifiable ... > header declares NOT RFC 9381 and externally verifiable + header cites ADR-002 Option A and ADR-005 Decision PASS (2 tests)

Additional safety nets (beyond the formal AC list):

  • selectLeader — determinism + single-arbiter > distinct round_ids produce distinct leaders at least once (catches a stuck/biased implementation; observed >1 distinct arbiter across 100 round_ids).
  • selectLeader — variable arbiter sizes > non-array arbiters throws VrfError and > non-Buffer seed throws VrfError (input-validation rounding-out beyond AC#20).
  • vrfVerify — rejection paths > non-Buffer arguments return false without throwing exercises eight argument-mutation cases plus empty pubKey and wrong-length output/proof.

§4. Acceptance summary

  • 41 / 41 new tests pass in isolation.
  • 2861 / 2861 total tests pass across 61 suites.
  • 100% statement / 100% function / 100% line coverage of vrf-stub.ts; 94.4% branch (single unreachable narrowing branch).
  • npm run build and npm run lint both exit 0.
  • No new npm dependency. @noble/curves deferred to Phase 1.5 per ADR-002 §Option B.
  • Module header carries the mandatory “NOT RFC 9381” and “externally verifiable” disclosures (test-asserted).
  • VrfProvider interface is implementation-agnostic; the Phase 1.5 Option B swap touches only defaultVrf’s right-hand side.
  • selectLeader correctly serves the Phase 0 single-arbiter posture (fast path) and the multi-arbiter view-change flow alike.

§5. ADR-002 status disclosure

ADR-002 remains PROPOSED at slice close. This deliverable ships Option A per the dispatch packet’s unconditional ship directive. The PROPOSED status is reflected in:

  • Module-header citation: “ADR-002 §Option A” (not “ADR-002 §Accepted-Decision”).
  • The mandatory NOT-RFC-9381 / NOT-externally-verifiable disclosures.
  • Audit §2.2 acknowledges Option B as the future swap target without asserting it has been adopted.

When ADR-002 graduates to Accepted with Option B, the swap is:

  1. npm install @noble/curves (or peer-dep route if already present in the tree via @noble/hashes).
  2. Implement NobleCurvesVrfProvider in vrf-stub.ts (or move both into src/domains/consensus/vrf/index.ts with two provider files).
  3. Replace export const defaultVrf: VrfProvider = new HmacVrfProvider(); with ... = new NobleCurvesVrfProvider();.
  4. Update the module-header warning to point at the new behavior (drop the “externally verifiable” caveat).
  5. Existing callers of vrfEval / vrfVerify / selectLeader see no API change. The 41 tests survive unchanged; new ECVRF-specific tests are added alongside.

§6. Risks confirmed mitigated

Risk Mitigation Verification
Caller misuses VRF stub for external proofs Header warning + AC#23 test PASS
Modulo bias in selectLeader Documented in source + AC#18 uniformity smoke PASS (χ² well under the loose 16.27 cap)
Premature @noble/curves swap No dep added; package.json untouched Confirmed by git diff origin/main -- package.json showing no change
Non-power-of-2 arbiter sizes AC#19 covers {1,2,3,4,7,10,13,100} PASS (8 cases)
Module-corpus token leak AC#22 self-scan PASS (5 token classes asserted absent)

§7. Self-review

  • All gates green on 1ea60a04.
  • 41 new tests landed in src/__tests__/domains/consensus/vrf-stub.test.ts.
  • 100% / 100% / 100% / 94.4% coverage of the new module.
  • AC#1–AC#23 traced to specific test names.
  • No new npm dependency.
  • Module header citation chain complete (audit / contract / packet + ADR-002 §Option A + ADR-005 §Decision + consensus.md §View-change).
  • Forbidden-token scan inheritance verified (matches gossip-wire / time-anchors pattern).
  • selectLeader Phase 0 single-arbiter fast path tested.
  • selectLeader Phase 5+ multi-arbiter uniformity tested.

Ready for PR.


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.