Skip to content

spec: close the remaining open design decisions - #79

Merged
TaprootFreak merged 2 commits into
developfrom
fix/close-open-design-decisions
Jul 10, 2026
Merged

spec: close the remaining open design decisions#79
TaprootFreak merged 2 commits into
developfrom
fix/close-open-design-decisions

Conversation

@TaprootFreak

Copy link
Copy Markdown
Contributor

Why

The review of the batched spec identified six design decisions that were still open after the key-custody / receive-in-recursion / transport work landed (#74). This PR closes them. The biggest is a real soundness gap; the rest range from a buildability bound to small consistency fixes.

What

Soundness — bind the nullifier key to the account identity (double-spend)

The nullifier key nk was an unconstrained circuit witness and was not committed anywhere in AccountState. Because the address was H(Pk₀) — independent of nk — the same address could back two accounts with different nk, letting the holder spend one received coin under two distinct nullifiers that the accumulator does not collide. This closes it:

  • nk_commit = Hc("NkCommit", nk) is a committed AccountState field (§1.5, §1.7.4, part of the ash preimage), and the address now derives from it: address = H(Pk₀ ‖ nk_commit) (§1.4). Different nk ⇒ different address, so a received coin has exactly one valid nullifier and account ↔ address is genuinely one-to-one.
  • §2.1: clause 1 (InitialProof) fixes nk_commit and the address at genesis; clause 4 checks the witnessed nk against prev.nk_commit before deriving any nullifier; clause 7 carries nk_commit forward unchanged (like owner). This closes both the same-prev-state fork and the genesis-equivocation variant.
  • nk_commit is carried through Invoice/addressing (§4.3), the ownership proof (§5.1), the mint owner-binding (§2.3.1/§6.5), recovery (§4.5 — reproducible from the seed), the glossary, and test vectors V.2/V.3/V.4 (affected pinned values marked <REGEN>).
  • §2.6: the genesis owner-binding is a fourth in-circuit SHA-256 site.
  • §6.5/§1.4: asset_id binds Pk₀ alone, so one Pk₀ may issue under several accounts (one-to-many) — benign for v1 (minting still needs sk₀), noted for a future auditable-supply schema.

Bounded balances (buildability)

  • §2.5 pins MAX_ACCOUNT_ASSETS = 32; the balances slots follow the same inactive-sentinel padding as the input/output slots. §1.7.4 notes the in-circuit padded absorption equals the out-of-circuit variable-length ash bit-for-bit (inactive slots contribute nothing), so the fixed-shape circuit can compute ash deterministically.

Transport & access

  • bundle_locator → blob_id: §7.5 /v1/chain/inscriptions returns blob_id; a scanner fetches a BatchBundle by blob_id (resolved from the inscription) and binds it by recomputing bundle_locator — consistent with the plaintext-blob model of §4.2.1/§7.4.
  • Pull session: §5.1 gains a normative pull-session — POST /v1/pull issues a short-lived bearer token bound to chan_bind/subject/resolved scope with its own expiry; GET /v1/proof and the kernel GetCoinProof reference it (resolves the previously dangling term).
  • Requirement 9(b) ↔ §5.7: requirements 9(b) reconciled with the balance attestation (the single on-chain anchor is a documented v1 limit); §5.7's leak-free-anchor path raised to a normative future-version clause.
  • §2.5 recursion-tree wording aligned with the member_root shape.

Review

Built through a build+review loop: three review passes across two lenses (conformance/consistency + logic/soundness). Pass 1 caught that the initial nk_commit field bound nk only to a lineage, not the coin-owning identity — the fix (address = H(Pk₀ ‖ nk_commit)) closes the genesis-equivocation variant and was verified in passes 2–3. The soundness chain (fork + genesis double-spend fully closed, account ↔ address one-to-one, no guarantee weakened, nk stays a secret witness) was confirmed clean.

Resolves the design decisions that the review of the batched spec left
open. The bulk of the key-custody, receive-in-recursion and transport
work already landed; this closes what remained.

Soundness — bind the nullifier key to the account identity:
- add nk_commit = Hc("NkCommit", nk) as a committed AccountState field
  (§1.5, §1.7.4, part of the ash preimage) and derive the address from
  it: address = H(Pk0 ‖ nk_commit) (§1.4). Two accounts under one
  address are no longer possible, so a received coin has exactly one
  valid nullifier and account<->address is genuinely one-to-one.
- §2.1: clause 1 (InitialProof) fixes nk_commit and the address at
  genesis; clause 4 checks the witnessed nk against prev nk_commit
  before deriving any nullifier; clause 7 carries nk_commit forward
  unchanged (like owner). This closes both the same-prev-state fork and
  the genesis-equivocation double-spend the accumulator alone did not
  catch.
- carry nk_commit through Invoice/addressing (§4.3), the ownership
  proof (§5.1), the mint owner-binding (§2.3.1/§6.5), recovery (§4.5,
  reproducible from the seed), the glossary and test vectors V.2/V.3/V.4
  (affected pinned values marked <REGEN>).
- §2.6: the genesis owner-binding is a fourth in-circuit SHA-256 site.
- §6.5/§1.4: asset_id binds Pk0 alone, so one Pk0 may issue under
  several accounts (one-to-many) — benign for v1, noted for a future
  auditable-supply schema.

Bounded balances (buildability):
- §2.5 pins MAX_ACCOUNT_ASSETS = 32 and the balances slots follow the
  inactive-sentinel padding of the input/output slots; §1.7.4 notes the
  in-circuit padded absorption equals the out-of-circuit variable-length
  ash bit-for-bit (inactive slots contribute nothing).

Transport and access fixes:
- §7.5/§3.6: /v1/chain/inscriptions returns blob_id; fetch a BatchBundle
  by blob_id (resolved from the inscription), then bind by recomputing
  bundle_locator — consistent with the plaintext-blob model of §4.2.1.
- §5.1: a normative pull-session — POST /v1/pull issues a short-lived
  bearer token bound to chan_bind/subject/scope with its own expiry;
  GET /v1/proof and the kernel GetCoinProof reference it.
- requirements 9(b) reconciled with the §5.7 balance attestation (the
  single anchor is a documented v1 limit); §5.7's leak-free-anchor path
  raised to a normative future-version clause.
- §2.5 recursion-tree wording aligned with the member_root shape.
…odel

The glossary BatchBundle entry, the §1.1 domain-tag list, and the V-section
byte layout still labelled bundle_locator a 'content address'; a BatchBundle
is fetched by blob_id and bundle_locator is the on-chain binding value (§3.6).
@TaprootFreak

Copy link
Copy Markdown
Contributor Author

Ready for review. This went through 5 review passes (build+review loop, two lenses each round: conformance/consistency + logic/soundness) until both lenses came back clean.

The soundness lens earned its keep in pass 1: the initial nk_commit field bound the nullifier key only to a lineage, not to the coin-owning identity — an attacker could genesis two accounts under one H(Pk₀) address with different nk and double-spend a received coin under two nullifiers the accumulator does not collide. The fix (address = H(Pk₀ ‖ nk_commit)) closes that genesis-equivocation variant and was re-verified across passes 2–5. Later passes caught follow-on consistency items propagated from the address change: the §2.6 in-circuit SHA-256 enumeration (now four sites), the §6.5 asset_id↔account cardinality (now one-to-many), the AccountState glossary entry, and the bundle_locatorblob_id content-address wording.

Out of scope, noted for a possible follow-up: three pre-existing (base develop) wordings around serialize(BatchBundle) as a hash preimage (§1.7.9) and "addressed by bundle_locator" (§1.5/§3.1) that predate this PR and were not introduced here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant