spec: revert accumulator to on-chain half-aggregated nullifiers (implements research#16) - #97
Conversation
…ements research#16)
Review status — held at draft pending one soundness decisionTwo parallel review lenses were run over this branch: conformance (CONTRIBUTING/style, English, internal anchors/links, #96 §9 edit-map coverage, hygiene, no Conformance — clean after one fix
Logic — one must-fix soundness gap (this is why the PR is held at draft)The headline invariant this revert exists to establish — every state-advancing transition in a coin's lineage is first-occurrence-anchored — is asserted in the prose (§2.1 clause 1 line 594; §2.3.3 step 2; §2.4 "No double-spend") but is not enforced by the enumerated predicate for a transition that advances state without creating a delivered coin. The only clauses that force an account-state nullifier into a canonical Concrete account-fork double-spend (the docs#92 class this PR claims to close): account X receives coin Proposed fix (mirrors the accepted clause 10(d), symmetric): extend clause 1's This is a substantive normative change to the core compliance predicate with hard-fork implications, so it is left for maintainer decision rather than applied unilaterally. The rest of the logic review is clean: accumulator determinism (pure function of Bitcoin, well-defined total order, no DA gate), conditional-NAV/reorg semantics, and the surviving hardening (nk_commit #79, coin-value #82, next-key hiding via Recommendation: resolve the clause-1 anchoring check before this moves out of draft. |
…cate (close whole-lineage double-spend) Clause 1 only enforced anchoring of a delivered coin's immediate creating transition (clause 10(d)); a pure receive, self-held mint, or genesis-receive advanced state (rotating current_pubkey) with no verifier-enforced anchor of its own (Pki, Ri). The prefix-NAV carry-forward only ever carried each ancestor's dependency set, never an ancestor's own nullifier leaf, so an account could fork a receive at one state, anchor only one branch, and spend the same coin twice under distinct rotated keys with no on-chain collision. Add, on every AccountUpdateProof, a predecessor-nullifier check: witness the previous transition's on-chain nullifier (Pk_prev, R_prev, R'_prev), require (Pk_prev, R_prev) a canonical member of w.nav via the same gadget clause 10(d) uses, and require R_prev to sign-to-contract-open H(prev_proof.ProofData) so the accumulator leaf at Pk_prev is exactly R_prev (leaf, not key-only). This is the faithful adaptation of the paper's per-transition union-membership check (ToSAccVVerifyUnionMembership): the single circuit cannot commit its own R before H(ProofData), so it binds the predecessor's nullifier, already on-chain at proving time. The anchoring invariant now tiles losslessly: 10(d) covers the immediate/cross-account edge, this check covers each own-lineage edge, and genesis is caught by the first successor's predecessor-check plus the Pk0 first-occurrence. Rewrite the descriptive whole-lineage-anchoring prose to point at the enforced clause. Residual Pk_prev key-binding and proving-cost impact flagged as open refinements.
…ssor key is a load-bearing open gap, not belt-and-suspenders Adversarial review of the predecessor-nullifier check found that binding only the leaf R_prev (via sign-to-contract to H(prev_proof.ProofData)) while leaving Pk_prev a free witness does NOT close the double-spend against a malicious prover: because the accumulator folds any validly-signed (Pk, R) pair by first-occurrence (proofs are off-chain) and raw single nullifiers are permissionless, an attacker can publish a naked nullifier under a fresh key it controls whose leaf opens H(prev_proof.ProofData), satisfying both membership and leaf checks without anchoring the fork loser's real (Pki, Ri) — so no first-occurrence collision arises and the fresh-key-substitution fork survives. Clause 10(d) / receive-check-4 carry the identical free-Pk_create gap. Reclassify the Pk_prev key-binding from an optional 'open refinement' to a normative Known soundness gap that is REQUIRED to close the fresh-key variant (the predecessor-check closes only the same-key fork), note it is the same systemic binding clause 10(d) needs, and make the closure statements in clause 1, 2.2, 2.3.3, 2.4 and 3.7 explicitly contingent on binding the consumed key. Flagged for the paper authors' review; no closure is claimed that the predicate does not enforce.
… fresh-key fork end-to-end Completes the predecessor-anchoring fix by binding the nullifier KEY, not only its leaf R. C now exposes each transition's consumed key (Pkᵢ = txn_pubkey, the current_pubkey it spends and publishes as its on-chain nullifier key) as a public output, distinct from serialize(ProofData) (so H(ProofData) and vectors V.4–V.6 are unchanged). Every on-chain-nullifier membership check is bound to it: - clause 1 predecessor-check: Pk_prev == prev_proof.consumed_pubkey (in-recursion); - clause 10(d) / receive-check-4: Pk_create == creating_proof.consumed_pubkey. Together with the existing leaf/S2C check this is airtight: the leaf pins a same-key fork loser (its stored leaf is the winner's R), the key pins a fresh-key substitution (a naked nullifier minted under an attacker key no longer matches the predecessor's exposed consumed key). This is the faithful port of the paper's ToSAccVVerifyUnionMembership, which binds BOTH the incoming account state's nullifier public key AND its tx commitment. Genesis is closed because the InitialProof exposes consumed_pubkey = Pk₀ (bound to the address via owner = H(Pk₀‖nk_commit)) and the first successor binds Pk_prev to it; the mint-fork the same way via a mint's successor/receiver. Remove all 'Known soundness gap' / contingent framing and rewrite clause 1, §1.4, §2.2, §2.3.3 steps 2 and 4, §2.4, §3.7, §6.5 as unconditionally enforced. Add the public-input-layout update (§2.5), a Requirement-2 privacy note (Pkᵢ is already the public on-chain nullifier key; consecutive keys stay unlinkable, rotation hidden in new_account_state_hash), and the proving-cost note (an equality on an exposed key, not a new gadget).
…x() leaf-preservation Follow-up hardening found by independent verification of the consumed-key closure. A (disclosure-surface fresh-key substitution — was still open). The key binding reached the in-circuit successor (clause 1 (iii)) and receiver (clause 10(d)) but NOT the out-of-circuit disclosure verifiers, where the attacker is the subject: a malicious subject could prove a valid C-proof of a fork-loser/never-anchored state and point a link at a fresh-key naked nullifier (R S2C-opening H(pi.ProofData), permissionless) to make it read completed / attest a false balance. Bind Pk to the proof's own consumed_pubkey everywhere first-occurrence anchoring is asserted for a third party: §5.6 step 3 (Pk_create == creating_proof.consumed_pubkey), §5.7 balance attestation (Pk_anchor == pi.consumed_pubkey, statement 5), §5.8 address view and §5.5 anchoring trail (inherit it), §1.5 creating_nullifier (tie Pk_create, not just R_create), and update §3.7 to name disclosure verifiers alongside successor/receiver. B (pin prefix() leaf-preservation). The same-key closure at recursion-internal hops lifts each hop's witnessed w.nav to the canonical top only via prefix(); its soundness needs prefix() to be a genuine LEAF-PRESERVING monotone SMT extension (an extension must not alter an existing leaf). Pin it normatively in §3.7 (a forged leaf Pkᵢ->R_loser at a deep hop cannot be a leaf-preserving sub-map of a canonical accumulator whose first-occurrence leaf is R_winner), reference it from clause 1 / clause 10(c), and clarify MAX_NAV_DEPTH=48 (prefix-chain admission count) is orthogonal to the accumulator's 256-bit SMT key length. C (editorial). §1.7.9: circuit_digest(C) reflects the new consumed_pubkey public output. §2.5: pin its encoding — 8 Goldilocks field elements (secp256k1 NonNative [U32Target; 8], as txn_pubkey), so C exposes 20 + 8 = 28 application public inputs.
…rification Round-4 review consistency fix. The §2.6 proving-cost note still described the clause-1 membership gadget as 'against MAX_NAV_DEPTH = 48', conflating the admission-count bound with the membership path length — the exact confusion the prefix()/orthogonality clarification removed elsewhere. Reword: the membership gadget is a 256-sibling Poseidon path (the accumulator is 256-bit-keyed, §1.7.6), bounded in admission count by MAX_NAV_DEPTH=48, not in path length (§2.5, §3.7). Also add §5.8 to the §5.5 anchoring-trail disclosure-verifier cross-ref for completeness.
…st-state + §3.10 cross-ref Cosmetic parity/cross-ref completeness (soundness-neutral; both review lenses flagged these as the only remaining nits): - §4.5 recovery step 4: state the inherited Pk_create == creating_proof.consumed_pubkey binding explicitly (already covered by the re-run §2.3.3 receive checks). - §7.5 latest-state selection: note each qualifying nullifier's key is bound to its transition's consumed_pubkey (re-run by the candidate's own recursive proof), so no fresh-key substitution qualifies. - §3.10 completed-requiring cross-ref list: add §5.8 alongside §2.3.3/§4.5/§5.6/§5.7.
Resolved — the clause-1 anchoring gap is closed end-to-end; moving out of draftThe soundness gap that held this PR at draft — every state-advancing transition in a coin's lineage must be first-occurrence-anchored, but the enumerated predicate did not enforce it for a transition that advances state without creating a delivered coin (pure receive / self-held mint / genesis-receive) — is now closed, verified across four adversarial review rounds (parallel logic/soundness + conformance lenses each round, plus an independent confirmation pass). Each round surfaced a deeper layer; all are now closed. What was found, layer by layer
Consistency / non-soundness updates
Open item (honestly scoped, not a soundness hole)The realizability of a fixed-size in-circuit gadget deciding the leaf-preserving-extension relation over a 256-bit SMT is an implementation/proving-cost question. The spec pins the relation (§3.7) and defers the gadget to the proving-cost / §1.7.8 review (§2.6, "not yet quantified"). The specification is sound independent of gadget efficiency. The reverted paper-model anchoring is now complete and self-consistent. Ready for review. |
…align encodings #97 already landed the on-chain half-aggregated state-nullifier model in specification.md. Recast the remediation/analysis/assurance/risks framing from 'accepted future direction' to 'normative via #97': F-01/F-02/F-04/F-06 are resolved in the spec (with § citations), Gate A is closed, and only the executable-conformance (vectors) and assurance (proofs/backend/audit) gates remain open. - risks.md: drop the retired public-BatchBundle/root-prefix ledger-safety claim and state the residual data-availability risk in V3 terms (§3.6/§4.6). - §1.1: align the domain strings and encodings with the shipped spec (m_state = 'zkCoins/v1/StateUpdate', H(ProofData), x-only R). - paper-conformance-analysis.md: English-only per CONTRIBUTING — English title and H1 (H1 above the snapshot blockquote), executive summary translated.
Adds the paper-deviation analysis (byte-pinned mirror of research#22) and the paper-conformance remediation register, and wires the release-gate banners into risks.md and assurance.md. Framed to reflect that PR #97 already made the on-chain half-aggregated state-nullifier model normative in specification.md: F-01/F-02/F-04/F-06 are resolved in the spec (Gate A closed), leaving only executable-conformance (vectors) and assurance (proofs/backend/audit) gates open. The §1 AggregateStateNullifierV3 object matches the shipped §3.5/§3.8 (no on-chain fee field or network_id; marker/version/format/u16-count/block_anchor layout). Documentation is English-only per CONTRIBUTING. Stacked on #99, which owns the risks.md/assurance.md body cleanup to the V3 model; this PR adds only the remediation banners and gate rows on top.
… (V3) Re-architects the v2 supply cap for the V3 on-chain half-aggregated nullifier model (#97), which removed the per-nf accumulator the original nf_mint mechanism relied on. V3-native enforcement: a v2 (capped) asset MUST be minted in the issuing account's genesis transition (send_counter == 0, current_pubkey == Pk0), so the mint consumes Pk0 and publishes (Pk0, R) on Bitcoin. Because asset_id binds creator_pubkey = Pk0 and every Pki is admitted to the global accumulator at most once by first-occurrence — across all accounts sharing Pk0 — the asset can be minted at most once globally. That single mint emits amount <= cap_total (exact wide-integer check), so total supply is provably <= cap_total and auditable by any holder from asset_terms. No nf_mint and no supply-specific accumulator: Pk0 first-occurrence is the uniqueness anchor. Adds AssetIdV2/IssuanceTermsV2 derivations (§1.4/§1.1), the v2 asset_terms payload (cap_total/terms_salt) and its §7.1 wire encoding, the version- dispatched receiver recompute (§2.3.3 step 6), and the v2 mint circuit branch (§2.1 clause 3 / §6.5).
Adds the paper-deviation analysis (byte-pinned mirror of research#22) and the paper-conformance remediation register, and wires the release-gate banners into risks.md and assurance.md. Framed to reflect that PR #97 already made the on-chain half-aggregated state-nullifier model normative in specification.md: F-01/F-02/F-04/F-06 are resolved in the spec (Gate A closed), leaving only executable-conformance (vectors) and assurance (proofs/backend/audit) gates open. The §1 AggregateStateNullifierV3 object matches the shipped §3.5/§3.8 (no on-chain fee field or network_id; marker/version/format/u16-count/block_anchor layout). Documentation is English-only per CONTRIBUTING. Stacked on #99, which owns the risks.md/assurance.md body cleanup to the V3 model; this PR adds only the remediation banners and gate rows on top.
Three drifts against the post-#97 spec: - fee section described the paper's first-to-publish-wins gossip race as the current model; spec §3.8 adopts spender-picks-publisher in v1 and defers the race. Rewrote the publisher-role bullets to v1 (pick a publisher, one fee coin under the shared ocr, re-pick/self-publish on censorship) and marked the gossip race as the deferred upgrade. - trade-off table called the 231-byte root chain 'current'; it is retired by #97. - a mint-verified sentence still read 'the specification must remove'; #97 removed it.
* docs: paper-conformance remediation register and analysis Adds the paper-deviation analysis (byte-pinned mirror of research#22) and the paper-conformance remediation register, and wires the release-gate banners into risks.md and assurance.md. Framed to reflect that PR #97 already made the on-chain half-aggregated state-nullifier model normative in specification.md: F-01/F-02/F-04/F-06 are resolved in the spec (Gate A closed), leaving only executable-conformance (vectors) and assurance (proofs/backend/audit) gates open. The §1 AggregateStateNullifierV3 object matches the shipped §3.5/§3.8 (no on-chain fee field or network_id; marker/version/format/u16-count/block_anchor layout). Documentation is English-only per CONTRIBUTING. Stacked on #99, which owns the risks.md/assurance.md body cleanup to the V3 model; this PR adds only the remediation banners and gate rows on top. * docs: reconcile the remediation register with the normative spec Three drifts against the post-#97 spec: - fee section described the paper's first-to-publish-wins gossip race as the current model; spec §3.8 adopts spender-picks-publisher in v1 and defers the race. Rewrote the publisher-role bullets to v1 (pick a publisher, one fee coin under the shared ocr, re-pick/self-publish on censorship) and marked the gossip race as the deferred upgrade. - trade-off table called the 231-byte root chain 'current'; it is retired by #97. - a mint-verified sentence still read 'the specification must remove'; #97 removed it. --------- Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
…ion-readiness pass) (#111) * docs: make the v1 assurance model self-contained — no external gates, instantiation final, surface frozen Project decision (2026-07-22): v1 ships without an external audit or any other human-gated mainnet step. The assurance weight moves to the executable conformance harness and the spec's internal soundness argument. - spec 1.7.8: reference instantiation is final for v1 (was: pending cryptographic review); any refinement is a version bump. New normative 'v1 freeze' clause: circuit shape (C, C_balance), 1.7 encodings and the section-7 wire formats are frozen once the digests are pinned; IssuanceTerms_v2 is part of the initial circuit build. - assurance: mainnet gate rewritten to machine-checkable criteria; external audit removed from workstream 2; paper proofs demoted to a quality goal. - remediation: F-05 closed (immutable plonky2 1.1.0 stays pinned), F-07 closed for v1, Gate C rewritten without external-review steps. - risks: the four residuals (hosted-prover redirect, co-output ocr, publisher ash-chain, fee pricing) are now accepted, final v1 boundaries (register IDs D-17..D-20), no longer open design work. - mandate: future-review escape hatch replaced by the version-bump rule. * spec: close the normative defects blocking autonomous implementation - S2C tweak preimage unified to t = H(bytes(R') || H(ProofData)) everywhere (clause 2 carried an undefined tag placeholder that contradicted 3.2/1.7.10). - awaiting_signature now surfaces all five ProofData fields plus proof_data_hash; the wallet MUST recompute H(ProofData) itself before signing (fail-closed), and the /sign body key is canonically s2c_nonce. - v2 mint witness lists cap_total/terms_salt (present iff version 2). - clause 7 balances update is an exact per-asset wide-integer equation. - clause 8 coin-history updates are constrained two-root transitions with explicit prior-leaf checks (admit requires H'_leaf(0), spend H'_leaf(1)) over witnessed history_update_paths — in-circuit replay-guard. - crediting may never rest on a single foreign Path-B answer: own Path A or agreeing multi-node fan-out, discrepancies fail closed. - asset_terms with an unknown issuance_version is malformed (receive step 6 and the 7.1 wire rule). - the 6-confirmation receive floor is hard (MAY more, MUST NOT fewer). - gadget freedom ends at the digest pin: network conformance reproduces the reference circuit exactly. - fuzzy message detection is explicitly not part of v1. - hash-derived scalars get big-endian interpretation and reduction rules. - privacy claims scoped precisely: publisher-link sentence corrected, 2.4 privacy row scoped to the on-chain observer, and a normative 'Precise v1 privacy statement' added to 6.7 (D-17..D-20 boundaries). * spec: pin the section-7 wire surface byte-exactly - 7.5: normative TransitionRequest JSON schema (kind-dependent presence rules, fail-closed malformed handling; receive/fee-less publication clarified) and exact pull/challenge + pull bodies mapping the 5.1 objects onto the wire. - 7.8: complete normative kernel.v1 proto contract for all 14 procedures, with the API-layer/kernel authority boundary made explicit (chan_bind is an opaque equality token kernel-side). - 7.6: publisher body field encodings pinned; fee-less hand-off defined. - 7.4: Blossom authorization event fully specified (kind 24242, tags, rejects, idempotent PUT response). - 7.1: nested CoinProof fixed-field layouts and a closed malformed-bundle enumeration. - 7.5: machine_code is now a closed enumeration with HTTP mappings; unknown conditions map to internal_error. * spec: complete the executable conformance harness - 3.2: even-y normalisation (step 1b) and tweak/nonce redraw rule (step 3b) — as previously written, roughly half of all signing attempts would have produced a signature no BIP-340 verifier accepts; 1.7.10 Sign() aligned. - New V.8: fully pinned synthetic signing + NISSHAC half-aggregation fixture (SHA-256/secp256k1 only; computed twice independently, byte-identical, all algebraic self-checks pass). Covers both key normalisation branches and a deterministic fixture nonce rule. - New V.9: normative negative-control table (mutation, aggregate tampering, duplicate member, malformed encodings, wrong network, bounded-reorg behaviour). - V.2-ext: the real BIP-39/BIP-32/HKDF derivation chain pinned end to end (reference mnemonic, all-hardened paths, nav_rand@0/1). - V.4 + /v1/info now pin circuit_digest(C_balance) alongside C. - V.7: node<->SDK parity matrix (byte-equal vs verify vs node-only). - mandate: the A-to-Z suite is a machine-evaluable pass predicate with fixed fixtures and hard assertions, including reorg, restore (Requirement 6) and portability (Requirement 10) controls. * mandate: add the normative 10-step path-to-mainnet runbook Ordered, machine-followable sequence from first circuit build to mainnet activation — every step with explicit inputs, outputs and a hard pass predicate, no human-gated stop (project decision 2026-07-22). The assurance gates now name the runbook as their executable form. * docs: make the deviation register live again + align conformance claims - paper-conformance-analysis: the audit stays a pinned snapshot, but the register table becomes the living index CONTRIBUTING points to — new authoritative 'Status (current develop)' column (six pre-#97 rows marked RETIRED, D-06 superseded by D-16, D-12 decided, D-13 partially superseded by token standard 2) and new rows D-17..D-20 for the accepted v1 boundaries. - spec: new 'Relationship to the source papers' section naming every load-bearing v1 deviation; requirements-traceability rows for Req 3 (freeze resistance) and Req 4 (own-node re-verification) fixed; intro box now references the register instead of claiming 'faithful'. - protocol/comparisons: construction-identity overclaims replaced by register-referencing wording (CONTRIBUTING paper-conformance rule). - remediation: Gate B wording matches the single-implementation reality; F-08 matrix timing concretized. - requirements: Req 9 partial (single-anchor attestation) signed off as final for v1. * spec: avoid MDX JSX evaluation of a brace set in V.2-ext/V.8 prose * docs: acceptance-sweep remediation — mirrors, dimensioning, closed verdicts specification.md: - S2C tweak mirrors unified (5.7 statement 4, glossary, remaining sites); glossary Path B / nf / Mint / Circuit digest entries aligned with the normative sections (the nf entry wrongly claimed on-chain disclosure). - V.3 worked example fixed to the post-mint state (coin_history_root@0, not the empty root) — it contradicted the exact clause-7 equation. - unknown inscription version/format bytes are fail-closed (3.5). - 5.7 statement 5 split: in-circuit key binding vs host-side anchor checks (inscription, first occurrence, completed). - clause-1 dependency attribution corrected (receives via 10(d), inputs inductively via the prefix carry-forward). - privacy scoping: clause 9 counterparty scope, D-19 generalised to any holder of two consecutive CoinProofs, D-18 output-count bucket. - 7.6 fee-less hand-off rule made single and consistent (no publisher-side S2C without a fee CoinProof; receivers verify downstream). - new MAX_HISTORY_UPDATES = 20 circuit constant with no-op slots (2.5); C_balance public-input layout pinned (2.5); closed network-tag set incl. zkCoins/v1/regtest with 1:1 short-name mapping; Path-B response byte layout + tip_block_hash; /sign field widths; SMT big-endian bit rule; Idempotency-Key semantics; ECDH x-only lift convention (1.1). - V.8 label strings explicit; V.9 gains N-11..N-15; N-10 requires fail-stop (health not ready) — mirrored as a 3.9 MUST; V.7 step 5 uses curve-valid fixture keys; parity matrix: SDK reproduces Poseidon values byte-equal, only circuit digests are node-only. other docs: - risks: closed 'Verdicts at a glance' table (18 rows, none open) — the mainnet incentive gate is now machine-checkable; D-18/D-19 scope notes. - assurance: verdict enum + accepted-v1-boundary, WS1 output = the risks table, testnet gate deep-review replaced by hard predicates, mainnet gate names Gates A–C. - remediation: 6-conf floor wording, Gate C machine-checkable, security package explicitly non-gating, F-08 paper-baseline column corrected. - analysis: snapshot sections declared non-release-governing; D-12/D-14 evidence cells superseded; D-09 verdict disentangled from D-20. - protocol/comparisons: privacy claims aligned with 6.7; 'full specification' points at the spec, not the paper. - mandate: runbook step 3 REGEN predicate honest about verification-locked cells; A-to-Z gains a second (capped, v2) asset — multi-asset discipline. * spec: crediting is Path-A-only; pin the V.10 note-encryption fixture; testnet = Signet - Crediting decisions now rest exclusively on the verifier's own Path-A accumulator (project decision: one mechanism, no multi-node quorum variant). Path-B answers are display/delegation only and can never back a credit — stated in 2.3.3 step 4, 3.7 and the glossary. - New V.10: the 1.3 note-key chain (esk/epk/IVPK/ss/K_tx/K_out/kb) pinned end to end under the 1.1 x-only ECDH lift — computed twice independently, byte-identical; parity-matrix row added. detect_tag stays REGEN (Poseidon), NIP-44 covered by its own vectors. - Runbook step 7 pins the public test chain to Bitcoin Signet. - Grammar fix in the clause-1 dependency bullet. * docs: resolve internal inconsistencies flagged in review - V.4: rename nav_rand@0 -> nav_rand_sample@0 (collided with the real HKDF value of the same name in V.2-ext); no pinned bytes changed - 3.2: correct 'pure receive publishes nothing' — a pure receive publishes its own on-chain state nullifier (2.3.3, 3.10) - Req-3 summary: the spending key is held only by the wallet, not 'no component' - publisher body: 'no coin plaintext' -> no payment/change plaintext; the fee CoinProof addressed to the publisher is the necessary exception - add zkCoins/v1/regtest to the network-tag list - block_anchor.height: align REST body + kernel proto to the on-chain u32, with an explicit out-of-range rejection - kernel.v1: add EntrustOperationalBundle / RevokeOperationalBundle procedures (+ proto messages) backing the /v1/bootstrap/entrust and /revoke endpoints - comparisons/spec: replace unqualified superlatives with verifiable claims * docs: scope conservation to no-inflation; fold change-drop into D-17 Clause 3 enforces only no-inflation (Out <= In) in-circuit. The earlier 'funds are conserved' overclaimed: returning the difference as change is part of the node-built output_templates witness, not a separate in-circuit check. A dishonest self-selected prover can therefore redirect OR drop (burn) the change - the same accepted v1 boundary D-17 as send-output redirection (the wallet trusts its own node; the trustless path is self-hosting, Req 4). - specification.md clause 3: precise no-inflation wording + D-17 reference - specification.md 6.6 (both trust-config statements): redirect-or-burn - paper-conformance-analysis.md D-17: scope extended to redirect-or-burn - risks.md: table row, heading, risk + mitigation aligned to redirect-or-burn - no-inflation mechanism (wide-integer >=) unchanged; the across-account no-inflation meaning of the requirements-traceability row kept intact * docs: close the real section-7 wire gaps (receipt-push endpoint + minor completions) - 7.5: add GET /v1/receipts/stream - the public, pull-session-gated (chan_bind) SSE front of kernel.v1 SubscribeReceipts (4.9 push source); typed Receipt event - 7.1: canonical self-delivery-record wire layout (CoinProof + M x {epk, out_ciphertext}) - 7.8: Scope message invariant (exactly one of all_assets / non-empty asset_ids) - 7.4 + /v1/info + kernel.v1 Info: advertise max_blob_bytes; Blossom DELETE returns 200, or 409 retention_hold when the blob is still a required replica - 7.6: publisher reason as a closed enum, batch_eta typed as u64 seconds - 7.8 SubscribeReceipts row now maps to GET /v1/receipts/stream Job-SSE union and out_ciphertext were already fully specified; no redundancy added. No test vectors / REGEN touched. * docs: escape pipe chars in the publisher reason enum (MDX table build fix) The unescaped | in reason?: "..." | "..." split the §7.6 table cell, which tore the inline-code span and left { in prose -> MDX expression error. Escaped to \| so the cell (and the { accepted, ... } span) stays intact. * docs: round-2 acceptance fixes — flows, wire gaps, harness corrections specification.md: - 2.3.1/2.3.2 wallet/node step order now matches the 7.5 handshake (intent first, sign after the witness-determined ProofData is surfaced). - 2.6 cost catalogue includes the in-circuit foreign-nullifier S2C openings (clause 1(ii) + 10(d)) — part of the pinned shape. - change formula includes Mint(a); stray 'pure receive publishes nothing' corrected; clause-1 induction covers clause-8 self-outputs. - public-input limb encoding pinned for C and C_balance; kernel.v1 gains EntrustBundle/RevokeBundle plus AttestBalance/IssueViewGrant with REST counterparts (/v1/attest/balance, /v1/grants) — Requirement 9 now has an executable surface; bootstrap bodies exact. - invoice_message framing and ViewGrant payload byte-exact; idempotency_conflict code; format-0x00 count rule; NullifierPath REST field; V.4 gains detect_tag/AssetIdV2/terms-hash rows; V.5 cell and V.7 step 5 single-sourced; N-01 canonical case; privacy mirrors (2.4 row, 5.6 scoped note, 6.7 D-17 breadth, glossary nav_opening). mandate/README/others: - A-to-Z corrected: v2 asset genesis-minted by a third account (Carol) with explicit outputs (6.5 forbids self-credit), Bob's balance asserted after the receive transition, Alice arithmetic fixed (999_749_000), new attestation and grant controls; build report defined as a normative artefact; step-7 predicate excludes regtest-only controls; freeze timing consistent (in force since the vectors pin); README testnet = Signet; analysis evidence cells closed; remediation NISSHAC import non-gating; CONTRIBUTING structure lists all living pages; risks hosted-prover text reflects the five-field recompute. * spec: unify the kernel entrust/revoke boundary after the parallel-session rebase Both lanes independently added the missing bundle procedures; keep one consistent authority model (7.8 rule: the API layer runs the 5.1 gate, the kernel never re-verifies and binds chan_bind opaquely) — the kernel-side OwnershipProof variant is removed, the fail-closed erasure comment is preserved, table descriptions reworded accordingly. * docs: close the gaps for autonomous implementation — append-only-log accumulator (D-05) + wallet-verifiable rotation (npk_commit, D-21) (#114) * docs: resolve the mechanical findings of both PR-111 reviews - Requirement 3 scoped precisely to the holder's own delegation boundary (the self-selected prover's D-17 redirect/burn limit) instead of an absolute 'no one can steal' that risks.md itself contradicts. - D-number ranges made consistent (privacy D-17..D-19 in 6.7; D-16 in 3.9; D-20 in the risks verdict table); Gate C reference fixed. - publisher 'never holds a coin/proof' gains its one exception (its own fee coin's CoinProof); glossary Publisher/Half-aggregation/issuance_version entries corrected. - /v1/attest/balance job now fits the kind/result contract ('attest_balance' + attestation field); clause-4 Path-B leftover removed (crediting is Path-A-only); C_balance statement binds R_anchor to the signature nonce; witness lists the 256-sibling membership paths clause 1(i)/10(d) require. - A-to-Z credit timing consistent with the deferred-credit model; three accounts named; runbook gains the full-stack step 4b; assurance REGEN exception and the expected-attempts wording corrected. * spec: replace the nullifier-accumulator SMT with an RFC-6962 append-only Merkle log Fixes the core soundness defect: the leaf-preserving SMT-submap prefix() was not provable in constant circuit size (linear in the foreign insertions between two of an account's transitions), and the naive weakening reopened a buried-fork-loser double-spend. - 1.7.6: the global nullifier accumulator is now a Certificate-Transparency Merkle tree (RFC 6962 / RFC 9162 over Poseidon) over the canonical first-occurrence sequence; first-occurrence = append, later occurrence = skip; leaf binds its position; nav_root binds size; a local Pk->(pos,R) index serves out-of-circuit first-occurrence/non-membership. - 3.7: prefix() becomes an RFC-6962 log-consistency proof and membership an inclusion proof, both written out normatively (SUBPROOF/PATH recursion, integer split point k = largest power of two < n). Constant-size, independent of n-m. Leaf-preservation is now a theorem (consistency composes; first-occurrence gives one winner per key), closing the buried-fork-loser without a per-leaf submap. - The construction is a port of a peer-reviewed primitive (CT log consistency); the exact algorithm was differential-tested against an RFC-6962 reference by two independent implementations (0 soundness violations over ~4300 cases). The in-circuit arithmetization of the consistency recursion remains the one element flagged for human cryptographic review (1.7.8). Coin-history SMT, on-chain object, and the ProofData layout are unchanged. MAX_NAV_DEPTH, the witness fields, the finality gate, C_balance and the test vectors follow in subsequent commits. * spec: align the compliance predicate with the append-only-log accumulator - 2.1 witness: nav_prefix -> nav_consistency (+ size_prev), the 256-sibling membership paths -> RFC-6962 audit paths (nav_inclusion / creating_nav_inclusion) with u64 positions pos_prev/pos_create, and nav is now the (size, mth) log value committed as nav_root = Hc("NfLog/Root", size || mth). - clause 1: nav_commitment opens nav_root; prefix(prev.nav, w.nav) is the RFC-6962 log-consistency relation; the predecessor membership check (i) is an RFC-6962 inclusion at pos_prev < size; genesis is the trivial empty-log consistency. - clause 10(c): consistency prefix(r_nav || w.nav) with size_r <= size and the sender's minimal-covering size rule; 10(d): inclusion at pos_create < size. Key/leaf bindings (1(iii)/10(d)/9) unchanged. MAX_NAV_DEPTH, the finality gate, the sender size rule, C_balance and the test vectors follow next. * spec: dimensioning, finality gate and sender rule for the accumulator log - 2.5: MAX_NAV_DEPTH is now H_MAX = 64, the Merkle-log height (log2 of the entry count); inclusion <= H_MAX hashes, consistency <= 2*H_MAX. The old '48 vs 2^48 / orthogonal to the 256-bit key' muddle is gone (a position- indexed log has no separate key length). - 3.9: define size_final = the log prefix whose inclusion blocks have >= 6 confirmations (height <= tip_height - 5, from the 5.6 confirmation count tip_height - height + 1 — 6 conf, not 7). A committed nav and a balance attestation MUST authenticate only positions < size_final, so an ordered log's reorg-order sensitivity stays a liveness cost, never a double-spend lever (a <=5-block reorg touches only positions >= size_final). - 2.3.3 step 2: the receiver MUST additionally require w.nav.size <= size_final. 2.3.2 step 5: the sender sets nav to the minimal covering log size <= size_final (all dependencies must be final, else it waits). - 2.6 cost note reworded to the log inclusion/consistency gadget; nav_commitment preimage is nav_root everywhere. * spec: C_balance on the log, residual-review note, and V.11 vectors - 5.7: the balance-attestation circuit C_balance takes the same gadget change — prefix(nav, nav_ceiling) becomes an RFC-6962 log-consistency proof (size <= size_ceiling), nav_root binds size||mth, and the verifier requires nav_ceiling.size <= size_final (the 3.9 finality gate). - 1.7.8: a normative residual-review note names the in-circuit RFC-6962 consistency verifier's arithmetization as the one element recommended for human cryptographic review before mainnet, with a differential-test obligation against an RFC-6962 reference at the 2^k boundaries. - V.4: the retired SMT empty root E256 is replaced by nflog_empty = Hc("NfLog/Empty", 0); no NfAcc/* constants remain. - new V.11: nullifier-accumulator-log conformance vectors (MTH, inclusion, consistency at the 2^k-1/2^k/2^k+1 boundaries) and a normative negative- control table (non-prefix, forged-leaf, position-swap, size-inflation, out-of-range, buried-fork-loser, non-final-position), all differential- tested against an RFC-6962 reference before pinning. * docs: register the accumulator-log redesign (D-05) honestly - paper-conformance-analysis D-05 status: DECIDED — the accumulator is now an RFC-6962 append-only Merkle log; prefix is a constant-size log-consistency proof (succinctness/soundness gap fixed), still a pure function of the on-chain nullifiers; registered as a CT-consistency (ordered-sequence) port, distinct from the paper's ToS set IsPrefix. - risks 'Accumulator history relation': the prefix-succinctness defect is fixed, with two honest caveats — the ordered log is more reorg-order- sensitive than the order-independent SMT (a liveness cost only, bounded by the size<=size_final finality gate, never a double-spend lever), and this is a CT-consistency port, not the paper's ToS IsPrefix. The hard 6-confirmation finality (D-16) and its rationale (the absent DistinctElement no-op, #105) are unchanged. * spec: wallet-verifiable key rotation via a sixth ProofData field npk_commit Closes the hosted-prover rotation-capture (Requirement 5): a node could previously fold a next_pubkey of its own into new_account_state_hash (Poseidon, which the thin wallet cannot recompute) and have the wallet blind-sign it, capturing the account's future spend authority. - new sixth ProofData field npk_commit = H("zkCoins/v1/NpkCommit" || next_pubkey || npk_rand) (SHA-256, wallet-native); serialize(ProofData) 160 -> 192 bytes; C application public inputs 28 -> 36 (npk_commit is a non-Poseidon 32-byte value, 8 u32 limbs). - clause 2 opens npk_commit against w.next_pubkey/w.npk_rand; the custody S2C signature commits H(ProofData) over it, so a substituted rotation key is caught. - 7.5 awaiting_signature surfaces npk_commit; the wallet MUST recompute it from ITS OWN next_pubkey + fresh npk_rand and refuse to sign on a mismatch (fail-closed) -- this is what makes the rotation verifiable. TransitionRequest and the kernel AwaitingSignature carry npk_rand / npk_commit; npk_rand is fresh per attempt, never reused (a reuse would re-link two transitions). - 1.4/2.1/2.5/glossary/V.4 updated for the six-field 192-byte ProofData. The V.8 signing fixture is recomputed at 192 bytes in the next commit. * spec: recompute the V.8 signing fixture at 192-byte ProofData The sixth ProofData field npk_commit grows serialize(ProofData) to 192 bytes, changing m_SC and every dependent signature/aggregate value. The V.8 synthetic fixture is recomputed accordingly: each ProofData gains a sixth npk_commit field, and m_SC, t, R, e, s, z, a1, a2, s_agg follow (the pre-tweak nonces R', keys and the five original fields are unchanged). Values computed twice independently (two model families), byte-identical, all BIP-340 / CommVerify / AggregateVerify self-checks pass. V.1 pins the SHA-256-computable npk_rand@0 / npk_commit@0 for the V.4 H(ProofData@0) formula. * docs: protocol.md accumulator row follows the Merkle-log redesign (fix broken anchor) * spec: propagate the append-only-log accumulator to the remaining SMT mirror sections §1.6 tree table, §3.7 (opening, Path-B, Storage), §2.5 dimensioning, the glossary, §7.1 nav_opening, §7.5/§7.8 Path-B REST+proto, §2.3.2/§2.3.3 nav prose and V.4 nav_empty now describe the RFC-6962 append-only Merkle log instead of the retired 256-bit nullifier SMT. u64 position/size absorbed as 8-byte big-endian byte strings per §1.7.2. The per-account coin-history SMT is unchanged. * docs: propagate the two crypto-blocker fixes to the register, mandate and protocol Register the append-only-log accumulator (D-05, log-consistency prefix) and the sixth wallet-verifiable ProofData field npk_commit (192-byte serialize, new deviation D-21) across the paper-conformance analysis and remediation notes, the implementation mandate and protocol.md. Pinned-snapshot columns kept intact; only the living/normative statements were updated. * spec: propagate the six-field/192-byte ProofData to the remaining mirror sections npk_commit is the sixth ProofData field: §2.5 public-input count (20 + 8 + 8 = 36, read map 28+8), §1.4 field enumeration and serialize, §1.7.2 concatenation note, §2.6 in-circuit SHA-256 inventory point (e), §7.8 kernel-proto npk_rand, V.9 N-01 fuzz range (192), the Requirement-5 traceability row and the send/mint flow prose now describe six fields / 192 bytes. Add the V.11 pinned sample-leaf sequence so the log vectors are reproducible, and retire the E₂₅₆ token from the V-determinism summary in favour of E'₂₅₆ + nflog_empty. * docs: close four residual go-live gaps from the acceptance sweep - V.9: add negative controls N-16/N-17 so Requirement 5 is testable — the wallet MUST refuse to sign when the surfaced npk_commit (or proof_data_hash) does not match its own next_pubkey/npk_rand recomputation (§7.5 fail-closed). - V.7 parity matrix: add the V.11 append-only-log vectors so the log conformance is a byte-equal node↔SDK cross-check, and generate them in the runbook (implementation-mandate step 2 now lists V.11). - V.11 intro: state that the RFC-6962 construction structure — not the still <REGEN> byte vectors — was differential-tested, avoiding an overclaim. - paper-conformance-analysis: the living maintenance note now names the pinned external-audit action items (§§8–10) as superseded and not a v1 release gate. * spec: pin the per-network scan origin and tie up three log/balance residuals - §3.6/§1.7.9: pin a consensus-critical activation_height per network. Because the accumulator log is position-bound (Hc("NfLog/Leaf", p ‖ Pk ‖ R)), the scan-origin height is a consensus parameter — nodes starting at different heights assign different positions and every inclusion/prefix/nav diverges. The origin is now pinned (mainnet at deployment, runbook step 9), foreclosing a pre-deployment-inscription split. - §5.7 statement 6: bind the disclosed nav_ceiling to a committed log root (Hc("NfLog/Root", size_ceiling ‖ mth_ceiling)) so the balance-attestation prefix check runs between two committed roots, not free witnesses. - Glossary ProofData: six fields / 192-byte serialize; §1.7.8 residual-review note now also names the inclusion-PATH gadget. * spec: finish the append-only-log/6-field propagation to the remaining mirrors §2.2 on-chain anchoring, §1.4/§2.1/§3.6 accumulator API, the §3.7 insertion paragraph (the log is order-SENSITIVE — positions are order-bound, unlike the retired set-keyed SMT), §1.7.6 coin-history/empty-log notes, the V.4/V.7/V.11 harness rows, the source-paper relationship (D-05 = the CT-consistency port), and §1.7.8 (differential-test discharges the review, not a v1 gate). Also make the ViewGrant Bech32m payload signature-recomputable (carry the nonce, unify the asset_ids discriminator) and pin concrete per-network activation_height values plus /v1/info + kernel Info exposure. * docs: correct the living gate/register/mandate bodies to the log + 6-field model The main prose (not just parenthetical notes) of the remediation gate doc, the implementation-mandate D12 decision and the D-05 register row now describe the 192-byte six-field ProofData and the RFC-6962 append-only-log accumulator, and D-05 gets an explicit release gate (the V.11 differential-test). Runbook step 9 also checks the pinned activation_height. * spec: set the conditional NAV to the shared size_final prefix (privacy + soundness) The conditional NAV is now the SHARED >=6-confirmation-final prefix size_final, not a per-account minimal-covering size. size_final is identical for every prover at a given tip, so the fee-coin nav_opening reveals only a shared global ordinal, not the account's activity — closing the receive-recency linkage the minimal- covering size leaked. Building against a still-pending dependency is allowed only as proving-pipelining: the nullifier MUST NOT be broadcast until every covered position is final, otherwise a tolerated reorg could strand Pki as the first- occurrence winner with a non-canonical nav and brick the account. Adds the §3.7 canonical-value predicate (mth = MTH(D[0:size]) on the verifier's scan; creditable iff size <= size_final), reframes NL-7 as deferred-not-rejected, defines serialize(BalanceAttestation) in §7.1, binds nav_ceiling to a committed log root and defaults the REST/kernel nav_ceiling to size_final, and aligns the privacy narrative (spec + risks D-17/D-18/storage) with the size_final model. * spec: finish nav=size_final across mint/receive/clause-10c + close attestation gaps Propagate the shared-size_final conditional NAV to the mint (§2.3.1), receive (§2.3.3) and cross-account (§2.1 clause 10c) flows and the §5.6 privacy note — the size_final default reached only §2.3.2 (send) before. Define size_final precisely in §3.9 (size at height <= tip-5, 0 when tip<5) and reconcile it with proving-pipelining. Pin activation_height to a unique value (the genesis- inscription block height) and define the network parameter set as a published artefact. Make size_ceiling a C_balance public input (so serialize(Balance- Attestation) matches the PI list), make /health/ready return the byte-unique nav_root, add the nav_size proving-pipeline request field, and enumerate the V.11 inclusion vectors. * docs: propagate size_final + append-only-log framing to the gate/risk/mandate docs remediation §3.2 takes the conditional NAV at size_final (not the live tip) and names the CT-log accumulator as deviation D-05 (not paper-compatible); the risks.md storage verdict and the mandate D12 decision follow the size_final / append-only-log model; the A-to-Z reorg assertion names the concrete (size, mth) / nav_root value. * spec: close the §7 wire-consistency and register-framing residuals Every §7 accumulator surface now names the byte-unique nav_root (not the bare mth); the attest-balance body carries size_ceiling for a non-default ceiling and its job has no awaiting_signature phase; network-params.json gets a byte-exact canonical encoding and activation_height is the observed genesis-inscription height (no chicken-egg); §3.9 separates authenticating a position from crediting it; the source-paper relationship lists D-05 among the load-bearing deviations; the §6.7 Requirement-5 row names npk_commit; and the Passkey seed is a named HKDF-SHA-256 construction. * docs: size_final-by-default framing and D-05 attribution in the register/risks remediation states the conditional NAV is size_final by default with proving- pipelining as the opt-in, and ties D-05's V.11 differential-test to the runbook conformance step; the risks verdict row credits the CT-log consistency port to D-05 and keeps D-16 as the separate bounded-finality boundary. * spec: final wire/harness/§3.9 consistency fixes Every §7 accumulator surface reports the field root = nav_root = Hc(NfLog/Root, size ‖ mth) with a consistent field name; §3.9 scopes the < size_final rule to creditable NAVs with the pipelined-nav exception spelled out; V.11 disambiguates the inclusion split point s from the size exponent and states NL-7 as a credit rejection (deferred, consistent with the negative-control header); the testnet activation_height is the observed Signet genesis-inscription height; and the receive-path Path-B is an RFC-6962 log-inclusion proof, not an SMT path. * docs: D-05 living column, honest deviation claims, and a machine-evaluable runbook The D-05 register's living column describes the append-only RFC-6962 log and frames its differential-test as a release gate (not a past-tense claim); intro.md qualifies 'implements Shielded CSV' with the registered v1 deviations; and the runbook step-8 gate is an explicit checklist while step 9 spells out the genesis inscription, activation_height read, and network-params.json assembly. * spec: remove proving-pipelining and close the codex-review findings A soundness review showed the pipelining 'do not broadcast until final' rule is not adversarially enforceable — the wallet cannot see the nav size behind the hiding nav_commitment, and the node holds the signed SpendRecord and can publish a non-final nav early, bricking the account under a tolerated reorg. v1 therefore has NO proving-pipelining: nav is always size_final; a not-yet-final dependency means the wallet waits. The §7.5 wallet recompute now also binds nav_commitment to size_final (fail-closed). Also: mint takes nav = size_final in every case; /v1/chain/accumulator reports (size, nav_root); the V.11 inclusion vectors are a closed (p,n) list and NL-1/NL-2 are complete; and the Passkey seed HKDF is named. * docs: drop pipelining from the register/runbook/risks and fix review findings Remediation, the mandate D12 decision and the risks D-18 note follow the no- pipelining nav = size_final model; remediation §1.3 describes Path-A anchoring instead of non-existent CoinProof location fields; the runbook step-5 gate lists the encoding/signature negative controls (moving the reorg cases N-09/N-10 to step 6 with their own convergence/fail-stop predicates) and the V.11 + D-05 differential-test; and intro.md drops the 'true privacy' marketing overclaim. * spec+risks: address the second codex review round Remove the §7.5 wallet check (c): a thin wallet runs no Poseidon and no scan loop, so recomputing nav_commitment was unimplementable — the pipelining removal already forecloses the honest-node reorg brick, and the malicious-own-node case is the accepted D-17 thin-client boundary, now registered honestly in risks (a compromised own node can freeze an account, not only redirect; mitigated by self-hosting). Also: /v1/chain/accumulator prose matches the JSON field name; the wallet (not the node) waits for a pending dependency and a dependency_not_final machine_code is added; npk_rand is a fresh uniform-256-bit CSPRNG draw (not an HKDF value); the V.4 mint vector is marked the genesis case; and the stale not-yet-final-nav wording is corrected. * docs: reconcile the own-node-freeze note with the §6.6 guarantee The §6.6 freeze-resistance is scoped to third-party nodes/publishers; a compromised own node is the separate D-17 thin-client boundary (it can strand your own key), now registered in the D-17 row and the risks verdict table and tightened to a never-finalizing (fork-loser) or unsatisfiable ProofData. The stale '§2.3.2 nav the wallet verifies' line is corrected to node-enforced, not wallet-verified, after the §7.5(c) removal. * docs: the freeze/redirect actor is the selected prover (own or vetted foreign node) §6.6 already grants a self-selected foreign node used as prover the same lie/omit capability as the own node (the wallet checks only npk_commit + H(ProofData), not satisfiability), so the freeze/redirect boundary is the transition's selected prover — own or a vetted foreign node — not the own node alone. §6.6, the risks verdict row and freeze note, and the D-17 register now say so; the mitigation is self-hosting or vetting the prover. * spec: qualify the §6.6 freeze-resistance closing statement with the prover exception The paragraph's closing 'no party ... holds the authority to withhold a holder's own coins' now excepts the holder's own selected prover (the D-17 thin-client boundary the same paragraph already establishes).
Summary
Reverts the normative on-chain layer of
docs/specification.mdfrom the batchedBatchInscription/BatchBundle/AggregateBatchProofmodel back to the peer-reviewed Shielded CSV paper model, implementing the accepted decision record (research#16, accepted inf392fa0) and the finding dispositions of the paper-conformance remediation (#96, which authorizes exactly this versioned specification rewrite).The on-chain object is now a per-transition account-state nullifier
(Pkᵢ, Rᵢ)— theAggregateStateNullifierV3— published on Bitcoin, NISSHAC half-aggregated across a publish (~64 B/tx asymptotic). Every node rebuilds the global nullifier accumulator by first-occurrence keyed byPkᵢfrom Bitcoin alone (no off-chain consensus object). Reorg safety is the paper's conditional NAV (execute/no-op under arbitrary-depth rollback/replay). The per-account recursive proofCand all surviving hardening are preserved.Relationship to #96
This PR is the execution of #96's accepted plan, not a competing revert. #96 is the accepted architecture direction + finding dispositions (F-01…F-08) + the §9 specification edit map + Gates A–C; it explicitly states "the normative source remains
specification.mduntil one versioned specification PR incorporates this plan." This is that PR. It follows #96's §9 edit-map area-for-area and adopts its vocabulary (AggregateStateNullifierV3,TransitionEssenceV3, NISSHAC).risks.md/assurance.mdare owned by #96 and are deliberately not touched here (kept identical todevelop) to avoid conflicts. Rebase this PR to land after #96. Still needed on those pages (for #96 or a follow-up): remove/replace the Publisher concentration (serial accumulator), conflicting-SpendRecordgriefing, andBatchBundleretention risk sections (their cause is removed by this revert); fix the onerisks.mdsentence that still says "Bitcoin holds only the opaqueBatchInscription"; and updateassurance.md's actor-behaviour-risk clause.Per-section change map (
specification.md)NavCommit,NavRand,StateUpdate;HalfAggnow on-chain(Pkᵢ, Rᵢ)/AggregateStateNullifierV3;SpendRecordredefined (96 B authorization);ProofData5th fieldnav_commitment;ProofData= v1TransitionEssenceV3; removedBatchInscription/BatchBundle/next_pubkey_commitrowsCoinProofcarriescreating_nullifier+nav_opening(wasanchor_hint/anchors_opening)Pkᵢ, first-occurrence, Bitcoin-only; anchors-MMR row removednf → Pkᵢ, leafRᵢC_batch/AggregateBatchProof/H_agg; on-chain binding isH(ProofData), not proof bytesm_statesignature + in-circuit sign-to-contract opening; conditional-NAV carry-forward (clause 1); whole-lineage receive-anchoring (clause 10); genesis publishesPk₀C_batch/AggregateBatchProofremoved; on-chain anchoring + conditional NAV describedC_batchdimensioning removed (MAX_NAV_DEPTHadded); NISSHAC-cited crypto costsPkᵢ-keyed accumulator, fees, deterministic any-depth reorg + conditional NAV, states (nopending-DA, nomint-verified)BatchBundleconsensus-DA removed;CoinProofcustody-DA keptAlso updated (derived pages #96 does not own):
protocol.md,comparisons.md,intro.md,requirements.md,implementation-mandate.md.F-04 verification outcome — anchor every state-advancing transition
F-04 (a mint accepted without a Bitcoin state nullifier) is settled toward anchoring: the unanchored
mint-verifiedpath is removed. Adversarial two-lens verification confirmed the batched-modelmint-verifiedwas "out of frame" of the safety proofs, i.e. not proven safe. In thePkᵢ-keyed accumulator this generalizes: an unanchored receive would also let a coin be double-spent across an account fork (the two branches' later spends consume different rotated keys, so only anchoring the fork-point transition'sPkᵢby first-occurrence catches it). Therefore every state-advancing transition — send, receive, and mint, including the genesisInitialProofunderPk₀— publishes(Pkᵢ, Rᵢ)and is arbitrated by first-occurrence. Genesis publishesPk₀itself (thenk_commitbinding alone does not separate two genesis forks under onePk₀). Consequence, stated honestly: issuance frequency/timing become chain-visible (amounts/assets/parties/graph stay hidden).docs#95 acceptance-criteria checklist
Pkᵢ(§3.6, §3.7); two honest nodes at one tip cannot diverge.BatchBundleremoved from admission;CoinProof-bundle DA is custody/bearer-recovery only (§4.6).next_pubkey_commithiding (spec: bind next_pubkey into the transition signature message (custody) #85/spec: hide the next_pubkey rotation edge behind a per-transition commitment #86) is preserved in substance — the rotated key is bound+hidden via the off-chainnew_account_state_hashfolded intoH(ProofData)— see judgment calls.)risks.md/protocol.mdupdated —protocol.mdupdated here;risks.mddeferred to docs: define a coherent paper-conformance remediation path #96 (its owner) — see Relationship to docs: define a coherent paper-conformance remediation path #96.npm run build(Node 20) passes withonBrokenLinks/onBrokenAnchors=throw; full-text sweep confirms no staleBatchInscription/BatchBundle/AggregateBatchProof/C_batch/prev_root/mint-verifiedinspecification.md(only intentional design-history asides remain, listed below).Issue impact
Closes #56— the single sequential writer is removed; contention-free self-publish restored.Closes #62— nullifiers on-chain + first-occurrence make admission a pure function of Bitcoin; noBatchBundleDA gates admission, so two honest nodes at one tip cannot diverge.Self-check (specification.md)
The self-check sweep over
specification.mdreturns only intentional design-history asides: §1.4ProofDatanote ("without a redundantnext_pubkey_commit"), §2.1 clause 2 ("the earliernext_pubkey_commithiding commitment, now carried structurally byash"), and §3.10 ("the batched design's dependence on …BatchBundle… is gone"). No broken internal or cross-page anchors (build is green).Judgment calls
next_pubkey_commit/TransitionEssenceV3— docs: define a coherent paper-conformance remediation path #96'sTransitionEssenceV3lists a separate "next-key hiding commitment". This PR realizes it throughnew_account_state_hash(which commits the rotatedcurrent_pubkeyinside an off-chain hash folded intoH(ProofData)), not as a separate published field — keeping spec: hide the next_pubkey rotation edge behind a per-transition commitment #86's bind+hide substance without a redundant field. Flagged for reviewer.Pkᵢ-keyed fork-soundness argument above; receiving now costs a ~64-byte on-chain nullifier (accepted trade-off).Links
research#16 · accepted decision
f392fa0· paper-conformance review research#22 · the authorizing plan #96 · docs#40 (the design reverted) · docs#21 (the pre-#40 paper-faithful state) · docs#95 (scope) · zk-coins/landing-page#32 (derived-copy follow-up).