Back to Blog

Formal Verification of Zcash Ironwood Completed

Ironwood's supply soundness now rests on machine-checked proofs, not just carefully reviewed and audited code.
A dependency map of the Lean proofs behind Ironwood's formal verification, from the algebraic prover model through straight-line extraction, IPA opening, and circuit satisfaction to the final verifier soundness theorem, with the cryptographic assumptions outside Lean along the bottom.
Ironwood's balance integrity proofs contain over 2,700 theorems.

Ironwood is a new shielded pool activating in Zcash today as part of NU6.3. In our last post, we showed how formal verification can eliminate the possibility of undetectable counterfeiting bugs. Bugs of this kind only exist in the protocol's specification, which is ideally suited to formal analysis.

Today we are publishing a machine-checked proof, written in Lean, which shows that undetectable counterfeiting bugs do not exist in Ironwood. The proof is publicly available in the ironwood repository and comprises more than 2,700 theorems. It took three teams of researchers and cryptographers well over a month to complete.

All of this work culminates in a single security property called balance integrity.1 The vast majority of our effort was spent proving knowledge soundness for Ironwood's zk-SNARK, which is the primary focus of this post. But balance integrity depends on several other components of the protocol as well, and our proof addresses each of them, down to named assumptions.

Knowledge Soundness

zk-SNARKs are used in Zcash to let users demonstrate that they possess hidden transaction data that satisfies certain rules. That transaction information is encoded into a witness for some relation representing those rules, and the SNARK itself is a short argument that the prover knows such a witness. The verifier ingests the proof string, performs a computation, and then accepts or rejects.

Ordinary soundness is insufficient for shielded protocols; because a hidden transaction can contain anything, essentially every instance admits a witness.2 Knowledge soundness is stronger, requiring the prover to actually know a satisfying witness. We show this using a notion in the style of Bellare–Goldreich: an extractor with black-box access to the prover must be able to produce a witness whenever the prover convinces the verifier.3

Schnorr proofs don't reveal to the verifier; that's one of the points of the scheme. But suppose the prover can be rewound to the moment just after it sent , and challenged again with a different . If it answers with a valid for the same , the two verification equations subtract to , and so , exactly the witness the extractor must produce.

This is special soundness: a witness can be computed from enough accepting transcripts at distinct challenges, and knowledge soundness follows. Public-coin protocols, in which the verifier only sends random challenges, are often proven knowledge sound this way. The technique generalizes to multi-round protocols like ours, where the accepting transcripts form a tree with a fork at every challenge round.4

However, protocols built using the inner product argument, including ours, typically suffer large extractor runtimes from traditional special soundness analysis, since filling the tree takes a number of accepting runs exponential in the round count. This doesn't lead to a concrete break, and many protocols ignore it in practice, but an extractor this expensive cannot support a concrete security claim: at our depth, extraction can cost more than attacking the discrete logarithm outright.5

Algebraic Provers

Our analysis instead models the prover as algebraic6, meaning that for every group element it emits, it must also expose a representation over the generators it has seen.

Schnorr provers in this model now expose with when they emit . The extractor substitutes that representation into the verification equation , rearranges to , and computes . No rewinding happened: the witness came out of a single accepting run. Extraction fails only at , one challenge out of , and was already fixed when arrived. Failures of this kind are charged to the knowledge error, the threshold that a prover's success probability must exceed before extraction is guaranteed.

That is the shape of our entire analysis. In Ironwood, the prover's representations are written over the URS7 (in our Lean code, AlgebraicProofString), whose generators are themselves derived by hash-to-curve; the URS is a trustless setup that anyone can recompute.8 Because every commitment in a proof arrives with a representation, the extractor always holds a candidate witness, and the work is in proving that a wrong candidate cannot survive verification. Each way it could is either accounted for exactly, like above, or converted into a computed break of a hardness assumption.

Extraction here means recovering openings of the commitments in a proof, and nothing about a commitment alone makes its opening unique. The security claim is that no efficient adversary can find a second one. Any ambiguity in extraction directly yields a nontrivial discrete-log relation among the URS generators:9

-- A concrete break: a nontrivial relation among the generators.
structure NontrivialDLRelation (urs : URS G) where
  coeffs : Fin (2 ^ urs.k) → F
  nontrivial : coeffs ≠ 0
  relation : commitGen urs.g coeffs = 0

-- Two distinct openings of one commitment yield such a relation.
def NontrivialDLRelation.ofCollision (urs : URS G) {a a' : Fin (2 ^ urs.k) → F}
    (hcol : commit urs a = commit urs a') (hne : a ≠ a') :
    NontrivialDLRelation urs

Relations of this kind always exist. The hardness assumption forbids computing one, which is why the break is data rather than a bare existence claim. Because an algebraic prover must write each point as a combination of the generators, a binding collision anywhere in the construction reveals such a relation, and the reduction turns it into an ordinary discrete logarithm.

Fiat-Shamir Transformation

As with Schnorr signatures, our protocol is made non-interactive with the Fiat-Shamir transformation in the random oracle model.10 In the deployed protocol, each challenge is "squeezed" out of a hash of the transcript so far, representing the oracle's response to the current prefix of the transcript.11 This allows us to analyze the non-interactive verifier through the lens of its underlying interactive protocol. In our Lean code, the squeeze function is the oracle itself:

-- Use the random oracle as the Fiat-Shamir squeeze function.
def roChallenges O init proof :=
  deriveChallenges (ofOracle O) init proof

Non-interactivity lets a cheating prover grind for favorable verifier challenges, varying its own transcript and querying the oracle repeatedly. Our analysis measures this power with a parameter , the number of queries the adversary may make to this oracle.12 Each query at a new prefix hands the prover another fresh draw, but by the time a draw arrives, its prefix is already fixed, and so is everything the extractor will read from that prefix.

Inner Product Arguments (Bulletproofs)

Ironwood's polynomial commitment scheme is built from an inner product argument (IPA). In each round of the IPA, the prover sends two round commitments, and the verifier's challenge folds the two halves of a witness vector; where Schnorr has a single challenge round, the deployed IPA has eleven, taking the vector down to one scalar.

Because the prover is algebraic, each of those commitments arrives with a representation, so the extractor holds a candidate opening at every stage of the fold. It remains to prove that a wrong candidate cannot survive the rounds. We follow the discrepancy between the prover's claim and its own representations as the rounds fold:13

-- Advance the claimed-opening discrepancy through one IPA round.
def ipaDiscrepancyStep (current : Fp) (round : Fp × Fp) (challenge : Fp) : Fp :=
  current + challenge⁻¹ * round.1 + challenge * round.2

-- The quadratic whose roots record a nonzero discrepancy becoming zero.
def ipaDiscrepancyPolynomial (current : Fp) (round : Fp × Fp) : Polynomial Fp :=
  C round.1 + C current * X + C round.2 * X ^ 2

-- A nonzero-to-zero transition means the challenge hit one of its roots.
theorem ipaDiscrepancyStep_mem_badSet (round) (h : current ≠ 0)
    (hstep : ipaDiscrepancyStep current round challenge = 0) :
    challenge ∈ szBadSet (ipaDiscrepancyPolynomial current round)

A wrong candidate can survive verification in only two ways. If the discrepancy ends nonzero, the extractor holds two different openings of one commitment, the computed discrete-log relation from before. If it returns to zero, some round's challenge landed on a root of its quadratic, a polynomial fixed before the challenge was squeezed: two roots per round, eleven rounds, and grinding attempts add up to , the first term of the bound we display below. If neither happens, the candidate is the witness.

Every other statistical term arises the same way: a set of roots pinned before its challenge was squeezed, counted once per oracle query.14 The multiopen argument and the quotient check contribute their own families, and one more challenge has a single excluded value (, where is the challenge that binds the claimed value into the IPA opening). None of them ever needs the prover to run twice.

The Concrete Reduction

The random oracle ensures that each of the polynomials above is fixed before its challenge is drawn, the algebraic model turns any ambiguity in extraction into a computed discrete-log relation, and the DLOG assumption forbids computing one. Because everything between the assumptions is machine-checked, the reductions compose into a single chain from a convincing prover down to a discrete logarithm.

Following that chain end to end gives the security claim: the probability that the verifier accepts while the extractor comes up empty is at most the advantage of a derived DLOG adversary, plus error terms. The traditional move at this point is to argue that every loss is polynomially bounded and leave it at that. We instead prove a concrete bound for the deployed protocol, making every loss explicit.

For the curves and fields Ironwood deploys, the labeled terms are negligible at any feasible query budget: even oracle queries at leave their sum near .15 The burden falls on 's advantage alone. A prover whose success against the extractor exceeds those terms hands over, as computed data, an algorithm for DLOG.16 The best known attack on DLOG over Vesta is Pollard's rho, at roughly group operations. Ironwood's zk-SNARK is therefore knowledge sound.17

The Verifier Fingerprint

The zk-SNARK verifier written in Rust and used by Ironwood can be thought of as a function that takes a proof string and public instance as inputs and returns a decision bit (accept or reject) as output. We can prove that some formalized protocol has knowledge soundness, but it would mean nothing if we did not show that it is faithful to the real verifier.

We are fortunate that the real verifier is more structured than a bare decision bit. In fact, it returns the coefficients of a linear combination of group elements (a multi-scalar multiplication, or MSM) derived from the proof string, the public instance, the verifying key, and the URS. The consensus rules then evaluate this combination and accept the proof only if the result is the group identity.18 Every coefficient is determined by the transcript, so each proof has exactly one canonical combination, computable by anyone from public data.

There is thus a single symbolic combination, with the proof string and instance left as variables, that describes how those coefficients are computed. We call this symbolic combination the verifier's fingerprint, and it is where we draw the boundary between mathematics and software:

  • Above the boundary, our Lean proofs define the verifier to be the fingerprint, and the entire knowledge soundness analysis rests on exactly that object. We can think of the real verifier as any black box that reproduces its output.
  • Beneath the boundary, the Rust code that assembles and evaluates the combination is ordinary implementation. Errors in that implementation can only ever produce detectable counterfeiting because blocks commit to the full contents of every transaction, including its proofs, so history can always be replayed through corrected software.

In our Lean code, the fingerprint is a function called assemble. (The snippet below uses assemble?, its partial form, which reports assembly failures as none; assemble is the total wrapper over it.)

def DeployedAccepts urs hk vk instanceCommitment ps ch : Prop :=
  -- Assemble the verifier's final MSM.
  match assemble? vk instanceCommitment ps ch with
  -- Accept only if the MSM evaluates to the group identity;
  -- `hk` casts it to the URS's size for evaluation.
  | some m => (hk ▸ m).eval urs = 0
  -- Any assembly or well-formedness failure rejects.
  | none => False

By isolating the verifier behind the fingerprint, we avoid reasoning about the Rust code more specifically than our claimed security property requires.

Checking the fingerprint against the implementation is then a matter of running both sides on the same inputs: we run the verifier in halo2 against an instrumented backend that records the challenges the verifier draws and the coefficients of the final combination instead of evaluating it.

Lean proves that assemble reproduces those recorded runs exactly:19

theorem fingerprint_matches :
    -- Conclusion: Lean's assembled MSM matches the captured Rust MSM.
    MsmMatch (assemble vk derivedInstanceCommitment ps ch) capturedMsm := by
  -- Proof body: compute and check the concrete equality.
  native_decide

The captured runs are validation, not the guarantee itself. Every accepted proof falls into one of two cases, because each one determines a single canonical combination, recomputable from public data. Either that combination evaluates to the group identity, in which case our knowledge soundness analysis applies; or it does not, making the verifier's deviation from the fingerprint public. The real verifier can escape our knowledge soundness guarantee only in ways that are detectable in practice.

Soundness of the Circuit

The fingerprint captures what the deployed SNARK is by describing its verifier algebraically. We showed the formalized verifier is knowledge sound with respect to a particular low-level witness, arriving at the bound we displayed earlier. That witness is a vector of field elements satisfying the equations that the verifier checks, and nothing in that description mentions a note commitment or a nullifier. A high-level witness must be extracted from those field elements for the proof to mean anything to our balance integrity analysis.

We do this with a transcription of the circuit into Lean (built on Clean). The trust boundary remains the fingerprint: the circuit is an algebraic object encoded in the verifying key (one of the fingerprint's inputs), so we reason directly about the equations the low-level witness satisfies, without auditing the circuit's Rust code.

The Action Statement

The high-level witness is judged by a statement of its own, independent of the circuit. In our Lean code that statement is SpecPost, and its content comes straight from the Zcash protocol specification: every clause of the Action statement in §4.18.4, plus the cross-address restriction that NU6.3 introduces.20

An Action is Ironwood's unit of transfer. Each one consumes one note and creates one note, and a bundle carries of them (the same from the knowledge error terms earlier). For each Action, a satisfying witness means:

  • correctly derived nullifiers,
  • values in range and sound value commitments,
  • spends bound to the correct spending key material,
  • well-formed note commitments,
  • enforcement of the spend and output enable flags,
  • membership of the spent note in the note commitment tree (zero-valued dummy spends excepted), and
  • an output note addressed to the spent note's own receiver whenever the restriction is active.

The record these clauses constrain holds the Action's public inputs together with the private data behind them (the notes, values, and keys underlying a shielded transaction):

-- Everything the Action statement speaks about: the ten public
-- inputs, then the private witness data.
structure ActionData where
  anchor : Fp
  cvX : Fp
  cvY : Fp
  nfOld : Fp
  rkX : Fp
  rkY : Fp
  cmx : Fp
  enableSpend : Fp
  enableOutput : Fp
  disableCrossAddress : Fp
  psiOld : Fp
  rhoOld : Fp
  nk : Fp
  vOld : Fp
  vNew : Fp
  ...
  cmOld : Point Fp
  gdOld : Point Fp
  akP : Point Fp
  pkdOld : Point Fp
  ...
  merklePath : ℕ → Fp × Fp
  ...

For example, three of the clauses as our Lean code states them:

-- Nullifier integrity:
-- nf_old = Extract([PRF(nk, ρ_old) + ψ_old] K + cm_old).
wit.nfOld = (wit.cmOld
    + (poseidonHash #v[wit.nk, wit.rhoOld] + wit.psiOld) • B.nullifierK).x

-- The anchor check, over the Merkle root recomputed from the witnessed
-- path. Real spends must land on the anchor; dummy spends have v_old = 0.
wit.vOld * (root - wit.anchor) = 0

-- Old note-commitment integrity: wherever the Sinsemilla hash lands,
-- NoteCommit must open to cm_old.
HashGuarded G.S B.noteQ
  (noteScalars wit.gdOld wit.pkdOld wit.vOld wit.rhoOld wit.psiOld).chunks
  (fun bp => wit.cmOld = bp + wit.rcmOld.2 • B.noteCommitR)

The last clause is guarded (HashGuarded): wherever the Sinsemilla hash is defined, the note commitment must open exactly as the protocol specifies. The hash is partial because Sinsemilla's underlying point addition is, and an undefined query is not a free pass; a classifier extracts the incomplete chain from the witness as a concrete break of the underlying hash, in the breaks-as-computed-data convention from earlier.

Balance integrity rests on three of these clauses in particular. Values in range rule out overflow (the trick behind Bitcoin's counterfeiting incident), and sound value commitments tie each Action's hidden values to the transparent value its bundle publicly moves in or out of the pool. Correctly derived nullifiers give every note exactly one nullifier, so the network's rejection of duplicates makes a second spend of the same note collide with the first. And membership means every real spend consumes a note that the pool actually contains.

Each of the three closes a route by which the hidden value stored in the pool could come to exceed the net public value that has flowed into it.

The Circuit Theorems

Extraction from the low-level witness runs through the circuit, whose constraints govern a grid of field elements called cells. An assignment of the cells satisfying every constraint is the circuit-level witness, and an explicit extraction function reads the record above off any such assignment.

The transcription proceeds chip by chip and region by region against the circuit code that ships with NU6.3.21

The circuit theorems prove that the constraint system means what it is supposed to mean: any satisfying assignment of the cells yields (through the extraction function) a witness satisfying SpecPost for the same public inputs. Soundness is the contract soundnessPost, as our Lean code states it:

theorem soundnessPost (G : Generators) (B : Bases) (cfg : Config) :
    FormalCircuit.Soundness (Witness := fun _ => ActionData)
      (mainPost G B cfg)    -- the transcribed synthesis
      (extractPost cfg)     -- reads an `ActionData` off the cells
      (EnvAssumptions cfg)  -- table wiring, selector distinctness
      (fun _ => True)
      (SpecPost G B)        -- the Action statement

Completeness is proven alongside (completenessPost), but it is not important for our knowledge soundness analysis since failures in completeness do not enable counterfeiting bugs.

#99 landed the deployed-key reduction that takes an accepting run, on challenges clear of the charged failure surfaces, to the circuit's semantic statement for every Action in the bundle, or to a nontrivial discrete-log relation among the URS generators.

Agreement with the Verifying Key

Given the boundary, the transcription's obligation is agreement with the constraint system encoded by the deployed verifying key, not with the code it was transcribed from. Consulting the code merely makes the proof easier to write.22

Balance Integrity

Balance integrity is what all of this work is for. It requires that the shielded pool never pay out more value than has publicly flowed into it. Knowledge soundness is one component of the analysis, and covers a single proof at a time. The property concerns an entire ledger, so the last layer of our Lean development reasons about ledgers directly.

The Ledger Model

Our Lean formalization models a ledger as a list of transactions, each carrying its Actions, its declared transparent value balance, and the sighash its signatures cover. ValidLedger transcribes the consensus rules: every Action's witness satisfies the Action statement, no nullifier is revealed twice, every anchor is a root the note commitment tree actually reached at an earlier transaction boundary, and every spend authorization and binding signature verifies.23 The theorems below quantify over every valid ledger, with no model of the network and no interaction, a superset of anything a real attacker can assemble.

Each Action in the model arrives annotated with its witness. Knowledge soundness is what justifies the annotation. It puts a low-level witness behind every accepting proof, the circuit theorems read an ActionData off it that satisfies SpecPost, and specPost_to_ledger shows that a witness satisfying SpecPost either satisfies the abstract Action statement that the ledger theorems consume or computes a break of the Sinsemilla hash from its own queries.

The Ledger Theorems

Every theorem over this model follows the convention from earlier: the property holds, or the ledger's own contents compute a concrete break.

  • Every spend is a committed output (balanceSubsetOrBreak). The nonzero spends across any prefix of a valid ledger form a sub-multiset of the outputs of strictly earlier transactions: each spend opens the exact output that created its commitment, at that output's leaf position, and no output is spent twice. Otherwise the reduction computes a hash collision inside the note commitment tree, a note commitment opened two ways, or a key-binding break.
  • Value is conserved (ValueShape.conservationOrBreak). After any prefix of a valid ledger, the pool's hidden value plus the transparent pool's balance equals the total issuance minted so far, so the pool never holds more than came in. Otherwise the reduction computes a nontrivial discrete-log relation between the value commitment bases. The binding signature carries this argument, and we return to it below.
  • Received notes stay spendable (spendabilityOrBreak). The owner of a note the pool contains can always extend the ledger with an honest transaction spending it.24 Otherwise the reduction computes the obstruction: an Action that already spent the identical opening, a note-commitment break, or two distinct openings sharing one nullifier, itself a discrete-log relation among the nullifier and commitment bases (NontrivialRelation.ofNullifierCollision). This closes the Faerie Gold attack, in which notes are crafted to share a nullifier so that spending one forfeits the rest.
  • Spending requires authorization (spendAuthorityOrBreak). If an Action spends a note addressed to a victim, in a transaction whose sighash the victim never signed, the reduction computes a verifying forgery against a randomization of the victim's spend authorization key, or a key-binding break.25

The Binding Signature

The binding signature ties a bundle's hidden values to the transparent value it declares. Every Action publishes a value commitment to its net value , over fixed bases and , with the randomness hiding the value perfectly. Consensus sums a bundle's commitments and subtracts a randomness-free commitment to the declared amount, yielding the binding verification key , in which is exactly the imbalance between the witnessed values and the declared amount. The transaction must sign its sighash under with base , a Schnorr-style argument of knowledge of with . An imbalanced bundle has therefore presented two representations of one group element over and , and subtracting them is a relation:

-- An imbalanced bundle whose binding signature verifies computes a
-- nontrivial discrete-log relation between the two value bases.
def NontrivialRelation.ofImbalance (V R bvk : M) (A B bsk : F) (hA : A ≠ 0)
    (hExtract : bvk = bsk • R) (hSum : bvk = A • V + B • R) :
    NontrivialRelation V R

A relation between and always exists, just as one did among the URS generators. The assumption forbids computing one. The dichotomy so far gives balance only modulo the group order , not over the integers. The Action statement's range clauses close the gap. Because every value is range-proven and a bundle carries at most Actions, the net value cannot wrap, so a residue of zero forces the integer to be zero (intBalance_eq_zero_of_lt).

Key Binding

The key-binding break that two of the theorems above fall back on has a theorem of its own, ZIP 2005's key-binding theorem: a satisfying witness pins the key material behind its address, or a break is exhibited. We prove that two openings of one key commitment force their committed scalars equal up to sign (commit_scalar_pm), that a break computes a random-oracle collision up to sign, and that producing a collision within oracle queries has probability at most the birthday bound (break_measure_le_adaptive).26

The Probabilistic Capstones

Each property then carries a probabilistic capstone. An adversary is an arbitrary distribution over valid ledgers, a violation lands its reduction in one of the break arms, and the violation's probability is at most the sum of the arms' break probabilities (balanceSubset_measure_le and its siblings). Every break in the ledger layer's vocabulary reduces to the discrete logarithm, to the random oracle, or to the unforgeability of the spend authorization signature; the first two are the same assumptions the SNARK analysis already consumed.

The book's security definitions draw the full dependency picture. The knowledge soundness bound displayed earlier is the largest single piece of that picture, but only one piece: every clause of balance integrity now ends in a machine-checked proof.


1

Balance integrity is a soundness property which captures counterfeiting bugs. Ironwood's privacy guarantees, which concern what a transaction reveals about the notes and keys behind it, are separate properties, and proving them is outside the scope of this work.

2

As an example, our value commitments are perfectly hiding, so any commitment opens to any value. The sibling assignments in our Sinsemilla-based Merkle trees likewise vastly outnumber the field's anchors. Finding these openings is the hard part, and that hardness is the binding property of the schemes involved.

3

Whenever the prover convinces the verifier with probability exceeding the knowledge error, the extractor must produce a witness, in expected time inversely proportional to the prover's advantage over that threshold. Our capstone proves the extraction half in failure-event form (straightLineConstraintFailureEvent): the probability that the verifier accepts while the extractor comes up empty is at most the knowledge error. The efficiency half is immediate for the deployed route, whose extractor runs the prover exactly once. The Reduction aside later in this section carries the cost accounting.

11

Strictly, the proof string carries the prover's messages only, just as Schnorr signatures do in practice. These protocols are public coin: the verifier's half of the transcript is exactly the oracle's responses, recomputable by anyone.

9

The code snippets in this post are lightly abridged: we shorten names, elide some binders and annotations, and paraphrase comments, but the semantics match the repository sources.

4

Three branches at every fork, because each accepting path yields one equation in three unknown vectors (the parent commitment and the round's two), and pinning them down takes three distinct challenges. The tree is DForkCert in Zcash/Snark/Soundness/Forking/Extractor.lean, one fork level per IPA round. DeployedForkValid states that every path through the tree satisfies the verifier's equation, with each node's three challenges pairwise distinct and nonzero, and algebraicForkCertAttempt_valid (in Zcash/Snark/Soundness/Forking/Adversary/Algebraic.lean) proves that whenever the rewinder returns a tree, the tree is valid. The slice where it returns none is bounded separately. The multiopen challenges never fork; each is covered by its own root event.

5

We formalized this route end to end anyway, with the gap visible: special soundness for the IPA in isolation, unattached to the deployed verifier (ipa_extractV, a plain algorithm from a tree of accepting transcripts to a witness, whose return type bundles the witness with proofs of both opening equations); the fork-tree extractor against the deployed verifier; the oracle reprogramming that drives rewinding; and an expected-runtime analysis in the style of Attema, Fehr, and Klooß. It ships in the repository as its own capstone, with its own bound. Filling the tree takes accepting runs, and the proven bound on the rewinder's expected total is runs: polynomial in , but about at , far past the cost of simply attacking DLOG.

10

This is sometimes referred to as the Fiat-Shamir heuristic. The heuristic isn't the use of the transformation itself, but rather modeling a hash function (in our case, BLAKE2b) as a random oracle in the first place. "Random-oracle semantics" also bundles two smaller idealizations, built into our model: that the encoding of transcripts into oracle queries is injective, and that the conversion of squeezed bytes into field challenges is exactly uniform. The deployed conversion reduces 512 squeezed bits modulo the 255-bit field prime, for a per-challenge bias below . Our bounds are proved against the exactly uniform model; a transfer lemma (badSet_measure_le_of_bias) charges any conversion bias as an explicit additive term, and pinning the deployed squeeze width that instantiates it is a fact about halo2's transcript code, on the same side of the boundary as the byte encoding.

14

In the deployed bound, the adaptive slice costs and the eleven IPA quadratics , with . The multiopen and quotient terms carry the same kind of factor. The multiopen family's counts the prover's queries, the twenty-two squeezes the reduction re-reads (eleven pre-IPA, eleven IPA rounds), and the standard one-query loss; its takes whichever of the domain size and the verifier's opening-query count bounds the cleared quotient's degree, and the two cross at . The rewinding route pays for its reprogrammed replays the same way: its capstone charges at IPA depth for the runs that accept without filling a fork tree, on top of the same -slice, multiopen, and quotient families.

13

The walk and its pinned quadratics live in Zcash/Snark/Soundness/AGM/StraightLineIpa.lean. The chronology facts, that each staged computation reads only the transcript prefix and never its own squeeze, are stated by StraightLineIpaOnlineTrace and consumed by the capstone; the end-of-walk dichotomy is AlgebraicWfProof.straightLineIpaZeroOrRelation.

6

Like the random oracle, this is a modeling assumption. It restricts the class of provers the theorem quantifies over, and it is a comfortable restriction because no known attack strategy against these groups steps outside it. In our Lean code the restriction is the adversary's output type: every group element in an AlgebraicProofString (Zcash/Snark/Soundness/Forking/Adversary/Algebraic.lean) is an AlgebraicPoint, a point bundled with coefficients over the URS and a proof that they reproduce it (Zcash/Snark/Soundness/AGM/Adapter.lean). We use the model to obtain these representations. The deployed extractor decodes the batch opening from them directly, while the rewinding route earns it by forking.

7

Short for uniform reference string: its elements are independently sampled, with no intended algebraic structure or trapdoor. It is distinguished from a structured reference string, whose elements satisfy prescribed algebraic relations. "Common reference string" is the umbrella term covering both. Ironwood's URS bundles the vector-commitment generators with an inner-product generator and a blinding generator; representations, and the relations extracted from them, carry coefficient slots for all of these.

8

orchardGeneratorROSetup in Zcash/Snark/Soundness/AGM/ProbabilityVesta.lean samples uniform group elements at distinct parameter queries, modeling halo2's domain-separated derivation of the URS generators, and orchard_uniformURSIdentification_of_generatorRO proves this matches the basis distribution the discrete-log reduction simulates. As with BLAKE2b, identifying the deployed hash-to-curve with this oracle is an assumption.

15

Promoting the extracted witness to the Action circuit's semantic statement adds one more family of the same shape, for the challenges that compress the circuit's constraints into the identity the verifier checks (, , , , ): a tail bounded by at any , below , and near at .

16

The finder invokes the prover at most four times (straightLineConstraintRelationFinderCalls_le_four): one run feeds the decode, and up to three replays cover its fallback branches. At an adversary work target of , the derived solver's oracle queries and group operations each stay within and the statistical terms stay below (straightLine_consensus_2pow123_workFactor_generatorRO), so the guarantee is a conservative 123-bit work-factor target for the protocol. The cost side is a stated resource model: the decode arithmetic is charged under an explicit premise capping the represented source read from the proof at entries, and the reduction's group work enters the profile as an explicit bound rather than an instrumented count.

17

The displayed inequality is the shape-parametric capstone straightLineConstraintFailure_prob_le_of_dlogProfile written out at the deployed dimensions: eleven IPA rounds, five multiopen point sets, the captured schedule's quotient budget, and opening queries (queryBudget_at_captured_shape), with the multiopen polynomial expanded by hand from algebraicRootBudget. The repository evaluates that polynomial at the consensus maximum (straightLineConstraintFailure_prob_le_at_consensus_max), and the captured two-Action fixture discharges the remaining static premises at its own shape (orchard_deployed_knowledge_error_captured_straightLine); halo2's transcript counts one proof per Action in a bundle, so the fixture's numProofs := 2 and the above measure the same axis. At the captured key, the statement pins the key's scalar content (CapturedVerifierKeyProfile): the domain parameters, gates, query layouts, permutation chunks, and lookup expressions. Its group-valued commitments are deliberately not pinned, because the AGM experiment samples the basis they must be represented over. All three theorems bound the failure event straightLineConstraintFailureEvent: the verifier accepts while the extractor comes up empty. The extractor runs the prover once and its decoding arithmetic carries a proven polynomial cost model, so the efficiency half of knowledge soundness is discharged rather than left open. An explicit instance-free prover family, with all eleven IPA rounds live, inhabits the interface, so the premises are satisfiable at that shape. The rewinding capstone (orchard_deployed_knowledge_error_captured) remains alongside as an alternative with its own bound, its expected-run analysis charged as an explicit truncation term in the style of Attema, Fehr, and Klooß.

18

In the deployed halo2 verifier, verify_proof folds every check into a Guard, whose use_challenges method yields the final MSM for the caller's verification strategy to evaluate. Transcript and well-formedness failures reject before any combination is produced, which is the none branch of DeployedAccepts.

19

Two runs are captured, both at the Post-NU6.3 verifying key: a single-Action bundle and a two-proof capture with multiple Actions, each auto-generated by halo2's dump_vesta_lean_fixture and carrying its own fingerprint_matches theorem. MsmMatch requires the gScalars, wScalar, and uScalar coefficients to be equal and the remaining commitment terms to agree up to reordering. msmMatch_eval transfers a match to equal evaluations under any URS. The fixtures also close two gaps a captured MSM alone would leave. The instance commitments are re-derived in our Lean code from the captured public inputs rather than trusted as opaque captured points, and proven equal to the commitments the deployed verifier used (instance_commitments_derived; zcash/halo2#918, zcash/orchard#538). And replaying deriveChallenges against the captured squeeze schedule reproduces every challenge the Rust verifier drew (deriveChallenges_matches_captured_schedule), chaining into the same match (nonInteractiveFingerprint_matches); the schedule is captured as typed transcript events, so the byte-level encoding identification remains an assumption. These comparisons bottom out in native_decide, whose compiled evaluation is itself a trust point, surfaced by assert_axioms in the fixtures' trust boundaries. The Rust side of the capture (FingerprintStrategy, ChallengeRecorder, MSM::fingerprint_terms) lives in zcash/halo2#914 and the capture test in zcash/orchard#531; Rust key generation and the comparison against the canonical Post-NU6.3 PinnedVerificationKey remain at the fixture-generation boundary rather than being reimplemented in Lean.

20

The one constraint NU6.3 adds to the Action circuit: when a designated public input is nonzero, the output note must be addressed to the same expanded receiver as the note being spent. Consensus keeps the flag on for Actions in the legacy Orchard pool, winding that pool down, and off for Ironwood. The mechanism is described in the design notes.

21

The transcription lives beside the SNARK proofs in the same repository. The proof-carrying bundle is Zcash.Circuits.Action.Circuit.circuit, and its synthesis is ported region by region (there are 395) from the Rust circuit.rs, on top of proof-carrying subcircuits for the elliptic-curve arithmetic, Sinsemilla, Poseidon, and the lookup range checks.

22

Mechanically, the constraint system that the transcription's configure builds is projected onto the same data shape dumped from the Rust circuit's configuration and compared for literal equality, before and after halo2's selector compression. That comparison runs in our continuous build (the CircuitVkCheck target), against dumps whose hashes the build pins, and #99 landed something stronger: the verifying key derived in our Lean code from the transcribed circuit is proven equal to the captured one (vk_eq_derived). The circuit theorems also carry environment premises (EnvAssumptions: shared-table wiring and a selector-distinctness fact), discharged for the transcribed configure; table contents are never assumed, but derived from the circuit's own constraints.

23

ValidLedger's remaining conjuncts: the tree never overflows its capacity, declared balances are in range, per-transaction Action counts are bounded, and the transparent chain value pool balance never goes negative (the consensus rule of §4.17 of the protocol specification; the model reduces the transparent pool to this one aggregate balance). The model tracks transactions rather than blocks, and every anchor references a transaction boundary, so an Action can never spend an output of its own transaction. Anchors are per-Action rather than per-transaction, a documented simplification that only widens what the adversary may do.

24

"Always" carries the evident side conditions as hypotheses: room left in the note commitment tree, a transparent pool that stays nonnegative, a declared balance in range, and the signatures the owner's wallet itself produces. spendabilityOrBreak lists them explicitly, and honestTx_valid is the completeness half it rests on.

25

The Action statement pins a verification key behind an address only up to its y-sign, so the computed forgery carries the sign as data and the unforgeability hypothesis must cover both signs of the key, a deliberate factor-of-two loss. That hypothesis is the unforgeability of the re-randomized signature scheme (RedDSA).

26

Key binding reaches the ledger theorems through an interface with two instantiations. The birthday bound covers ZIP 2005's Recovery Statement, whose derivation constraints tie the commitment's randomness to a random oracle; it is ZIP 2005's as sharpened in zcash/zips#1338. The deployed Action circuit enforces the bare commitment opening instead, and its breaks fold into the same Sinsemilla discrete-log terminal as the other arms.