Ragu & Tachyon
Home
  • Scaling Zcash
  • An Introduction to Proof-Carrying Data
  • Statements and Challenges
  • Arithmetization
  • QAP and Polynomial Encoding
  • Polynomial Commitments
  • The Inner Product Argument
  • Batched Openings
  • The Complete Proof
  • Recursive Proofs
  • Accumulation
  • Split Accumulation
  • Curve Cycles
  • Revdot Folding
  • Wiring and the Registry
  • Endoscalars
  • Staging
  • Recursion in Ragu
  • Notes, Nullifiers, and Tachygrams
  • Pool State and Anchors
  • Oblivious Sync
  • The Proof Tree
  • Aggregation and Bundles
Final Recap
  • Notation
  • Glossary
  • Curves
Home
  • Scaling Zcash
  • An Introduction to Proof-Carrying Data
  • Statements and Challenges
  • Arithmetization
  • QAP and Polynomial Encoding
  • Polynomial Commitments
  • The Inner Product Argument
  • Batched Openings
  • The Complete Proof
  • Recursive Proofs
  • Accumulation
  • Split Accumulation
  • Curve Cycles
  • Revdot Folding
  • Wiring and the Registry
  • Endoscalars
  • Staging
  • Recursion in Ragu
  • Notes, Nullifiers, and Tachygrams
  • Pool State and Anchors
  • Oblivious Sync
  • The Proof Tree
  • Aggregation and Bundles
Final Recap
  • Notation
  • Glossary
  • Curves
  • Scaling Zcash
  • An Introduction to Proof-Carrying Data
  • Statements and Challenges
  • Arithmetization
  • QAP and Polynomial Encoding
  • Polynomial Commitments
  • The Inner Product Argument
  • Batched Openings
  • The Complete Proof
  • Recursive Proofs
  • Accumulation
  • Split Accumulation
  • Curve Cycles
  • Revdot Folding
  • Wiring and the Registry
  • Endoscalars
  • Staging
  • Recursion in Ragu
  • Notes, Nullifiers, and Tachygrams
  • Pool State and Anchors
  • Oblivious Sync
  • The Proof Tree
  • Aggregation and Bundles
  • Final Recap
  • Appendix

    • Notation
    • Glossary
    • Curves

Recursion in Ragu

Part four built the pieces one at a time. We have a way to fold revdot claims, a registry that makes wiring cheap to check, endoscalars that let challenges cross the curve cycle, and stages that let circuits share data. This chapter puts them together into the operation that Ragu actually runs, and then follows a finished proof out of the tree.

The first half describes one fuse. We list what it has to do, the order of its messages, the circuits that divide the work, the split of the transcript between two of those circuits, and the public inputs they share. The second half describes compression, with a complete run on the toy curve, and then covers what the verifier pays, the size of the result, rerandomization, and the comparison with Orchard.

Reconstructed from code at commit 3219e08, not confirmed by the authors

The descriptions of the circuits and of compression follow the code and the book's recursion chapters. The numeric runs use small circuits of our own and a shortened instance, so they show the structure of the code and not its real contents. The sizes come from our reading of the code's wire format. We did not build or run the code, and we did not run its benchmarks.

Terminology

This site says fuse for the operation at a node of the tree, as chapter 02 explained, and native and nested for the two sides of the cycle, as chapter 13 explained. The decider is the check that runs when the recursion ends. The code writes pre_beta for the challenge from which β is extracted.

1. What one fuse has to do

A fuse receives two child proofs and a step, and it has to produce a new proof that a parent can fuse in turn. Chapter 10 described the idea as a proof about a verifier. The children's verifier cannot run in full, so each child's expensive work joins an accumulator, and a fuse checks the cheap remainder inside its circuits. That remainder is still large, and a circuit may hold at most n=211 gates. These are the jobs:

JobExplained inWhere the code does it
Run the application's stepChapter 02Application circuit
Replay the transcript to get the challengesChapter 03hashes_1, hashes_2
Compute the children's k(y) from their public dataChapter 10hashes_1, collapse
Fold the scalars of the revdot claimsChapter 14collapse
Check the batched evaluation vChapter 08compute_v
Connect the challenges to the other side of the cycleChapter 16bind_challenges, bind_beta
Fold the other curve's commitmentsChapter 16bind_endoscalar, endoscaling_step

Dividing the jobs among circuits creates a new need, because the circuits have to agree with each other. They must use the same challenges, and they must read the same child data. Stages give them shared witness data, and Section 5 gives them shared public inputs. So a fuse produces many small traces where a textbook recursion step would produce one large one.

In the standard profile, one fuse therefore fills 45 native polynomials, of which 34 are circuit traces and 11 are stages, and 39 nested polynomials, and it adds the a, b, registry_xy and p polynomials on each side, for 92 in all. The native side folds 86 revdot claims and the nested side folds 72.

Each internal circuit's claim counts once per child in the fold, since a parent checks both children's copies. All of these polynomials come from one call to fuse.

2. The order of the messages

Each challenge has to depend on everything it tests, so the order of the commitments is the backbone of a fuse. The code follows one schedule, and its phases sit in numbered files:

PhaseFileWhat the prover computesChallenge that follows
1_01_applicationThe step's trace, with the headersNone
2_02_preambleA stage holding the children's dataw
3_03_s_primeThe restrictions m(w,xi​,Y) for each childy, z
4_04_inner_errorm(w,X,y) and the first layer's quotientsμ
5_05_outer_errorThe second layer's quotient, the groups' values and the saved sponge stateμ′
6_06_abThe new accumulator pair A, Bx
7_07_querym(W,x,y) and the values that the registry claimsα
8_08_fThe batch quotient fu
9_09_evalThe value of every polynomial at upre_beta
10_10_pThe new batch polynomial p and the walks over its pointsNone
11_11_circuitsThe traces of the internal circuitsNone

Nearly every row is a step of an earlier chapter. Phases 2 to 4 and 7 run the walk of chapter 15, phases 4 to 6 run the quotient fold of chapter 14, and phases 8 to 10 run the split accumulation of chapter 12.

Two details of the last rows matter later. Only pre_beta is ground, because it has to lie in the endoscalar range, so the prover re-blinds the eval stage and tries again until it does. And the internal circuits come last, because they prove statements about everything before them. Their traces feed no challenge of this fuse. Instead their commitments enter the parent's preamble, which puts them on the parent's transcript.

3. The internal circuits

We can now say what each circuit does. This table extends the one in Section 15 of the staging chapter, and it adds a column for the job:

CircuitIts jobStages in its sumFinal mask
hashes_1Derives w, y, z, saves the sponge state, enforces the children's unified k(y) and that their circuit IDs are domain pointsPreamble, outer errorOuter error
hashes_2Resumes from the saved state and derives μ, μ′, x, α, u, pre_betaOuter errorOuter error
collapseEnforces the children's application k(y), and checks both layers of the fold down to cPreamble, inner error, outer errorInner error
compute_vComputes v from the query and eval stages, using the lift of βPreamble, query, evalEval
bind_challenges (4 circuits)Recomputes the nested challenge stage's commitment from the transcript's challengesPreamble, query, evalEval
bind_betaFinishes that binding for each child, and ties the child's walked points to its unified instancePoints binding, preamblePreamble
bind_endoscalarTies the walk's bits to pre_beta, enforces curve membership, and exports the walk's end pointThe five input points stages, walkWalk
endoscaling_step (23 circuits)Performs 4 endoscalings of the Horner walk over the nested batchThe five input points stages, walkWalk

The stage column shows why stages were worth building. The hashes_1 circuit computes each child's k(y) and needs the child's data, which sits in the preamble. The collapse circuit reads that same data. Both read one polynomial, so a prover cannot show them different children, and only the bridge commitment has to enter the transcript.

The nested side has a shorter list. Its collapse checks the nested fold and pins its unified instance to the stages, its compute_v computes vn​, and 26 endoscaling steps walk the native batch. The loading polynomial ties the walk's points to the bridge stages. That side has no transcript and no application circuit, as chapter 13 found.

The application's circuit uses no stages. All of the machinery above sits beside it and leaves its budget of gates to the application.

The stage chains

Book: the recursion chapter gives hashes_2 the chain preamble, outer error.

Code at 3219e08: hashes_2's claim sums only its own trace and the outer error stage, and its documentation marks the preamble as skipped. The layout still reserves the preamble's gates, which is what skipping a stage means.

This site follows the code.

4. Splitting the transcript

The transcript has to be replayed in circuit, because the Fiat–Shamir challenges would otherwise be numbers that the prover merely asserts. One circuit cannot hold the whole replay:

A permutation costs 288 gates, and the code documents 3 permutations in the first half and 5 in the second. Together that makes 8 permutations and 2,304 gates, which already exceeds the limit of 2,048 gates per circuit before any other check runs. The two halves cost 864 and 1,440 gates, so each fits.

So the replay has to cross a circuit boundary, and the sponge's state has to cross with it. The second circuit cannot recompute the state without repeating the permutations it is trying to avoid, and passing the state as a public input would add wires to every k(Y). The code places it in a stage. The state is known once the inner error bridge is absorbed, and the outer error stage is the next one committed, because it holds values folded with the μ that the state produces.

Eight bridge commitments in a row, with the challenge each one releases below it. A blue line marks the first three as hashes_1, and a teal line marks the last five as hashes_2. An arrow takes the sponge state from the inner error commitment into a box for the outer error stage, and another arrow takes it to hashes_2, which squeezes mu first.

We run this on the toy sponge from chapter 03. The bridge commitments are points on E′, whose coordinates lie in F43​, the field the sponge works in. The first point is the one chapter 17 built, and the others are stand-ins:

Bridge commitmentToy point on E′Squeezed nextReplayed by
preamble(21,25)w=11hashes_1
s_prime(32,3)y=4, z=10hashes_1
inner_error(34,3)μ=2hashes_1, then saved
outer_error(35,21)μ′=10hashes_2
ab(37,7)x=4hashes_2
query(40,18)α=12hashes_2
f(42,7)u=25hashes_2
eval(2,12)pre_beta=33hashes_2

The first half ends by permuting and saving the three cells of the state, (2,42,12). The second half loads those cells and squeezes μ=2 with no further permutation, which is why its count starts from the absorption of outer_error. We ran the sponge once straight through and once split at this point, and all nine challenges agree. The toy sponge takes 3 permutations in the first half and 5 in the second, as the code does, because we leave out the domain tag and its rate of 2 fits one point per permutation.

Reading the state from a stage leaves one question, which is what forces the stage to hold the right value. Only hashes_1 knows that value, having computed it, so the check goes in that circuit:

// save_state() applies a permutation (since there's pending absorbed data)
// and returns the raw state, ready for squeeze-mode resumption in hashes_2.
transcript
    .save_state(dr)
    .expect("save_state should succeed after absorbing")
    .enforce_equal(dr, &outer_error.sponge_state)?;

Without this line, a prover could pick the state and with it the challenges:

Suppose a prover wants μ=9, and it writes the state (9,42,12) into outer_error. The second half alone would resume from this state and produce μ=9 and μ′=20. Of the 79,507 possible states, 1,849 give the wanted μ, but the first half compares the stage with the state it computed, and exactly 1 state passes that comparison, the honest one. Both circuits read one committed stage polynomial, so the prover cannot show them different values.

5. Public inputs

Each circuit also has a public input polynomial k(Y), and a parent has to compute k(y) for each of a child's circuits. If every internal circuit exposed its own values, a parent would compute many different polynomials. The code avoids that with a unified instance, one structure of values that nearly all internal circuits expose in full, so that one computed k(y) serves them all.

Sharing an instance raises the question of who checks each value, since a circuit that merely exposes a value proves nothing about it. The code assigns each slot to exactly one circuit, and the prover asserts that all were covered. Other circuits read a slot as a witness, and the shared k(y) forces them to agree with the owner:

SlotsWiresOwner
Bridge commitments for preamble, s′ and inner error; w, y, z9hashes_1
Bridge commitments for outer error, ab, query, f and eval; μ, μ′, x, α, u, pre_beta16hashes_2
c1collapse
v1compute_v
The nested challenge stage's partial commitment2The last bind_challenges
Pn​, An​, Bn​ and the nested registry restriction8bind_endoscalar

The rows add to 13 points and 11 elements, which is the code's 37 wires.

Recursion uses three forms of k(Y) over these values, plus a fourth with no wires of its own. All are Horner evaluations ending in the constant wire's 1:

kunified​(Y)=Horner(unified∥0∥1, Y),

kbridge​(Y)=Horner(unified∥left∥right∥0∥1, Y),

kapp​(Y)=Horner(left∥right∥output∥1, Y).

Here Horner over wires w0​,…,wm−1​ and the final 1 means (⋯(w0​y+w1​)y+⋯+wm−1​)y+1, so the last wire before the 1 is the coefficient of Y. The hashes_1 circuit uses kbridge​, because it exposes the child headers, and the application circuit exposes the same headers through kapp​. Together those two claims stop a proof from using one pair of headers in the step and another in the recursion. An endoscaling step exposes nothing but the zero, so its polynomial is k(Y)=1.

Take a toy unified output of five wires, (21,25,11,4,10), which are the first bridge point and the challenges w, y and z from the run above. Take child headers (8,3) and (12,3), whose second entry is the suffix, and the output header (20,3). At y=4:

PolynomialWires, in orderValue at yCoefficient of Y
kunified​Unified output, 0390
kbridge​Unified output, left, right, 0260
kapp​Left, right, output63
Endoscaling step010

Horner's rule reaches 40 after the five unified wires, and both kunified​ and kbridge​ continue from that value. Each table entry also matches a direct evaluation of the polynomial.

These values reach the fold through the parent. Its hashes_1 and collapse circuits compute each child's values from the preamble stage with gates, at the y of the current fuse, and those values become the targets of the claims being folded.

The unified instance

Book: the public inputs chapter says that the shared structure holds 29 wires, from eight commitments and thirteen field elements. The recursion chapter's list gives nine challenges and two final values, which makes eleven elements. The public inputs chapter also says that four internal circuits share kunified​.

Code at 3219e08: thirteen points and eleven elements, which makes the 37 wires above. The five additional points are in the last two rows of the table. The count of circuits sharing kunified​ is nine, because bind_challenges has four instances.

This site follows the code.

6. Keeping internal and application circuits apart

The registry holds internal and application circuits side by side, and a proof names its application circuit by a domain point the prover selects. So a prover could try to name an internal circuit and claim its trace as an application step. To rule that out, the code makes the two kinds of k(Y) unable to coincide. Each internal circuit ends its output with a zero, and each application header ends with a suffix that is never zero.

Let an internal circuit expose wires (a,b) and its zero suffix, so that kint​(Y)=aY3+bY2+1, and let an application circuit expose a header (d,s), so that kapp​(Y)=dY2+sY+1. If an application could use the suffix s=0, then 43 of the 79,507 triples (a,b,d) would make the two polynomials identical, and the swap would pass at every y. For s=3,4,5 the difference depends only on a and b−d, so we tried all 1,849 pairs of those, and none makes the polynomials identical. The two values then agree at no more than 2 of the 42 nonzero values of y, and on a real field the same count is a negligible fraction.

This also explains why an endoscaling step needs the zero although it exposes nothing. Its comments say that the suffix is what stops a step from satisfying an application instance, given that the application's circuit ID may name any native slot. The nested registry holds internal circuits only, so it omits the zero.

The first application suffix

Book: application-defined suffixes begin at two, with one reserved for the trivial header.

Code at 3219e08: three values are reserved, with 0 for fixed circuits, 1 for the unit header and 2 for the Dummy header of the base case, so application suffixes begin at three.

This site follows the code. The book's argument is unaffected, since it needs only that the suffix is nonzero.

The value 2 has a job of its own, which chapter 10 described. Only the internal Bootstrap step declares the Dummy input, and only then does collapse waive its final comparison. The Rerandomize step has a witnessed suffix, so it constrains that wire away from 2.

7. What a proof carries to its parent

A parent does not repeat the checks in a child's circuits. It folds the child's claims into its own accumulators and reads the child's public data. Here is what it takes from a child:

From the childWhat the parent does with itExplained in
Headers and unified instanceHolds them in the preamble and computes the child's k(y) valuesSection 5
(A,B,c)Adds the pair as a raw claim with target c, folded into the new (A,B,c)Chapter 14
Each trace and stage commitmentAdds a circuit claim or mask claim to the same foldChapter 17
(P,u,v)Adds the claim p(u)=v to its batch, folded into the new (P,u,v)Chapter 12
m(W,xi​,yi​)Opens it at the new w, and opens its own new m(W,x,y) at the child's circuit ID, so one restriction remainsChapter 15
Nested points and challenge partialWalks the points and completes the challenge binding in bind_betaChapter 16

Three accumulators therefore pass up the tree, namely (A,B,c), (P,u,v) and m(W,x,y), one of each per side of the cycle. Each fuse replaces two of each with one, so a proof has the same shape at every depth. A child's internal circuits do not run again in the parent, and they cannot be skipped either, since their claims are among those folded.

The application's circuit sits in the registry like any other, and the base case runs through a single bootstrap proof. Chapter 10 records that the code has no Merkle root of initial states.

8. From a large proof to a short one

The accumulators stay few, but each one still holds polynomials of 213 coefficients, and so does each stage and trace. A parent needs those polynomials to compute its fold. A verifier outside the tree does not want to receive them. The code's verify_uncompressed does hold every polynomial and checks the claims directly, and the code keeps it as a test oracle.

Compression replaces each polynomial with its commitment and a few of its values, and it restates the decider's checks so that those are enough. Chapter 09 ran this idea on one circuit. The book's compression chapter and the code arrange it in three reductions, which run once per curve:

The sections below run all three on the toy curve. We use the two circuits from the staging chapter, with D=16 and the sixteen generators listed there. We choose the challenges by hand. In the code a transcript under its own tag supplies them, after absorbing the instance and the output header, with fresh w, y, z and σ on each curve.

9. The instance and its claims

The instance has to tell the verifier everything it would otherwise read off the polynomials. For our two circuits that gives this:

Instance entryToy valueRole
Aˉ, Qˉ​(22,24), (14,9)Two stages. Qˉ​ holds the values read from the registry
Rˉ1′​, Rˉ2′​(24,30), (24,1)Final traces of the squaring and cubing circuits
Aˉacc​, Bˉacc​(1,29), (25,29)The accumulator pair
c11Its stored revdot value
Mˉ(17,24)The restriction m(W,9,7)
Pˉ, v(27,30), 37The batch claim at the fuse's u=9
A(u), B(u)35, 15The accumulator pair at that u

These numbers carry over from earlier chapters. The accumulator pair stands in for what the last fuse left, and we took the squaring claim at x=5 for it. The polynomial p behind Pˉ is chapter 06's example, with its opening point 9. The real instance holds a commitment for each native and nested polynomial, which makes it the largest part of the proof.

The verifier now rebuilds the decider's revdot claims from what it holds, in the order of the code's claim list:

SlotClaimKindLeft vectorRight vectorTarget ki​
0AccumulatorRawAacc​Bacc​11
1Squaring circuitCircuita+r1′​r1​(zX)+s1​(X,y)+t(X,z)7
2Cubing circuitCircuita+r2′​r2​(zX)+s2​(X,y)+t(X,z)22
3Stage maskBondingza+qMask of gate 10
4Final maskBondingzr1′​+r2′​Mask of gates 2, 30
5Wire bindingMaskedq−Eσ​Mσ​0

The first five claims have appeared in earlier chapters. The sixth exists because the decider also compares individual wires of certain stages against values it computes for itself, and a verifier that holds only a stage's commitment needs another way to make that comparison. The code turns it into a revdot claim. For wires at degrees dj​ with expected values ej​, it sets

Eσ​=j∑​ej​Xdj​,Mσ​=j∑​σjXD−1−dj​,

and the revdot product picks out the differences at those wires:

⟨⟨q−Eσ​,Mσ​⟩⟩=j∑​σj(qdj​​−ej​).

The target is 0, and it holds at a random σ only if each wire holds its expected value. The real list has five such claims natively and three on the nested side. In our run a small stage q(X) plays the role of the query stage:

The registry here has two circuits at W=1 and W=−1, whose wiring gives m(1,9,7)=11 and m(−1,9,7)=12. The stage q(X) claims those values in the wires a1​ and d1​. With σ=20, the mask Mσ​ has a 1 opposite a1​ and a 20 opposite d1​, and Eσ​ holds the expected values at those two degrees. So the product is (a1​−11)+20(d1​−12)=0. If the stage holds a wrong a1​, the product is nonzero at all 43 values of σ. If both wires are wrong, the two errors cancel at one value of σ out of 43.

A real compressed proof has 56 native claims, namely one accumulator pair, one application circuit, 33 internal circuits, 16 masks and 5 wire bindings, and it has 46 nested claims. In groups of 7, the native side has 8 groups, 336 first-layer terms and 56 second-layer terms, and the nested side has 7 groups, 264 first-layer terms and 42 second-layer terms.

10. Folding to three claims

Now the fold, in the form the code's compression uses. Chapter 14 noted that this differs from the fuse's quotient fold, and that the sources do not say why. Claim i in group g and slot s takes the weights

λi​=(μ′)gμs,ηi​=(ν′)gνs,

and the folded pair is A=∑i​λi​ai​ and B=∑i​ηi​bi​. To see the target, expand the revdot product and separate the terms with i=j:

⟨⟨A,B⟩⟩=i∑​λi​ηi​⟨⟨ai​,bi​⟩⟩+ε1​+ε2​.

The first sum holds the true targets ki​. The other two terms collect the products ei,j​=⟨⟨ai​,bj​⟩⟩ that the verifier cannot compute, with ε1​ for pairs inside a group and ε2​ for pairs across groups. The verifier does not need those products individually. The prover lays them out as low coefficients of a vector Eℓ​ for each layer, and the weights go in mirrored positions of a public vector Wℓ​, so that each εℓ​ is itself a revdot product, as chapter 14 showed. Our six claims fall into two groups of three, where the code uses seven per group:

With μ=19, ν=38, μ′=2 and ν′=33:

ValueToy result
First-layer terms, group by group(40,9,2,36,11,34,36,28,29,0,0,0)
Eˉ1​, committed before μ and ν(20,25)
Second-layer terms(22,30)
Eˉ2​, committed before μ′ and ν′(27,1)
ε1​=⟨⟨E1​,W1​⟩⟩6
ε2​=⟨⟨E2​,W2​⟩⟩12
∑i​λi​ηi​ki​10
K, the target of (A,B)28
⟨⟨A,B⟩⟩, for comparison28

The vectors A and B have public parts that nobody needs to commit to, so the verifier separates what it derives from commitments and what it evaluates alone. A circuit claim's bi​ contains ai​(zX), so the committed polynomial T=∑circuits​ηi​ai​ takes care of that part at the dilated point rz:

A(X)=Ac​(X)+Apub​(X),B(X)=Bc​(X)+T(zX)+Bpub​(X).

Here Bpub​ holds the wiring restrictions, the t(X,z) terms, the masks and Mσ​, and Apub​ holds the expected values of the wire bindings. The commitments come from the instance:

The verifier obtains Aˉc​=(11,30), Tˉ=(20,25) and Bˉc​=(25,29) from small MSMs over the instance's points. Committing to the three polynomials directly gives the same points.

11. The split

We have three revdot claims, (A,B,K), (E1​,W1​,ε1​) and (E2​,W2​,ε2​), and nothing has yet shown that any of them is true. The split from chapter 05 handles this. The prover combines the products with a challenge ρ,

t(X)=A(X)B(X)+ρE1​(X)W1​(X)+ρ2E2​(X)W2​(X),

and writes the result as t(X)=XD−1p(1/X)+XDq(X). A revdot product is the coefficient of XD−1 in the ordinary product, so that coefficient of t is the combination of all three revdot products, and it lands in p(0). This is the one place that pays for the product polynomial and its FFTs. The verifier then picks a point r and checks the identity at it, and it sets aside its required value of p(0) to be opened later:

At ρ=41 the prover commits to pˉ​=(23,7) and qˉ​=(30,8), and the required value is p(0)=K+ρε1​+ρ2ε2​=21. At r=18, so that rz=26 and 1/r=12, it sends seven values:

Ac​(r)T(rz)Bc​(r)E1​(r)E2​(r)p(1/r)q(r)
38712253108

The verifier adds what it can compute alone. It needs s1​(r,y)=39, s2​(r,y)=9 and t(r,z)=3, the masks at r, which are 28, 29 and 5, and the two public vectors, W1​(r)=14 and W2​(r)=7. That gives A(r)=38+20=15 and B(r)=12+7+10=29, modulo 43. The left side of the split is A(r)B(r)+ρE1​(r)W1​(r)+ρ2E2​(r)W2​(r)=34, and the right side is r15p(1/r)+r16q(r)=34.

The two sides agree, but all seven values are still only claims about commitments, which the next step settles.

12. Batching and the argument

The reduction leaves eight claims, seven from the values above and one for the required p(0). The instance adds four of its own, which carry the fuse's accumulators into the check:

Claim jPolynomialCommitmentPoint xj​Value yj​
0Ac​(11,30)1838
1T(20,25)267
2Bc​(25,29)1812
3E1​(20,25)1825
4E2​(27,1)183
5p(23,7)1210
6q(30,8)188
7p(23,7)021
8m(W,9,7)(17,24)78
9P(27,30)937
10Aacc​(1,29)935
11Bacc​(25,29)915

With only 43 points on the toy curve, some commitments repeat by chance, such as those of T and E1​. The polynomials themselves differ.

The last four rows are what the decider still owes from the recursion. Claim 9 is the fuse's accumulated (P,u,v), and claims 10 and 11 pin the values of the accumulator pair at that u. Claim 8 is the restriction check of chapter 15:

Claim 8 opens m(W,9,7) at the fresh point w=7. The verifier computes the value 8 for itself, by evaluating the wiring of both circuits and combining the results with Lagrange weights, and this is the step that reads the whole registry.

Chapter 08's batch turns all twelve into one. The prover commits to the quotient f(X)=∑j​αm−1−j(hij​​(X)−yj​)/(X−xj​) and sends each polynomial's value zi​ at a new point u. Then the verifier derives, with n=11 polynomials and m=12 claims,

Hˉ=βn⋅Fˉ+i=0∑n−1​βn−1−i⋅hˉi​,

v=βnj=0∑m−1​αm−1−ju−xj​zij​​−yj​​+i=0∑n−1​βn−1−izi​.

Unlike the fuse's β, which has to be short so that a circuit can scale points with it, this β is an ordinary squeezed element, since no circuit runs this verifier.

With α=17 the quotient commits to Fˉ=(5,29). At u=38 the prover sends the eleven values (18,5,35,18,1,12,36,14,5,22,35). The verifier derives f(u)=30 from those values and the claims. With β=20 this gives Hˉ=(28,10) and v=14, and the prover's polynomial H agrees on both.

Now the inner product argument of chapter 07, in the code's form, proves H(u)=v:

The argument commits to the masking polynomial, Sˉ=(26,23), and takes ξ=29 and a multiplier z=12 on U. The statement becomes Hˉ−v⋅G0​+ξ⋅Sˉ=(1,29). Then come log2​16=4 rounds:

RoundChallengeLj​Rj​
112(25,29)(23,7)
217(30,8)(22,7)
333(22,7)(25,29)
441(16,21)(23,24)

The final scalar is a^=38. The verifier computes G^=(18,10) from an MSM over all sixteen generators and b^=14 from a product of four factors. The folded statement is (22,7), and a^⋅(G^+b^z⋅U)=(22,7), so the verifier accepts.

That finishes the run on this curve. In the code the same three reductions run again on the nested curve with its own claims, on one continuous transcript.

13. What the verifier pays

Chapter 09 named two linear costs, computing s(x,y) and computing G^, and part three set out to remove them from the circuits. They have not disappeared. No circuit pays them, which is what recursion needed, and the wiring chapter deferred the last piece to this page. The verifier of a compressed proof pays them once, at the root:

Verifier step, per curveWorkGrowth
Replay the fuse transcript8 permutations, on the native field onlyConstant
Recompute two nested stagesTwo small commitments, on the nested curve onlyConstant
Compute targets k(y)One Horner step per public wireSize of the instance
Derive Aˉc​, Tˉ, Bˉc​, HˉMSMs over the instance's pointsSize of the instance
Compute si​(r,y) for each circuit claim34 native and 28 nested circuits, one term per wiring coefficientLinear in the circuits
Compute registry values for the wire bindingsOne pass over the domain at (x,y), and natively 11 further evaluations of mLinear in the registry
Compute m(w,x,y)One pass over the registry, with Lagrange weightsLinear in the registry
Compute masks and t(r,z)Closed formsLogarithmic
Compute b^ and combine the Lj​, Rj​26 scalar multiplications and 13 factorsLogarithmic
Compute G^1 MSM of 8,192 scalar multiplicationsLinear in the vector length

The rows in bold are what remains of chapter 09's two costs. Wiring appears three times, because the compressed verifier evaluates each circuit claim's restriction at (r,y), the whole registry over its domain and at further points to fill the wire bindings, and the registry at the fresh (w,x,y). This last evaluation is the one chapter 15 described. It lets a whole tree of fuses rely on one restriction polynomial, since the verifier checks that polynomial against the full registry exactly once. Each of these passes grows with the wiring of the registered circuits, including the application's steps.

The last row is the deferred MSM of chapter 11. Compression computes it in full, with one per curve. None of these costs grows with the depth of the tree, so a proof that covers many leaves has the same verifier as a proof that covers two.

14. The size of the result

Counting from the code's wire format gives the following, for the standard profile at the production rank of 13 and headers of 4 elements, which is the size the code's benchmarks use:

PartPointsScalars
Instance, commitments and values9319
Instance, two child headers of 4 elements08
Revdot reduction, per curve49
Batch, per curve111
IPA, per curve271
Total, with two curves15769

At 32 bytes per point or scalar, with 4 bytes for the circuit index and the 28-byte envelope, that comes to 7,264 bytes. The uncompressed encoding writes 8,192 coefficients for each of 92 polynomials, which is 24,117,248 bytes before counting its cached points, so compression shrinks the proof by a factor of about 3,320. Most of the compressed proof is the instance, with 93 of its 157 points. The IPA rounds, the only part that grows with the circuit size, take 52 points.

The code's comment on profiles says "a few kilobytes", which agrees with this count.

A header larger than 4 elements adds 64 bytes per extra element, since the proof holds the two child headers. The output header does not count, because the verifier supplies it as the statement.

15. Why cheating fails

The chapters before this one each counted cheating at their own step. Here we look at what compression adds, which is the chance to hide a false claim in a value the verifier will never check directly. Start with a false public input:

Suppose the prover claims the output 11 for the squaring circuit in place of 10, so its target rises by y=35 while its vectors stay honest. At our challenges the folded pair then misses K by 14. Before the challenges, it can lower the term e0,1​, whose weight is ν, by some amount e. The false target adds 35μν to K and the lowered term removes eν, so the fold balances only if μ=e/35. For e=5 that means μ=37, and we confirmed the balance there on the full vectors. Over all 1,764 nonzero pairs (μ,ν), every nonzero e balances exactly 42 pairs, which is one value of μ out of 42.

The value ε1​ is a second place to try:

After seeing μ and ν, the prover could raise ε1​ alone and leave E1​ as committed. That can make (A,B) balance, but it makes the claim (E1​,W1​,ε1​) false by the same amount, and ρ arrives only after ε1​ is fixed. We tried all 42 nonzero shifts against all 43 values of ρ, and each shift passes at exactly 1 value of ρ.

The prover could also try to change the required value of p(0) directly:

Suppose the prover wants p(0) to be too large by 7. The coefficient of X15 in X15p(1/X)+X16q(X) is exactly p(0), so whatever else it does, the difference from the true product is a nonzero polynomial of degree at most 30. The prover can choose the other coefficients of p and q to give that difference as many roots as possible, and we built such a pair. It passes the split at 30 of the 42 nonzero values of r. This is the toy field's small size at work, since the same count on the real parameters is 214−2 roots out of about 2254 values.

Once the split passes, every remaining claim is a statement about a commitment, and chapter 08 and chapter 07 already counted those. A prover cannot avoid the registry either, since the verifier computes both m(w,x,y) and each si​(r,y) for itself. It cannot slip in a stray commitment, because every polynomial commitment the verifier reads gets opened through the IPA. The code's one exception is the nested challenge stage's partial commitment, which commits to no polynomial, and the bind_challenges claims hold it in place.

The argument above is informal, and it rests on these assumptions. Nobody knows a relation among the generators on either curve, so commitments bind. The two transcripts behave as random oracles, and each challenge comes after the commitments and values it tests. A false proof about a tree also has to get past the accumulation arguments of the earlier chapters. The book reports Lean proofs for some gadget contracts and says that these do not cover the protocol's overall soundness, which we do not establish here either.

16. Rerandomization and zero knowledge

A wallet would like two things from a proof beyond soundness. The proof should hide its witness, and when it reaches someone else it should not link back to the proof that produced it. As chapter 02 recorded, neither the Ragu Book nor the code at this commit establishes either property.

The code has an operation aimed at the second, rerandomize. It fuses the input proof as the left child with a cached proof of the unit header as the right child, under an internal step whose output header uses the same wires as the left input, so it cannot change the header. The result verifies as the input did. Both the book and the code say that unlinkability is the intention, and the comment on that function says that it is not achieved today. One of its reasons is that the fold is deterministic:

Reduce the fold to one commitment and let the output be Aˉout​=λ0​Aˉin​+λ1​Aˉseed​, with the public weights λ0​=16 and λ1​=22 and the cached seeded point (1,29). An input of (25,29) gives the output (24,30). An observer who wonders which proof went in can test each candidate against that equation. We tried all 42 points as candidates, and only the true input matches.

The comment's other reason is more direct, because the output holds the input's commitments and stage polynomials in the clear as left-child data. It lists the remaining work as a randomizer in the fold and a way of hiding the child data.

Hiding the witness has a similar status. The stages' random α is there to prevent cancellation and not for hiding, the IPA is unblinded at this commit, and the code says that a compressed proof is not hiding, because its openings are evaluations of the witness polynomials. To see what one evaluation can tell an observer:

Take the stage a(X) from chapter 17, where v=x2 and one random entry α sits at a0​. Among all 1,849 pairs (x,α), the commitment (22,24) matches 43. Adding one value at a point, a(17)=3, leaves 2 pairs, the true (3,10) and also (24,42), because the commitment and the value together give a quadratic equation in x. On a real curve nobody can list the commitment's matches, but the value still gives one linear equation in the secret wires, which is the sense in which the proof does not hide them.

This small case shows the effect of one value on one stage. A compressed proof opens combinations of polynomials at points the transcript chooses, so the leak there has a different shape, and we have not analyzed how much it reveals in practice. An uncompressed proof holds the polynomials themselves. The Tachyon book's description of stamps assumes zero knowledge, so this is an open requirement for Tachyon and not a settled property, as chapter 02 noted.

17. Performance against Orchard

The sizes above give a first comparison, which we can make from public formulas:

ZIP 225 gives an Orchard proof 2720+2272⋅n bytes for n actions, which is 7,264 bytes at n=2 and 25,440 bytes at n=10. So by our count a compressed Ragu proof with headers of 4 elements is about the size of an Orchard proof for two actions, and one Ragu proof can stand for many transactions. The exact match at n=2 is a coincidence of these parameters.

For verification time, the book's analysis chapter gives a rough model, and it states that the figures need checking against benchmarks. It puts Orchard's batch verification at about 10 to 15 ms per proof on one thread, scaling as roughly Ntx​/logNtx​. It estimates a Ragu proof at about 100 ms at K=11, driven mainly by an MSM of 213 points. Setting the two equal gives the crossover, with c the cost per proof:

logNtx​Ntx​×c​≈100 ms.

The book reports about 50 transactions with c=10 ms, and it does not state the base of the logarithm. We solved the equation at both ends of its range and for the two natural bases:

Cost per proof in the modellog=lnlog=log2​
10 msNtx​≈36Ntx​≈59
15 msNtx​≈20Ntx​≈34

The book's figure sits inside the range the model allows, and the range is wide, as one would expect of a rough estimate. The shape of the curves is the more reliable conclusion. Batch verification of independent proofs keeps growing with the number of transactions, and verifying one aggregated proof does not.

The parameter K gives a trade-off that the book leaves open. A larger circuit limit raises the verifier's cost for every proof and allows a shallower tree, and a smaller limit does the reverse.

The analysis chapter and the code

Book: the analysis chapter attributes the factor of 4 in 4N to "the circuit structure across multiple stages", and it describes one MSM of 213 points as the main cost.

Code at 3219e08: the number of coefficients is 4n, and chapter 04 derives that factor from the four wires of each gate, independent of stages. A compressed verifier runs the checks on each curve, with one MSM per curve and the registry evaluations of Section 13. We read this as more work than the book's single MSM counts, which would raise the crossover, but we did not measure it.

The site follows the code for the structure and quotes the book for the estimates. The code has benchmarks for compression and compressed verification, and we did not run them, so we report no timings of our own. The book lists further analysis as future work.

These figures describe verification only. They leave out the prover's costs, which the sources do not quantify, and they compare with Orchard as it runs today, whereas Tachyon changes what a transaction carries. Tachyon has not activated on mainnet, its design is still changing, and it pins a different Ragu revision from the one this chapter reads.

18. Fusing after compression

One part of this design is still moving, and later chapters depend on it. Chapter 02 recorded that the Ragu Book calls a compressed proof terminal, while the code's profile comments foresee an Aggregator profile that verifies a compressed proof in circuit, with an internal Decompress step that outputs its header. The code says this has not landed, and a test pins the aggregator's shape to the standard one until it does. Such a circuit would have to run the verifier of Section 13 with gates, which is why the design places it in extra circuits that only aggregators carry. The chapter on aggregation returns to this.

19. Where this chapter leaves us

This chapter took the following steps:

  1. A fuse runs one transcript through eleven phases, each commitment coming before the challenge that tests it, and it divides the checks among many small circuits because each has at most 211 gates.
  2. The replay takes more gates than one circuit holds, so hashes_1 and hashes_2 share it, with the sponge state held in the outer error stage and checked by the first circuit.
  3. The internal circuits expose one unified instance, with each slot owned by one circuit, so one k(y) serves them.
  4. Internal outputs end in zero and application headers end in a nonzero suffix, so the coefficient of Y keeps the two kinds of circuit apart.
  5. A parent folds three accumulators per side from each child, namely (A,B,c), (P,u,v) and m(W,x,y), so a proof keeps its shape at every depth.
  6. Compression replaces polynomials with commitments and values, through three reductions on each curve, from revdot claims to openings, from openings to one claim, and from that claim to an IPA.
  7. A wire binding turns the decider's comparisons of stage wires into revdot claims that a verifier can check from commitments.
  8. The compressed verifier pays the deferred linear costs once, as registry evaluations and one MSM per curve, and those costs do not depend on the tree.
  1. By our count a compressed proof takes 7,264 bytes, against more than 24,117,248 uncompressed. Rerandomization and hiding are not yet established, and the comparison with Orchard remains an estimate.

That completes Ragu as a proof system. The next part turns to what Tachyon proves with it, starting with notes, nullifiers, and tachygrams.

Last Updated: 11/4/26, 12:56 PM
Contributors: Hanh
Prev
Staging
Next
Notes, Nullifiers, and Tachygrams