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

Aggregation and Bundles

Chapter 2 followed one aggregate through its proof tree, treating the transactions around it as labelled boxes. This chapter opens those boxes. We look at what a Tachyon bundle contains on the wire, how a wallet commits to a fee before the fee is known, how merging and stripping change a bundle without disturbing what its owner signed, how identifiers survive those changes, and what an aggregator does when blocks land in the middle of its work.

The steps that produce a stamp have their internals in chapter 22, so here we use them by what they establish. We keep the bundles of chapter 2, an innocent aggregate A and three wallet bundles X, Y and Z, and give them real toy values.

Status of these designs

Tachyon has not activated on mainnet and its design is still changing. Everything here describes the Tachyon repository at commit 7e50254, which pins Ragu at 0725b52 rather than the 3219e08 used in the Ragu chapters. The bundle format ZIP and the aggregator protocol ZIP are empty outlines at this commit, so no normative specification exists. The book's own notes leave the tachygram union algorithm and the aggregation limits to be written.

As chapter 19 records, Tachyon builds against a mock of Ragu, so we describe what the proof steps declare and we did not find real circuits enforcing it. The crate also has no validator and no mempool. It has a Bundle::verify for one covering bundle, while block assembly, the window check, and transaction-level balance appear only in the book and in tests.

We follow the book's names "stamped" and "stripped" for the two bundle states. The code calls them ProofStamp and PointerStamp. We also say "action" and "stamp" for the book's "tachyaction" and "tachystamp". As in chapter 2, "fuse" names Ragu's operation, and "merge" appears only for what the Tachyon steps do to stamps.

1. What aggregation removes and what it keeps

A miner wants to check one proof in place of thousands, so we should ask which parts of a transaction exist only to serve its proof. Going through a bundle's contents with this question in mind gives a short answer.

Each action's signature authorizes that action, and no proof about other transactions can replace it. The value commitments and the binding signature show that one bundle balances, and that fact also belongs to the bundle. The tachygrams must stay public, because validators compare them against the two-epoch window and absorb their commitment into the anchor chain. That leaves the proof, plus a few values that serve only to check it.

The deep dive states the same conclusion. Balance, authorization, and the window check apply per bundle whether or not a bundle was aggregated, so aggregation reduces proof verification and changes nothing else that consensus checks. This gives us three requirements for the format:

  1. A bundle must be able to lose its proof without invalidating its signatures, so the signatures cannot cover the proof.
  2. A verifier must be able to find the bundles a proof covers, and must notice if one is missing.
  3. Something must still identify each bundle stably, while its proof changes hands.

Three names describe where a bundle can stand with respect to these requirements. An autonome comes from a wallet and carries a complete proof of its own actions. An aggregate comes from an aggregator and carries a merged proof that needs inputs from other transactions. An adjunct has been stripped by a miner, so it has no proof and it points to the aggregate that covers it. An aggregate with no actions of its own is an innocent aggregate, and one with actions is a based aggregate.

Here are our four bundles. They use small values so that every later equation fits on a line, and the columns on the right are explained in Section 3.

BundleSpendsOutputsmaxfeefeevbalChange note valueContribution vbal+fee
Anonenone00000
X25none5520025
Y302164327
Znone932-121-10

2. The bundle on the wire

The first byte, tachyonBundleState, selects among no bundle (0x00), stamped (0x01), and stripped (0x02). Any other value is rejected. A body follows, with the same layout in both states:

FieldSize in bytesMeaning
valueBalanceTachyon8net value of the actions, with the max fee note counted as an output
nActionsTachyoncompactsizenumber of actions n
vActionsTachyon64neach action's cv and rk, which together form its descriptor
cvFeeMaxTachyon32value commitment of the max fee note
vActionSigsTachyon64none signature per action
bindingSigTachyon64the binding signature
nMemoTachyoncompactsizememo length m, zero when absent
vMemoTachyonman opaque payload for the recipient

The trailer depends on the state, and it is where stripping happens. A stamped trailer holds the proof and what goes with it, and a stripped trailer holds a pointer:

Stamped trailerSize in bytesStripped trailerSize in bytes
vFeeTachyon8vFeeTachyon8
hStampCoverageTachyon32tachyonAggregateId64
anchorTachyon32
cTachygrams32
nTachygramscompactsize
vTachygrams32t
proofTachyonfixed

Two bars show a stamped and a stripped bundle. Both share the state byte and body, with the value balance, descriptors, max fee commitment and memo marked as effecting data, and the signatures marked as authorization data. The stamped trailer and the stripped trailer are both authorization data.

The figure separates the fields by how they are treated, because that division satisfies requirement 1 from Section 1. Effecting data states what the transaction does, and authorization data shows that it may do so. The signatures cover only effecting data, so everything in the trailer can change after signing. Section 7 builds the identifiers from the same division.

The memo sits on the effecting side for a reason that the book states and a test confirms. Relayers rewrite authorization data during aggregation, so a memo held there could be removed while the block still committed to the same transaction.

Bundle Y carries the memo first contact. Dropping it changes the bundle commitment from 27cd15dd to b9ca90b4, and the binding signature made over the first value fails against the second.

The reader also enforces a few rules that the book's table does not show. A stamped trailer must hold at least one tachygram, the list must be strictly increasing in its byte encoding, and repeats are rejected. A bundle with no actions must have a zero value balance, and the max fee commitment cannot be the identity.

3. Fees chosen after signing

Now we need to explain the fee fields, which exist because of the following problem. A signature over a fixed fee would fix the fee at signing time. A wallet may sign on a hardware device and prove elsewhere, and the right fee may only be known when the transaction goes out. The Tachyon answer is that the wallet signs a maximum once and picks the actual fee later, with the difference coming back as a new note.

The max fee note

To reserve the maximum, the bundle counts it as one more output, so that the signed balance already accounts for it. Every bundle carries a max fee note, an opening (pk,maxfee,ψ,rcm) with the same fields as any note. It is never spent, and the bundle publishes only its value commitment,

cvmaxfee​=[−maxfee]V+[r]R,r=PoseidonTachyon-FeeBlind​(pk, ψ, rcm).

A blind normally comes from fresh randomness. This one comes from the owner fields, so signing cvmaxfee​ also fixes who will own the change. A prover who substitutes a different pk gets a different r, and therefore a different commitment from the signed one, unless it can find a collision in Poseidon.

The value balance counts the reservation as an output:

vbal=∑vin​−∑vout​−maxfee.

The binding signature

A validator cannot see the values, so it has to check this equation on commitments. An action's value commitment is cvi​=[vi​]V+[rcvi​]R, where vi​ is positive for a spend and negative for an output. Chapter 22 shows how the proof ties these to notes. The validator's key adds all the commitments and removes the public balance:

bvk=i∑​cvi​+cvmaxfee​−[vbal]V.

Expanding the commitments and collecting the two generators gives

bvk​=i∑​([vi​]V+[rcvi​]R)+[−maxfee]V+[r]R−[vbal]V=[i∑​vi​−maxfee−vbal]V+[i∑​rcvi​+r]R.​

The first bracket is zero exactly when the claimed balance is right, since ∑i​vi​=∑vin​−∑vout​. In that case bvk=[bsk]R with bsk=∑i​rcvi​+r, and the planner, who knows every blind, can sign with bsk as a key. The signature proves knowledge of that key. Neither bsk nor bvk mentions the fee, so a signature made now stays valid at any fee chosen later.

We write V=(26,8) and R=(23,7) for the toy value commitment generators, and values and blinds are scalars in F43​.

Bundle Y spends 30, creates an output of 21, and reserves a maximum of 6, so vbal=30−21−6=3.

TermFormulaPoint
cv of the spend[30]V+[16]R(6,23)
cv of the output[−21]V+[15]R(26,23)
cvmaxfee​[−6]V+[5]R(14,9)
bvksum of the three, minus [3]V(17,7)
[bsk]Rbsk=16+15+5=36 modulo 43(17,7)

The last two rows agree, so the planner's key matches what the validator derives. Here is what happens if the claimed balance is wrong.

Suppose a bundle claims a value balance of 4 while the committed values still give 3. The validator computes bvk′=(8,9), which is [bsk]R−V, and the true key 36 no longer matches it. On our toy curve the matching key is 3, found by counting. Finding it requires the discrete log of V with respect to R, which on a real curve nobody can compute.

This rests on the assumption that the discrete log between the two generators is unknown. The real generators are shared with Orchard and come from hashing to the curve, and a book footnote suggests considering a separate personalization.

The fee change note

Later, a prover picks fee≤maxfee. The unused part of the reservation has to stay in the pool, so the proof creates a fee change note (pk,maxfee−fee,ψ,rcm), which differs from the max fee note only in its value. It is published only as its note commitment cmchg​, which is a tachygram. A fee equal to the maximum leaves a change note of value zero, and that note still publishes its tachygram, so the tachygram count does not show whether any change came back. The fee itself is public in the trailer. Bundle X in our table is this case.

We can now find what a bundle contributes to its transaction. The pool loses the spends and gains the outputs and the change:

net outflow​=∑vin​−∑vout​−(maxfee−fee)=(∑vin​−∑vout​−maxfee)+fee=vbal+fee.​

That is why the wire format defines the contribution as valueBalanceTachyon + vFeeTachyon. For Y at fee 4, the pool loses 30−21−2=7, which equals 3+4. Three of these leave to another pool and four pay the fee. A transaction's pools must then sum to a non-negative total, which the book assigns to transaction accounting outside the bundle.

Choosing the fee

The fee reaches the proof through a fee descriptor, the pair (cvmaxfee​,fee). Its Poseidon digest is

dfee​=PoseidonTachyon-FeeDg​(x(cvmaxfee​), y(cvmaxfee​), fee).

The step FeeFuse takes the bundle's Actions header and a witness holding the max fee note opening and the fee. It derives r, then cvmaxfee​, then dfee​ and cmchg​, and it adds these last two to the sets that the following section describes. Its output is a Stamp. Since the step accepts only Actions as input, it cannot apply twice to one lineage, which is the suffix idea from chapter 2. A bundle with no actions uses FeeSeed, which has unit inputs, requires maxfee=0, and uses fee 0.

These steps prove a local relation and not funding. The funding comes from the actions, the binding signature, and transaction accounting. Here is bundle Y proved at three fees. We use the stand-in hash from chapter 20, SHA-256 reduced modulo 43, for each Poseidon value, because the digests must be roots of polynomials over F43​. The two hex columns are the first bytes of real BLAKE2b-256 digests over toy encodings, which Section 7 defines.

feeChange note valuecmchg​dfee​ContributionBundle commitmentauth_digest contribution
061835327cd15dd5fd9be06
421236727cd15dd110857ce
60712927cd15dd8de8c40d

The bundle commitment stays the same in all three rows, and a test asserts the same thing on the real byte formats, along with an unchanged binding signature. So the fee can move without any new signature, but changing it rebuilds the stamp, so only a party holding the max fee note opening and the action proofs can do it. The sources do not describe a protocol for negotiating a fee between a wallet and anyone else.

The range check in FeeFuse deserves a closer look.

Our toy range check accepts 7 of the 43 field values of the fee for bundle Y, namely 0 to 6. The check is needed because field arithmetic would otherwise wrap around. A fee of 7 gives a change value of 6−7≡42 modulo 43. Fee and change then still sum to 7+42≡6=maxfee in the field, so the sum looks balanced, although the integers 7 and 42 are worth 49 in total against a reservation of 6.

The step rejects this by requiring maxfee≤MAX_MONEY and maxfee−fee∈[0,MAX_MONEY]. A real field is far larger, but it wraps around in the same way, and a dishonest prover can pick the fee freely, so the check is needed there as well. Under the mock this check runs natively on integers, and a real circuit would have to constrain it.

Each max fee note needs a fresh rcm, because reusing an opening reuses r. Reusing an opening at the same fee also repeats the fee digest, which consensus rejects, and a test checks this for a forged stamp that covers two such bundles.

4. What a stamp covers

Each stamp's header, as chapter 2 described, commits to an action set and a tachygram set. We can now list what these sets hold. For a stamp covering N actions in B bundles:

  • The action set holds one digest per action and one fee digest per bundle, which makes N+B members. An action's digest is di​=PoseidonTachyon-ActionDg​(cvi​,rki​).
  • The tachygram set holds two tachygrams per action and one change commitment per bundle, which makes 2N+B members.

Each set is encoded as a polynomial with its members as roots, and it is committed exactly as chapter 20 did for tachygrams:

action_acc=Commit(i∏​(X−di​)j∏​(X−dfee,j​)),tachygram_acc=Commit(k∏​(X−tgk​)).

The action set must ignore order because merging combines stamps from unrelated wallets in any order, and the polynomial gives this for free. It sits alongside the ordered BLAKE2b digest of the actions, which serves the signatures, and the book stresses that the two digests are not interchangeable.

Here are the four stamps for our bundles before any merge, all at one common anchor α. As in chapter 2, we assume the older stamps have already been lifted to it. The polynomials live over F43​ and the commitments use the generators of chapter 20, extended to thirteen points from the curve's list.

BundleAction digestsTachygramsaction_acctachygram_accCoverage indicator
A{13}{38}(20,6)(30,23)921fc7d3
X{34,9}{26,33,37}(4,25)(17,24)1bce90fd
Y{4,21,36}{10,12,22,25,31}(16,10)(18,21)09d993b0
Z{31,25}{14,23,27}(20,25)(11,30)79ece152

The sizes match chapter 2. A has one digest and one tachygram, which come from its fee alone. X and Z have two and three, and Y has three and five.

5. Merging stamps

An aggregator's process starts by choosing transactions and validating their stamps. It aligns their anchors with StampLift, as chapter 20 explained, and then it applies StampMerge pairwise. An innocent aggregate proves its own zero fee first with FeeSeed and merges the others into that stamp. Finally it serializes the merged stamp and publishes its bundle as a new transaction.

The merge needs no secrets from the wallets. ProofStamp::merge takes the two stamps and their covered descriptors, all of which the aggregator holds from the mempool. It outputs a new proof, the union of the tachygrams, a new coverage digest, and the fee of the left stamp. This choice of fee matters, because the merged stamp stays on the receiving bundle and keeps that bundle's fee. The other bundles carry their own fees separately once stripped, so the fee digests they contributed are still covered.

Running the merges on our stamps gives the following table. Each check is the product test of chapter 20, evaluated on the tachygram polynomials at a stand-in challenge, with products taken modulo 43.

MergeOutput action set sizeOutput tachygramsaction_acctachygram_accCheck at rTrailer fee
A with X34(24,30)(28,21)r=31: 36⋅17=100
previous with Y69(25,29)(24,30)r=20: 3⋅15=20
previous with Z812(7,25)(1,29)r=36: 9⋅37=320

The commitment column does not grow, which is the point of committing. The last row covers 8 digests and 12 tachygrams with two curve points and an anchor, and the same fixed-size proof as a one-action autonome.

Repeated values

Our Join rule in chapter 2 rejected a repeated entry on its own, and chapter 2 noted that StampMerge has no such check. A code comment confirms that the step proves a multiset union, and a dishonest prover can fuse overlapping stamps. The honest path in the crate cannot do this, because it builds the merged polynomial from the set union, which is not the product when the inputs overlap, so its product check fails. The defence against a dishonest prover comes from what the verifier compares.

Suppose a wallet publishes a second bundle X$'$ that spends the same note as X with a fresh θ. Its descriptor and fee note differ, but it shares the nullifiers 26 and 33 with X. A forger can still fuse A, X and X$'$, because the product polynomial has degree 7 with double roots at those two values, and the step checks only the product. Its header holds the commitment (9,9). The wire format forces the published list to be unique, so the list has 5 entries. The arity rule asks for 2⋅2+3=7, so the first check fails. Recomputing from those 5 values gives (28,10), which also misses the header.

This is why the book can call the second gate of Section 8 an exact test. Every action and bundle contributes a fixed number of tachygrams, and a published list has no repeats, so a stamp whose inputs shared a value always comes up short.

Decompression

We have to be careful about what the aggregator actually fuses, because the sources disagree here, as chapter 2 recorded. A stamp on the wire holds a proof in compressed form, for bandwidth. A step takes uncompressed children. Something must connect the two.

Sources disagree: how an aggregator fuses a published stamp

Tachyon book: aggregators decompress proofs to a mutable format, merge them, and recompress before broadcasting. Its notes add that serialization is expensive and that a stamp can only change while decompressed, and they ask whether the cost grows with size.

Ragu Book at 3219e08: compression is terminal, so a compressed proof can be verified and cannot be fused further.

Ragu code at 3219e08: the profile comments describe a planned design that differs from a format conversion. An Aggregator profile would add circuits that verify a wallet's compressed proof inside the circuit, so that the proof enters the aggregator's fuse as witness data. An internal Decompress step would then output its header. Those circuits have not landed, and the comment says they will need a larger rank.

Ragu at 0725b52, which Tachyon pins: the real crate has compress and we found no Decompress step or profile module. The mock that Tachyon actually builds against has seed, fuse, verify, and rerandomize, and no compression.

Tachyon code at 7e50254: the mock has one proof type, whose serialization pads it to a constant named PROOF_SIZE_COMPRESSED and parses back to the same type. So a stamp read from the wire can be merged directly. This works only because the mock has nothing to decompress.

This site follows the Ragu code. A compressed proof cannot be fused today, decompression is planned, and our examples merge stamps while they are uncompressed. The wire format and the merge run together in the Tachyon crate only because of the mock. The cost of the aggregator's real work, including its effect on proof size and on the rank, is therefore not yet known.

The same limitation reaches StampUnmerge in Section 11, since its divisors also arrive from the mempool in published form. That is our inference, because the sources discuss decompression only for merging.

Rerandomization and hiding

Each stamp-producing operation in the crate rerandomizes its proof before release, and we confirmed this at the end of merge. The book's reason is that an aggregator sees its inputs, and its output should carry no link back to them. As chapter 2 records, the Ragu book and code at 3219e08 say the construction does not yet establish zero knowledge or unlinkability. So we treat this as an intended property that the proof system has not yet delivered. It also affects what an aggregator could learn from the stamps it merges.

6. Stripping

Once a merged stamp exists, its covered bundles no longer need their own stamps, but they do need to reach the chain. A miner authoring a block replaces the stamp of each covered bundle with a pointer to the aggregate. The operation is small, and the code keeps every field of the body and replaces the stamp with the aggregate's identifier and the bundle's fee.

Comparing the two trailers from Section 2, the following values go away: the coverage indicator, anchor, tachygram commitment, tachygram list, and proof. The tachygrams have not been lost, since the aggregate's stamp holds them in its own list. Stripping keeps the fee in the trailer for a reason. The fee is part of the transaction's balance, and it is an input to the fee digest that the covering proof binds.

Book and code: who strips

The bundle page says aggregators strip and merge. The aggregation page and the aggregator guide assign stripping to miners, with aggregators publishing complete autonomes and aggregates. In the code, strip is a method on a bundle that anyone can call. This site follows the aggregation page, which also matches the logic, since only a block author knows which aggregate ends up in the block.

An innocent aggregate can be stripped as well, if another aggregator merges it into something larger. It then contributes no actions, and it still names its covering aggregate and keeps its fee descriptor and binding signature. The value zero is not a valid pointer, so the reader rejects an all-zero identifier, which leaves the state byte as the only way to say "no stamp".

Sources differ: what an innocent aggregate is

Deep dive: a purely aggregating bundle has an empty action list, a zero balance, and a trivial binding signature.

Fee and aggregation pages, and code at 7e50254: an innocent aggregate has its own max fee note, and its stamp starts from FeeSeed. So its bvk=cvmaxfee​=[r]R is an ordinary key, and it publishes one tachygram and one fee digest, and a test merges and strips such a bundle.

This site follows the code. Bundle A in our example has these values.

7. Transaction identifiers

We now have a bundle that exists in several physical forms: as an autonome, at a different fee, stripped with one pointer, or stripped with another. These forms need one stable name, so that a wallet can recognize its transaction. They also need to be told apart, because they are different bytes. Zcash's ZIP 239 already has this split, with wtxid = txid || auth_digest, and Tachyon routes each field to one half.

The first half covers effecting data only. Tachyon's contribution to txid, which the code calls the bundle commitment, is

BLAKE2b-256ZTxIdTachyonHash​(hActionsTachyon∥valueBalanceTachyon∥cvFeeMaxTachyon∥hMemoTachyon),

where hActionsTachyon hashes each cvi​∥rki​ in wire order and hMemoTachyon hashes the memo to a fixed width. Every signature signs a sighash built from this value. The second half covers what can change. Tachyon's contribution to auth_digest is

BLAKE2b-256ZTxAuthTachyHash​(tachyonBundleState∥vActionSigs∥bindingSigTachyon∥tachyonStampState).

The last input is 72 bytes in both states. A stamped bundle uses its fee, the coverage indicator, and hStampDataTachyon, a digest of the proof's digest, the anchor, cTachygrams, and the tachygrams. A stripped bundle uses its fee and the 64-byte tachyonAggregateId. The book says that the personalization ZTxAuthTachyHash is a placeholder until an amendment to ZIP 244 fixes it.

Here are forms of A and Y in our example. We compute real BLAKE2b-256 with these personalizations, over toy encodings of the points, field elements and signatures. A real txid and auth_digest would hash these contributions together with those of other pools, and we leave that step out.

Physical formState byteBundle commitmentauth_digest contribution
A with its FeeSeed stamp0x010a280ad91a82caa9
A with the merged stamp over A, X, Y and Z0x010a280ad90343b231
A stripped, pointing to a larger aggregate on T0x020a280ad970dbdc11
Y as an autonome at fee 40x0127cd15dd7d693073
Y as an autonome at fee 30x0127cd15dd0157e0a3
Y stripped, pointing to A with the merged stamp0x0227cd15ddb187f946
Y stripped, pointing to A with a stamp that covers only A, X and Y0x0227cd15dd459ecaa1
Y stripped, pointing to the aggregate on T0x0227cd15dd1e19351f

In each bundle, the third column stays fixed and the fourth never repeats. This table also shows why an adjunct points to a wtxid rather than a txid. The first two rows have the same bundle commitment, and only one of them is a stamp that covers Y. A pointer to the first half alone could not say which stamp it means, and a stamp that covers less would not validate the adjunct.

No circular dependency arises. An adjunct's identifier depends on the aggregate's wtxid, while the aggregate's wtxid depends only on its own fields and its stamp's digests. Neither depends on the pointers. So a miner can finish the aggregate first, then assign the references. Nor do pointers need anyone's signature, because they sit in the trailer. The covering aggregate must be top-level in the block, never stripped and never covered in turn, which keeps the pointed-to wtxid stable. So a stack of aggregates flattens when it reaches a block, and in the table both A and Y can point directly to the aggregate on T.

Sources differ: what the identifiers contain

Deep dive: txid commits to (action_acc∥vbal∥da_digest), which uses the polynomial commitment to the actions.

Identifier and authorization pages, and code at 7e50254: the formula above, with the BLAKE2b digest of the actions, the max fee commitment, and the memo digest.

A smaller point in the code: AggregateId names the first half of its constructor's input sighash, while the book and a test comment say txid and note that the two coincide only for fully shielded transactions.

This site follows the identifier page. The book's link to a "Block Layout" section points to a heading that the aggregation page does not have at this commit, and we found no specification of how the bundles of a block are ordered or how references get resolved.

8. Finding out what a stamp covers

A stamp carries no proof header. This creates a problem before a transaction reaches a block. Within a block, a verifier collects the bundles that point to an aggregate and rebuilds the header from them. In the mempool, nothing connects an aggregate to the bundles it covers, and the only way to test a guess at them would be a full proof verification.

The action set indicator hStampCoverageTachyon makes this test cheap. It is a BLAKE2b digest that the transaction's author supplies, built in two layers:

hStampCoverageTachyonhStampActionsTachyonhStampFeesTachyon​=BLAKE2b-256Tachyon-Coverage​(hStampActionsTachyon∥hStampFeesTachyon),=BLAKE2b-256Tachyon-Actions​(a1​∥⋯∥aN​),ai​=cvi​∥rki​,=BLAKE2b-256Tachyon-Fees​(f1​∥⋯∥fB​),fj​=cvmaxfee,j​∥LE64(feej​).​

Each sub-digest takes its entries in sorted byte order, so that the digest depends only on the set. The indicator has a side benefit, which is that it distinguishes an autonome from an aggregate. The code's test asks whether the digest matches the bundle's own descriptors alone.

The indicator is not a soundness mechanism. It is a claim by the author, and it strips away with the rest of the stamp. The proof binds the action set separately. A wrong indicator can only harm its author, because others will decline to handle the transaction. So consider what a verifier does with a candidate collection of bundles. It applies two fast gates before the proof: compare the indicator, then compare the tachygram count with 2N+B. Here are the candidates for the merged stamp on A, which has 12 tachygrams.

Candidate adjuncts with ANB2N+BIndicatorArity (12)Proof header
none011failsfailsfails
X124failsfailsfails
Y226failsfailsfails
Z124failsfailsfails
X, Y339failsfailsfails
X, Z237failsfailsfails
Y, Z339failsfailsfails
X, Y, Z4412passespassespasses
X, Y, Z$'$ (a different one-action bundle)4412failspassesfails
X, Y at fee 3 in its trailer, Z4412failspassesfails

The script enumerates every subset of the three other bundles, and only the full set passes. The last two rows show why two gates and a proof are not redundant. A bundle swapped for another of the same shape passes the arity gate, since arity only counts, and so the indicator has to catch it. If an author forged the indicator as well, the proof would still fail on the rebuilt header.

Verifying a block's aggregate

We can now write down the full check in the order of Bundle::verify, and run it on our block. A validator handles each adjunct's signatures in the same way, and the crate checks those separately, since this method verifies no details about the adjuncts beyond their pointers and descriptors. Our first row counts all four bundles.

CheckInputsResult on our block
Signaturesevery bundle's action and binding signatures8 signatures verify
Pointersthe 3 adjuncts' tachyonAggregateId against the wtxid of Aall equal 0a280ad9…
Coverage4 action descriptors and 4 fee descriptors, none repeatedthe digest 4c2ec215 matches the trailer
Tachygram count2⋅4+412 published
Tachygram setcommit to the published list(1,29) equals cTachygrams
Proofheader ((7,25), (1,29), α)the header matches the one the proof was made for

The proof verification reads the carried tachygram commitment and not the list, which is why the tachygram set row has to come first. Chapter 20 described the differing accounts of that comparison, and we follow the code. The checks that this method does not make are the anchor lookup, the epoch rule, the tachygram window, and the absorption, all of which chapter 20 listed.

The fee descriptors also pass through this check, so we can confirm that aggregation changes nothing about who pays.

The merged stamp on A states a trailer fee of 0, the adjuncts state 5, 4 and 2, and their fee descriptors are all covered by the one proof. The bundles therefore pay 11 in total, the same as if each had been mined alone.

A miner cannot quietly adjust these amounts.

Suppose a miner rewrites the trailer of the adjunct Y from fee 4 to fee 3, which is still within the signed maximum of 6. The bundle commitment does not change, because the fee is not effecting data, so the signatures still verify. The fee digest changes from 36 to 30, so the rebuilt action set has a different root, and its commitment becomes (27,1) in place of the header's (7,25). The proof cannot verify against that header.

9. What aggregation saves

The wire format lets us count bytes. The size of a real proof is not settled, so we use the mock's PROOF_SIZE_COMPRESSED of 23,000 bytes as a placeholder. A comment in Ragu at 3219e08 describes a compressed proof as a few kilobytes, so these numbers probably overstate the proof's share. They all assume empty memos.

Bundle shapeStamped size in bytesStripped size in bytes
0 actions, carrying 12 tachygrams (the merged stamp on A)23,596179
1 action, 3 tachygrams (X and Z)23,436307
2 actions, 5 tachygrams (Y)23,628435

Published separately, X, Y and Z take 70,500 bytes with 3 proofs. Published as the aggregate A with three adjuncts, the same three transactions plus A take 24,645 bytes with 1 proof. The saving grows with the batch, which we can see on hypothetical inputs. 1,000 two-action bundles take 23,628,000 bytes separately, and 618,246 bytes as one innocent aggregate and 1,000 adjuncts, which is 618 bytes per bundle in place of 23,628. The proof falls from 1,000 copies to 1, while the 5,000 tachygrams and 2,000 action signatures all remain.

The bytes that remain have a floor, since each action keeps its 128-byte descriptor and signature and adds 64 bytes of tachygrams. Aggregation removes the proofs and a few short fields, and it adds a 64-byte pointer to each bundle. So the byte savings per bundle depend on the proof size. The verification savings do not, because a block's validators check one proof in place of one per bundle, and they absorb one commitment into the anchor chain per aggregate and not per transaction.

There is a limit to how much one aggregate can hold, and it comes from the commitments rather than the block size, as the book's notes say. The reader rejects a list whose polynomial would have more coefficients than the rank allows.

At the production rank a polynomial has 213=8,192 coefficients, so a stamp can hold at most 8,191 tachygrams. Bundles of two actions contribute 5 tachygrams each, so one aggregate covers at most 1,638 of them, or 3,276 actions.

This agrees with the book's "on the order of thousands of actions". The rank comes from Ragu, and the comment on decompression suggests that an aggregator may eventually work at a larger one, so this limit is also unsettled.

10. The aggregator's role

Tachyon adds one role to the usual transaction path. The aggregator takes transactions from the mempool and returns aggregates to it:

The book expects miners to integrate this role, and it does not treat the peer-to-peer version as settled. Its notes ask whether aggregates have optimal qualities, whether miners will rely on the network at all, and whether they would pick aggregates first or transactions first. They also record that merging into one aggregate cannot be parallelized while several aggregates can be built side by side, and that merging a small autonome and a large aggregate cost about the same.

Everything else in this section comes from the aggregator guide, which is advice rather than consensus rules. Its starting point is that several aggregators can work on the same autonomes and blocks keep landing, so the work has to survive competition.

The anchor comes first. An aggregator works at a current-epoch anchor and lifts every input to it. Lifts cannot cross an epoch boundary, and a stamp anchored in epoch e cannot be published after epoch e+1, as chapter 20 explained. So an aggregate started in one epoch is finished within the next, or it is rebuilt.

Inputs have to be disjoint. It compares the tachygrams of its candidates with each other and with its working stamp, and it merges a disjoint set. It should keep the stamps of candidates that overlapped, because a block may include one of those instead.

Its reasons to aggregate are partly its own. A bundle of the aggregator's own gets mined along with what it covers, and an aggregate that carries finished proving work is more attractive for the next party to take on. We return to payment in Section 12.

Proving work should stay reusable. Suppose an aggregator holds the stamp of A after the merge, and the stamps of X, Y and Z. Dividing out the last three gives a stamp for A's own actions and fee, even though A's builder never published one. The guide calls this intended, because the signed actions are unchanged and the builder wanted them mined. The builder keeps no ownership of the grouping.

The guide calls an aggregate transparent to a participant who holds or can derive a stamp for each of the other bundles it covers, at the fee the aggregate selected. Such a participant can split the aggregate into any subset without asking the builder. The guide recommends that aggregators keep their inputs and merge order and cache recent mempool stamps until the working aggregate expires, and that they publish only complete autonomes and aggregates, since a released intermediate is a divisor someone else can use.

A builder could instead hold back a component proof, so that it alone can split its aggregate. Against this, the guide suggests a social rule. Aggregators merge another's aggregate only if it is transparent to them, or if they are willing to drop it whole. Then the cost of withholding falls on the party doing the withholding.

11. When a block lands

Suppose our aggregator is holding the merged stamp, which we call W, and a new block includes X on its own. The stamp of W repeats X's tachygrams, and consensus rejects a tachygram published twice within two epochs. So publishing W now would fail. Rebuilding from the remaining inputs would discard finished work, and we want to keep the proof of the rest.

The step StampUnmerge removes one stamp from another. It has the shape of a merge run backward. Its witness holds the quotient polynomials, and it enforces that each dividend polynomial is the product of the divisor's and the quotient's, at one common anchor. Each set polynomial is a product of linear factors, so unique factorization makes the division exact, and the divisor's factors are a sub-multiset of the dividend's. As chapter 2 observed, the output header may state less than the input, and that is acceptable because a step need only establish the relation it declares.

Dividing the tachygram polynomial of the merged stamp, which has degree 12, by that of X, which has degree 3, leaves remainder zero and a quotient of degree 9. Dividing the action polynomials takes degree 8 to degree 6, also with remainder zero. The quotient stamp has the header ((18,10), (28,21), α), which is the same header we would get by merging A, Y and Z directly. At the challenge r=30 the tachygram check reads 5=41⋅19 modulo 43. The trailer fee stays at 0, which is A's fee.

The quotient replaces the stamp on A, and building continues. Finding what to remove takes some care, because three comparisons are needed. Action descriptors show which of W's bundles the block includes. Fees must be compared separately, because a block may carry a bundle at a different fee. Tachygrams must also be compared, because a conflicting spend of one note has a different descriptor and the same nullifiers.

The fee case is where the exact-divisor rule applies. What has to come out is the bundle as it was merged, at the fee W holds.

Suppose instead that the block carries X at fee 4, while the merged stamp holds X at fee 5. The block's stamp has the fee digest 2 and the change commitment 39 where ours has 9 and 37. Dividing by it leaves a nonzero remainder of degree 1 on the action polynomial and of degree 2 on the tachygram polynomial, so no quotient exists. The two versions of X still share the nullifiers 26 and 33, so the merged stamp cannot be published either. Dividing by the cached fee 5 stamp of X removes X and gives the quotient above, and the fee in the block plays no part.

Three cases

The guide sorts all situations by one question, which is whether a stamp in hand or in the block covers exactly the bundles to remove. If one does, the aggregator divides once, which the guide calls the easy case. If none does, but one can be built by merging stamps held or derivable, it calls the case medium. Otherwise the case is hard, and the aggregator gives up a larger input that contains the mined bundles, or it rebuilds. Here is the medium case on our stamps.

Suppose the block includes an aggregate on a bundle T, covering T, X and Y, and T is not in our stamp. Dividing by T's stamp fails, because it has the factors of T and our stamp does not, which leaves a remainder of degree 6 on the action polynomial. Merging our cached stamps of X and Y gives a divisor that covers exactly what has to come out, and dividing by it leaves a stamp with 3 digests and 4 tachygrams, covering A and Z, with header ((16,21), (7,6), α). The other route merges a stamp of T into our stamp first and then divides by the block's stamp. It reaches the same header.

We condense the guide's table below:

Merged into WBlock includesCase
autonome XX as an autonomeeasy, divide by X
autonomes X,Y,Zan aggregate covering exactly thoseeasy, divide by that aggregate
aggregate GG, unchanged or inside a larger aggregateeasy, divide by G
aggregate Gone of G's adjuncts as an autonomeeasy, divide by that autonome
autonomes X,Yan aggregate on T covering T,X,Ymedium, merge X and Y, then divide
aggregate G covering G,X,Y,ZG regrouped with X and another bundlemedium if Y and Z are cached, hard otherwise
aggregate Gsome of G's bundles, with a needed stamp withheldhard
anything anchored in epoch ea block past the end of epoch e+1W is dead, so rebuild in the new epoch

The guide's order of preference is an exact divisor, a built divisor, and then dropping a larger input, with a rebuild whenever that costs less. Its regrouped row is the most instructive. If G's builder kept working and the block includes G's bundle with a new set of adjuncts at a new fee, the aggregator divides its kept G stamp by the cached stamps of Y and Z. The result covers G's bundle at its old fee and X, which is exactly what has to come out of W, and the new fee does not enter. The code also has a test of a divisor that crosses merge boundaries in this way.

Losing the receiver

The quotient keeps the dividend's fee, as unmerge says, and this has a consequence when the receiver is what the block mined.

Suppose the block included A on its own. Dividing A out leaves a stamp covering X, Y and Z, with 11 tachygrams. Its trailer fee is still 0, while the fees it covers are 5, 4 and 2. Placed on X, the verifier would build the fee descriptor of X with fee 0, and the indicator check fails. Merging the stamp into a new bundle B with its own fee of 1 gives a stamp whose receiver is B, with trailer fee 1 and 14 tachygrams, and it covers B, X, Y and Z.

The guide's remedy is exactly this merge into a bundle outside the quotient.

At the miner

A miner's task is short by comparison. It includes eligible disjoint aggregates as they are, with every bundle they cover stripped to point at them. Where two candidates overlap, it removes the overlap from one of them with cached stamps instead of rebuilding either, and it can spend the saved proving time on an aggregate of its own.

12. Fee levels and payment

Everything in Section 3 concerned how a fee is bound and balanced. It left open how large a fee should be and who receives what. On the first question, the book's ZIP 317 page says that how to define Tachyon's contribution to the conventional fee is under discussion and nothing has been decided. It links a draft in the ZIPs fork, whose pull request GitHub showed as closed without merging when we checked, so we treat the draft as a record of one proposal and not as the design.

That draft sets contributionTachyon​=nActionsTachyon for every bundle type, whether autonome, aggregate or adjunct. Its argument is that a transaction's fee should remain a function of its own public fields, with no knowledge of which aggregate covers it. It rejects a lower price for adjuncts because that would shift cost onto the aggregate. Applying ZIP 317's standard formula of 5,000 zatoshi per logical action with a grace of 2 gives the following amounts. These are conventions for wallets and not consensus rules.

BundlenActionsTachyonLogical actions, ignoring other poolsConventional fee in zatoshi
A (innocent)0010,000
X1110,000
Y2210,000
a bundle of 5 actions5525,000

An innocent aggregate has maxfee=0, so its Tachyon bundle can pay nothing, and any fee would need to come from another pool in its transaction. The sources do not address this directly.

On the second question, a bundle's fee enters its transaction's balance like any other Zcash fee, and we found no mechanism in the sources that pays an aggregator from it. The draft explicitly leaves the aggregator's proof work unpriced and calls it a market and protocol-design concern. Together with the guide's reasons from Section 10, this suggests that aggregation may be funded by vertically integrated miners and by aggregators with bundles of their own, though no source makes this into a design commitment.

13. Why cheating fails

The table below collects the attacks from this chapter. Each row assumes the proof system's soundness, and the mock means that the relations are declared and not yet enforced by real circuits. We also assume that BLAKE2b and Poseidon resist collisions and that the discrete log between V and R is unknown.

A cheater tries toWho triesWhat stops it
Claim a false balancewalletbvk keeps a V term, so no key is known (Section 3)
Set a fee above the maximumproverThe range check in FeeFuse (Section 3)
Redirect the fee change noteproverThe blind r depends on the owner fields, so cvmaxfee​ would differ from the signed value (Section 3)
Apply a fee twiceproverFeeFuse accepts only Actions, and consensus rejects a repeated fee digest (Section 3)
Rewrite an adjunct's feeminerThe fee digest changes, and the rebuilt header fails (Section 8)
Drop a memorelayerThe bundle commitment changes, so the signatures fail (Section 2)
Leave a covered bundle out of a blockminerThe rebuilt header differs from the proof's (Section 8)
Point an adjunct at the wrong aggregateminerThe pointer check fails, and the adjunct's descriptors would be missing from the right header (Section 8)
Merge stamps that share a tachygramaggregatorThe arity check or the recomputed commitment fails (Section 5)
Publish a false indicatorauthorOthers decline the transaction, and the proof binds the set regardless (Section 8)
Remove a bundle that was never merged inaggregatorNo quotient exists (Section 11)

Aggregators and miners hold no special power in this table, because everything they can change sits in authorization data that the proof and the rebuilt header check. An aggregator can choose what to merge, withhold component proofs, and fail to publish. These are questions of availability and incentives, so the guide treats them with conventions and not with cryptography. Forged stamps are also caught in tests, with the caveat that the mock proof can only show a mismatch of headers.

Reconstructed from code at commit 7e50254, not confirmed by the authors

The order of checks in Section 8 comes from Bundle::verify, and combining it with the adjunct and consensus checks is our reading. The claim that the block's other bundles get their signatures checked in the same way comes from the deep dive, since the crate has no block validator. The observation in Section 7 that pointers create no circular dependency is our own reading of the digest formulas.

14. Where this chapter leaves us

This chapter took the following steps:

  1. Aggregation removes proofs and leaves signatures, value commitments, fee descriptors, and tachygrams in place, since those check facts about each bundle.
  2. A bundle on the wire has a common body and one of two trailers. Effecting data states what the transaction does, and the signatures cover only that. Everything that aggregation changes sits in authorization data.
  3. A wallet signs a maximum fee through the max fee note's commitment. The binding signature balances against that maximum, and a proof later picks the fee and returns the difference as a fee change note that always publishes one tachygram.
  4. A stamp's action set holds action and fee digests, and its tachygram set holds 2N+B values. Merging multiplies these polynomials, keeps the receiver's fee, and needs no wallet secrets.
  5. Decompression remains a gap. The Tachyon book assumes it, the Ragu code plans it through in-circuit verification and has not landed it, and the Tachyon crate avoids the question by using a mock proof.
  6. A miner strips covered bundles, leaving each a fee and a pointer to the aggregate's wtxid. The txid half stays stable through every form, and the auth_digest half tells the forms apart.
  7. The coverage indicator and the tachygram count give two cheap checks before the proof, and the proof remains the check that matters. It runs against a header that the verifier rebuilds from the bundles it can see.
  8. Aggregators work within an epoch's deadline, merge disjoint inputs, and keep their stamps so that they can divide out whatever a block has mined.
  9. Fee levels and aggregator payment are unsettled, and the bundle and aggregator ZIPs are still empty outlines.

This completes the path of a transaction, from a note's keys to a block. The next chapter, Final Recap, traces one spend through every layer of the site, with a link to the chapter that explains each layer.

Last Updated: 11/4/26, 12:56 PM
Contributors: Hanh
Prev
The Proof Tree
Next
Final Recap