Notes, Nullifiers, and Tachygrams
The earlier parts of this site built a proof system that can absorb other proofs. This chapter returns to the application that system serves. Chapter 1 described Tachyon in outline, with epochs, evolving nullifiers, and a window of retained values. Here we work out the objects themselves: which keys a wallet holds, what a note contains, how a note's nullifiers are derived for each epoch, why a spend publishes two of them, and why every action publishes exactly two values of a single kind.
We do not cover how a proof refers to the pool's history or how a spender shows absence from old epochs. The next chapter builds the pool state, chapter 21 covers absence proofs, and chapter 22 walks through the steps that prove all of this.
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, whose shielded protocol ZIP is still an empty outline. The book's own key hierarchy page carries a note calling itself significantly outdated, and the book's analysis of nullifiers presents its choice as a recommendation with an alternative. Where the sources differ, we show both and follow the code unless we say otherwise. Tachyon also pins Ragu at 0725b52, not at the 3219e08 that the earlier chapters read.
1. Fields and stand-ins
Tachyon's objects live in two fields. Pallas, the curve used for spend authorization, has coordinates in and scalars in . Poseidon's inputs and outputs, and therefore all tachygrams, are elements of , and signing keys are elements of . The toy curve from the curves appendix has the same shape, with coordinates in and scalars in , so we use it for every signing key below.
The hashes need a different treatment. Tachyon uses Poseidon throughout, which chapter 3 introduced, and each use has its own domain. In a field of 31 elements, unrelated hash outputs would collide constantly, and that would hide the behaviour we want to see. So the examples use a stand-in. We write for SHA-256 of the tag and inputs, reduced modulo 65,537, and a stand-in sponge produces four such values from one call. These numbers are not Poseidon outputs and are not elements of the toy field. They only show the structure, and the small range is useful later, because it lets us count a cheater's chances exhaustively.
2. The spending key and its two children
Spending a note requires two separate abilities. The owner has to authorize the spend with a signature, and the owner has to produce the note's nullifiers, which nobody else should be able to compute. The deep dive observes that these are the only two abilities the shielded protocol needs from its keys. Everything else in Orchard's key tree, including viewing keys and diversified addresses, supports delivering notes to recipients and detecting them, and chapter 1 explained that delivery has moved to a separate payment protocol.
The root is a spending key of 32 random bytes, and each ability gets one child derived from it. The two derivations differ only in a one-byte domain, through BLAKE2b-512 with the Zcash expansion personalization:
The spend authorizing key is a scalar in , and its public point is the spend validating key . The nullifier key is an element of , and it has no public counterpart.
One detail comes from RedPallas, the signature scheme that Orchard and Tachyon share. It requires to have a cleared sign bit in its compressed encoding. A point and its negative have the same and opposite signs of , so if the derived point has the wrong sign, the code negates , since . On the toy curve we use the parity of as the sign.
Our generator is the toy point , and the wallet's is the text alice-wallet-seed-7, which stands in for 32 random bytes.
| Key | Derivation | Stand-in value |
|---|---|---|
| , before normalizing | expand with 0x21, in | 19 |
| its public point | , with odd | |
| , normalized | negated, since was odd | 24 |
| , with even | ||
expand with 0x22 | 40954 |
The derivations from are not part of what proofs check. The deep dive says that the circuits use and as witnesses and never constrain how they arose. A wallet standard chooses the path, and the protocol only needs the results to look like randomly sampled keys.
3. The payment key
A sender has to name the owner of a new note. Publishing to the sender would give away the nullifier key, so the note carries a commitment to the pair. The payment key is a hash of the coordinates of together with :
With our keys, and the stand-in in place of Poseidon, this gives:
If the same wallet walks to the next index for a second sender, it obtains and , which give . Nothing in the two payment keys shows that they belong to one wallet.
Two facts about this hash shape the rest of the design. First, is a deterministic function of the pair, so one payment key handed to two senders would let them compare notes and link the payments. Since a hash admits no tweak, unlinkability comes from minting a fresh address per sender. The index that makes keys differ sits above , which is why our second address started from a second seed. The book gives two reasons for preferring a hash over Orchard's diversified base. That trick has no known secure analogue under lattice assumptions, and handing to senders would let one who kept it attack it later with a quantum computer.
Second, the hash binds to the note's owner field, and Section 8 shows that this is what stops a spender from inventing a second nullifier sequence.
The wallet can also separate proving from signing. The proof authorizing key gives a device what it needs to build proofs for every note paid to the matching payment key, and it cannot sign. A hardware wallet can therefore hold while another device does the proving. The key hierarchy page and a code comment call wallet-wide, so that one covers all of a wallet's notes. That does not fit a fresh pair per sender, and the sources do not reconcile the two. The next table lists what each key allows, with the per-sender reading.
| Key | Secret? | What its holder can do |
|---|---|---|
| yes | everything below | |
| yes | sign for spends, after randomizing (Section 5) | |
| yes | compute nullifiers given a note's , and so see when the note is spent | |
| yes | build proofs for every note paid to this , without signing | |
| shared with senders | receive notes, with no other ability |
Book and code: narrower delegation
Book: the key hierarchy page, marked as outdated, lists a per-note bundle that would let a prover work on one note only, where is the per-note key of Section 6.
Code at 7e50254: NoteSeed and SpendAction both witness a full and rederive from it, so a prover holding only could not run them. A comment on the type also says that proof delegation is yet to be specified.
This site follows the code, and treats per-note proving delegation as unsettled. A sync service, by contrast, receives only values and no key material.
4. Notes and commitments
A note has to record who owns it and what it is worth, and it needs enough randomness to stay hidden and to have nullifiers of its own. Tachyon's note has exactly four fields:
Here is the payment key, and is a value in zatoshi between 0 and . The nullifier trapdoor gives the note its identity for nullifier derivation, and the commitment trapdoor hides the note inside its commitment. Comparing with Orchard's note from chapter 1, the diversifier and transmission key have become , and has gone. Section 9 returns to what removing costs.
The sender chooses and and so knows all four fields, as the threat model assumes. The recipient has to learn them as well, through whatever payment protocol the two wallets use. How to generate them is unsettled. The deep dive says they should come from a standardized derivation off a wallet key, the code comments say they can come from a key negotiated out of band, and we found no standard at this commit. Suppose Bob pays Alice 25 zatoshi using the payment key above:
| Field | Value | Who knows it |
|---|---|---|
| 35507 | Alice gave it to Bob | |
| 25 | Bob and Alice | |
| 625 | Bob and Alice | |
| 61249 | Bob and Alice |
Bob cannot publish these values, so he publishes a note commitment that fixes all four fields:
Sapling and Orchard use variants of the Pedersen commitment, which rely on discrete log, and Tachyon uses a hash alone. Each Tachyon use of Poseidon starts its sponge with a zero rate and with the 16-byte domain name as a number in the capacity cell. Different domains therefore behave as unrelated hash functions, even on identical inputs, and Section 11 relies on this. For our note:
Changing only to 10197 gives the commitment 15883, which looks unrelated to 55697 although the note has the same owner, value, and .
A random is what lets the commitment hide two notes with the same owner and value from each other, and hiding here assumes that Poseidon behaves like a pseudorandom function.
5. Randomizing the signing key for each action
A signature has to be checkable by everyone, so its verification key must be public. If that key were , then every spend under that key would carry the same public key, and an observer could link them all. Spend authorization therefore works as in Orchard. Each action picks a scalar and signs with a randomized key,
The second equation holds because scalar multiplication distributes over addition, so is exactly the public key of . The action publishes , and the proof shows that it came from the inside the note's payment key.
Tachyon derives from per-action entropy and the note commitment, with read as a scalar, which is possible because . This ties the key to the note, and it lets a signing device recompute from the note and without having to trust a value handed to it. Our stand-in output is far larger than 43, so here we reduce it modulo 43, which the real construction does not do.
| Quantity | Computation | Value |
|---|---|---|
| chosen by the signer | 4071 | |
| , reduced mod 43 | 23 | |
| mod 43 | 4 | |
| check |
A signature follows the Schnorr pattern. With nonce 9, the signer computes , derives a challenge from a hash of , , and the message, and returns modulo 43. The verifier checks , and both sides equal . The verifier never used .
The public reveals nothing about when is uniform, because adding a uniform multiple of the generator produces a uniform point whatever the starting key.
We can test this exhaustively on the toy curve. Of the 42 points other than the identity, 21 have even and could be a normalized . For each of those 21 candidates, exactly one in turns it into . The published key is therefore equally consistent with every owner, and a second action by Alice with publishes the unrelated-looking .
An output action has nothing to authorize, since creating a note spends nobody's money. It still has to look like a spend, so it also publishes a key and a signature, with and , using the domain Tachyon-Output in place of Tachyon-Spend. Both shapes produce a uniform point. Each action also publishes a value commitment , which the chapter on the proof tree covers.
6. Deriving a note's nullifiers
Chapter 1 set out what an epoch nullifier has to do. A note needs a different nullifier in each epoch, these values must look unrelated to anyone without the owner's secret, and each epoch must have exactly one valid value. A wallet might also need many of them at once, because a proof about an old note covers every epoch since its creation. These needs lead to a derivation in two stages.
The first stage combines the two secrets that make a nullifier belong to this owner and this note. It produces the note's master key:
The sender knows and not , and the wallet holds for many notes, so only the owner of this specific note has both. Doing this once also keeps out of later headers. In the proof tree, NoteSeed computes and passes it on, and no later header carries , although SpendAction witnesses again to check the payment key. Our note gives:
The second stage turns and an epoch into nullifiers. One Poseidon permutation yields a whole rate's worth of output, so four values cost the same as one. Wasting three of them would be a poor use of a limited circuit, and Tachyon takes all four and assigns them to four consecutive epochs. To find the value for epoch , round down to the group's start , run one sponge over , and take the entry at position :
For we get , so is the entry at position 3 of the group that starts at 4.
Here is the running note's sequence, created in epoch 3 and spent with target epoch 7, the same example as the figure in chapter 1. We use "target epoch" for the epoch that a spend's proof reaches. The sources also call it the anchor's epoch or the present epoch.
| Epoch | Group start | Position | Role for this spend | |
|---|---|---|---|---|
| 0 | 0 | 0 | 19392 | before the note existed |
| 1 | 0 | 1 | 15569 | before the note existed |
| 2 | 0 | 2 | 21425 | before the note existed |
| 3 | 0 | 3 | 14090 | tested for absence |
| 4 | 4 | 0 | 2976 | tested for absence |
| 5 | 4 | 1 | 63093 | tested for absence |
| 6 | 4 | 2 | 59553 | tested for absence |
| 7 | 4 | 3 | 55113 | published by the spend |
| 8 | 8 | 0 | 61177 | published by the spend |
| 9 | 8 | 1 | 32258 | never revealed |
| 10 | 8 | 2 | 31053 | never revealed |
| 11 | 8 | 3 | 34200 | never revealed |
A value depends only on , because each group absorbs its own . Two derivations that overlap therefore agree on the epochs they share, and the script asserts this for our note.
Both inputs matter. A second note to Alice with has and , so her two notes do not share nullifiers. Bob knows the first note's , but his own key 44055 gives and , where the true value is 55113. So the sender of a note cannot watch for its spend.
The privacy argument assumes that Poseidon, keyed by , behaves as a pseudorandom function. Revealing some outputs then says nothing about the others, including the pair revealed at spend time.
The code allows epoch indices up to 1,048,575, since a block height has 32 bits and an epoch has 4,096 blocks. At 75 seconds per block that range lasts about 10,207 years.
Book and code: the nullifier's inputs
Deep dive: and , calling this the leading candidate.
Nullifier page and code at 7e50254: absorbs first, and the sponge absorbs , the group-aligned epoch.
For the deep dive absorbs , while the code absorbs . In our stand-in the two choices give 35925 and 59553, so they are different functions, although each gives every epoch one fixed value.
This site follows the code. Comments in the code also lag behind it. One still gives , another mentions a GGM tree, and a third calls the commitment scheme undecided while the function below it uses Poseidon.
7. Why the wallet derives its own nullifiers
A reader might expect a design in which the wallet hands a sync service a key for a range of epochs, and the service computes the nullifiers itself. An earlier Tachyon design did this, and the book keeps it as an archived page. The reason it was replaced is proof cost, which the following comparison makes concrete.
The archived design used the GGM construction, which treats an epoch's bits as directions down a binary tree. A seed sits at the root, each node has two children given by hashes and , and each leaf is a nullifier. Hashes are one-way, so holding a node gives access to the leaves below it and to nothing else, which is exactly a key for a range of epochs.
Take a 3-bit epoch space and the seed 13625, with a stand-in hash for each . To delegate epochs 2 and 3, a wallet hands over only the node at prefix 01, which is . Walking one more level gives and , which are exactly the nullifiers for those epochs, and one-way hashing keeps the holder away from all the others. The range of epochs 0 to 6 needs 3 prefixes (0, 10, 110). Over all ranges in this small space the worst case needs 4, reached by epochs 1 to 6, so the amount of delegated key material depends on the range.
The trouble is that each level of the tree costs one hash inside a proof, and proof steps are small. The analysis counts it this way.
A step has multiplication gates, and one Poseidon permutation costs 288 of them, so a step fits permutations.
A binary tree over an epoch space of days, about 45 years, has depth 14, so one walk takes 14 permutations and overflows a step. A tree with higher arity near the root can cut the depth to 7, which just fits one walk.
The analysis wants at least two nullifiers in one step, since a spend publishes two, and a tree walk cannot provide that. It then tries other constructions and recommends using a Poseidon permutation directly, with its rate-4 output. The analysis calls this Attempt 3, and it is the design of Section 6:
| Design | Permutations for one nullifier | Nullifiers per step of 7 permutations |
|---|---|---|
| GGM, binary tree of depth 14 | 14 | none, since one walk overflows a step |
| GGM, mixed arity, depth 7 | 7 | 1 |
| One hash per nullifier (Attempt 3 before batching) | 1 | 7 |
| Rate-4 squeeze (Attempt 3) | of a permutation |
This construction gives up the range key, because a plain hash has no partial key to hand out. The trade is that wallets derive and prove their own nullifiers, and they send the service explicit values. The service then holds a finite list and can compute nothing beyond it, and chapter 21 shows how the wallet later ties the service's work to its note.
Book and code: nullifiers per step
Analysis page: nullifier derivations per step. The same page's conclusion offers a MiMC-based alternative at about 50 to 68 per step, for those willing to accept the added dependency.
Code at 7e50254: NF_DERIVATION_WIDTH is 16, so NullifierDerive runs 4 sponges per window, and the step also binds the window to a committed sequence. The sources do not explain the choice of 16.
This site follows the code, and treats 28 as the analysis's upper count.
8. How the commitment pins the nullifiers
Publishing a nullifier only prevents double spends if a spender has no choice about its value. If a spender could produce two nullifier sequences for one note, the second sequence would pass both the exclusion proof and the window, since neither has ever seen those values. The note commitment has to rule this out, and it does so through a chain of hashes:
The proof's NoteSeed step computes from the witnessed , builds the note, and derives both and from the same and . A seed can name any note it likes, so this step alone proves little. The claim closes later, when SpendableInit requires to be a member of a creation stamp's tachygrams. The spender is therefore held to a commitment that already exists in the pool, and both inputs to sit behind that commitment.
To cheat, a spender would keep fixed while changing what lies behind it. Our small hash range allows us to try every possible change to one field at a time:
| Field changed | Wrong values tried | Values that keep the same | What a match would give the cheater |
|---|---|---|---|
| (through ) | 65,536 | 1 | a second nullifier sequence |
| 65,536 | 1 | a second nullifier sequence | |
| 65,536 | 2 | a different value for the same note | |
| 65,536 | 2 | nothing useful, since the note is otherwise the same |
A random function into 65,537 values should produce about one match per row. The table shows 6 matches in total, and each of them is a real collision. With , for example, the payment key still comes out as 35507, so the commitment stays 55697, while the master key 18682 yields in place of 55113. On this toy scale a cheater could double spend.
The attack succeeds here only because the range is small. The protection comes entirely from the assumption that nobody can find such a collision, so it needs a hash with a far larger range. Real Poseidon outputs have about 255 bits, and a search of the kind we just ran would be hopeless. Two further assumptions complete the argument. The proof system must not allow a false statement to verify, and the proof must really compute these hashes, which Section 13 qualifies, since these steps do not yet run as real circuits.
The remaining pieces follow the same pattern. SpendBind computes the published pair from the threaded and the epoch, so no nullifier is a free witness. It also compares the secret's commitment with the one the spendable lineage carries, and the proof tree page notes what this rejects. A phantom note that reuses with a larger value has a different , and that commitment was never published.
9. Reusing a trapdoor
Nothing in the protocol forces to be unique, because the pool holds an unordered set of values and gives notes no position to bind to. Orchard avoided this by setting a new note's to the nullifier of a spent note, and consensus already forbids repeated nullifiers. Tachyon cannot enforce that binding, and a malicious sender can exploit this:
Suppose Bob sends Alice a second note with value 9, a new , and the same . Its commitment 53772 differs from 55697, so it looks like a separate note. Since fixes the same , both notes have and the same at every epoch. Once Alice spends one of them, the other can never be spent, because its nullifiers have already been published. A wallet detects this on receipt by computing one nullifier at a fixed reference epoch for each incoming note. With reference epoch 0, both notes give 19392, and the wallet rejects the second.
This is the faerie gold attack, in which a recipient holds money that turns out not to be spendable. It harms the recipient and creates no money, and Tachyon moves the defence out of consensus. A wallet's own note set is small, which keeps the comparison cheap.
Book and code: faerie gold
Deep dive: the attack remains possible in principle, and resisting it becomes the responsibility of the payment protocol, through the wallet check above.
Code comments at 7e50254: they call the attack moot under out-of-band payments, and say that nullifiers need no collision resistance. The nullifier page separately warns that notes should not reuse .
We did not find a wallet check in the crate. This site follows the deep dive here, since the example above shows that the attack works, and out-of-band delivery does not change the arithmetic.
10. Why a spend publishes two nullifiers
We can now return to the rule that chapter 1 stated and tested with labels. A spend with target epoch proves that is absent from epoch , for every closed epoch from the note's creation up to , and validators keep the tachygrams of the current and preceding epochs. Validators also accept a stamp for epoch during epoch or epoch , because a transaction may still be waiting when a new epoch begins, and neither a miner nor a sync service can update the proof on the wallet's behalf.
A later proof looks for each nullifier only in the epoch with the same number. So the mechanism works only if a spend leaves a value where such a test will find it. The grace period threatens exactly this, because a spend with target may land in epoch .
Suppose instead that a spend published only . A first spend with target 7 waits in the mempool and gets accepted in epoch 8, so 55113 lands in epoch 8. A second spend with target 8 now tests against epoch 7, where it is absent, and it publishes , which nobody has published. Both checks pass and the note is spent twice. Across all 111 scenarios, 48 get through in this way.
Publishing as well closes this gap. Write for the epoch in which a spend is accepted, so that is either or . Both possibilities are covered by the pair, so a spend accepted in epoch always publishes during epoch . The figure shows the three situations.

From this one fact, the double-spend argument takes two cases. Let a first spend have target and acceptance epoch , and let a second spend of the same note have target and acceptance epoch .
Case . Epoch is closed and lies before the second target, so the second proof has to show absent from epoch . The first spend put it there, so the proof cannot be built.
Case . Combining with gives , so is or . The second spend publishes both and , so it repeats . The same inequalities give , so epoch is still retained, and validators reject the duplicate. This is also why the window needs two epochs.
Take two spends with target 7, the first accepted in epoch 7 and the second in epoch 8. The colliding values 55113 and 61177 entered the pool during epoch 7, so a validator that kept only the current epoch would miss them when the second spend arrives.
The argument assumes that a spender cannot choose nullifiers, which Section 8 covered, and that proofs cannot be forged. Here is every outcome for a first spend with target 7, using our note's values:
| First spend: target | Second spend: target | Caught by | Reason |
|---|---|---|---|
| 7, accepted in 7 | 6, accepted in 7 | window | both publish |
| 7, accepted in 7 | 7, accepted in 7 | window | both publish |
| 7, accepted in 7 | 7, accepted in 8 | window | both publish |
| 7, accepted in 7 | 8, accepted in 8 | exclusion proof | is present in epoch 7 |
| 7, accepted in 7 | 8, accepted in 9 | exclusion proof | is present in epoch 7 |
| 7, accepted in 7 | 9, accepted in 9 | exclusion proof | is present in epoch 7 |
| 7, accepted in 7 | 9, accepted in 10 | exclusion proof | is present in epoch 7 |
| 7, accepted in 8 | 7, accepted in 8 | window | both publish |
| 7, accepted in 8 | 8, accepted in 8 | window | both publish |
| 7, accepted in 8 | 8, accepted in 9 | window | both publish |
| 7, accepted in 8 | 9, accepted in 9 | exclusion proof | is present in epoch 8 |
| 7, accepted in 8 | 9, accepted in 10 | exclusion proof | is present in epoch 8 |
The script checks this on every pair of spends of our note, with targets from epoch 3 to epoch 9 and each spend accepted in its target epoch or the next. There are 111 such scenarios. In 72 of them and the second proof cannot be built, and in the other 39 we have and consensus finds a shared value in its window. No scenario escapes. The two checks also overlap in 6 scenarios, where the first spend was accepted on time and the second targets and lands in the very next epoch, so the window would have caught it too.
Reconstructed from code at commit 7e50254, not confirmed by the authors
The rules come from the deep dive's consensus section, which argues by listing the safe cases for each acceptance time. The single fact about and the two-case argument are our restatement. The SpendBind step derives the pair as described, and a comment on the tachygram type states the window rule, but we did not find the window scan itself in the crate. The tachygram page adds a second reason for scanning a whole epoch, which the next chapter explains, since a stamp's anchor can advance within its epoch.
Publishing the pair has a cost in the circuit. The two values do not always come from one sponge.
Our note's pair shows why. The value comes from the group at , while comes from the group at , so this pair needs two sponges. A spend with target 5 would find both and in the group at .
A circuit's shape cannot depend on secret values, as chapter 4 explained, and SpendBind runs two sponges in every case. The pair also needs a following epoch, so the code refuses a spend in the final epoch.
11. Tachygrams and output padding
Earlier Zcash pools keep commitments in a Merkle tree and nullifiers in a separate set. Tachyon instead treats both as one kind of value, a tachygram, which is a 32-byte element of . A single structure can answer both questions a spend asks, namely whether a commitment is present and whether a nullifier is absent, and consensus does not need to know which kind a value is. This also helps privacy, as long as the published values give no other hint of their kinds.
Counting gives such a hint. A bundle already reveals its number of actions , because each action has one signature. A spend publishes two nullifiers and an output has only one commitment, so without further care the total would reveal the split, with spends.
| Spends | Outputs | Tachygrams without padding | Observer computes | Tachygrams with padding |
|---|---|---|---|---|
| 0 | 4 | 4 | 8 | |
| 1 | 3 | 5 | 8 | |
| 2 | 2 | 6 | 8 | |
| 3 | 1 | 7 | 8 | |
| 4 | 0 | 8 | 8 |
The deep dive notes that the leak is only about the count, since tachygrams never sit beside a particular action. To remove it, each action publishes exactly two. A spend publishes its pair. An output publishes its commitment and a padding tachygram, so the last column of the table is constant.
The pad cannot be any easily computed function of the commitment. To see what an observer could do if it were, start from a bundle's published list:
Take one bundle () with actions, two of each kind. It publishes tachygrams, and the stamp lists them in sorted order.
| Published tachygram | Known only to the parties involved |
|---|---|
| 13229 | output 2, pad |
| 24937 | output 2, |
| 30698 | spend 2, |
| 31831 | output 1, |
| 40944 | spend 2, |
| 46656 | output 1, pad |
| 55113 | spend 1, |
| 55356 | fee change note, |
| 61177 | spend 1, |
Suppose outputs derived the pad as . An observer holding the 9 published values hashes each one and looks for the result in the list, which takes 72 ordered comparisons. In our bundle with that design, the test finds exactly 2 matches, (31831, 52667) and (24937, 38763), and so the observer learns the output count. Run the same test on the values in the table above, where each pad comes from the note opening, and it finds no matches. To test the real relation, the observer would need the note's , , , and .
So Tachyon derives the pad from the note opening, with the same four inputs as the commitment and a different domain:
The hiding that the commitment gets from also hides any relation between the two values, and the domain tag from Section 4 is the only thing separating the pad from the commitment.
Book and code: the padding tachygram
Deep dive: , where is an arbitrary preimage, and it calls this a dummy.
Tachygram page and code at 7e50254: the pad commits to the note opening, and OutputBind computes both values from the note.
Both forms resist the pairing test, so the disagreement is about construction. This site follows the code.
Sources differ: tachygrams per action
The bundle page says an action is bound to one tachygram, and the deep dive's accumulator section defines one per action, while its transaction section and the code use two. As in chapter 1, this site follows the code, with two per action and one more per bundle.
One value in the table falls outside the pairs. Each bundle's fee change note publishes only its commitment, even when its value is zero, and so a stamp covering actions in bundles has tachygrams. The extra one appears once per bundle whatever the bundle does, so it adds nothing about the split. The aggregation chapter covers fees.
A stamp holds its tachygrams as a flat list, and the wire format requires them to be unique and sorted by their byte encoding. Sorting erases the order in which actions contributed them, and in the code a spend's step commits its pair as a set. Every published value is also required to be nonzero, which keeps each factor of the polynomial in the next section from degenerating. The proof tree page gives a different reason, a collision with the note's own commitment, and we did not establish that one independently.
These privacy statements cover what the published values reveal. As chapter 2 records, Ragu's book and code do not yet establish zero knowledge, so a separate question remains about what a proof itself reveals.
12. One structure for both tests
We can now see why one kind of value is enough. A stamp's set of tachygrams corresponds to the polynomial whose roots are its members, . A value is present exactly when vanishes there, and absent when it does not, and neither test cares about the kind of value.
For our bundle, working modulo 65,537, gives for output 1's commitment and for spend 1's . The test has no need to know which kind each root is. The value was not published, and .
A spender uses the first kind of test to show that its commitment was published, and the second to show that an old nullifier was not. The next chapter develops these polynomials and their commitments.
13. Where these derivations run
Each object in this chapter is computed inside a step of the proof tree. Chapter 22 walks through them in order, and this table locates what we have covered:
| Step | Who runs it | What it does here |
|---|---|---|
NoteSeed | wallet | derives from , then and |
NullifierDerive | wallet | derives a window of nullifiers from |
SpendBind | wallet | derives and , and checks they are nonzero |
SpendAction | wallet | checks against , derives and , and commits the pair |
OutputBind | wallet | derives and the pad from a note |
Reconstructed from code at commit 7e50254, not confirmed by the authors
At this commit, Tachyon builds Ragu with its mock feature, and these steps compute hashes natively. Comments mark the known gaps. A real circuit needs a low-bit decomposition of each epoch to find , since any field element can be written as , and a bit decomposition of to use it as a scalar. So our statements about what a proof forces describe the design that the steps declare, and we did not find real circuits enforcing them in this crate. The gate counts of Section 7 come from the analysis page, and they are estimates for those circuits.
14. Where this chapter leaves us
This chapter took the following steps:
- A wallet derives two children from . The scalar authorizes spends through its public point , and derives nullifiers. No proof constrains these derivations.
- The payment key is a hash of and , so it reveals neither of them, and wallets mint a fresh one per sender since a hash cannot be tweaked.
- A note is , its commitment is a domain-tagged Poseidon hash of those fields, and the sender knows all four fields but not .
- Each action signs with a key randomized by , so a published is consistent with every possible owner, and outputs use the same form with no spending authority.
- A note's master key is , and one sponge over and a group start gives four consecutive epoch nullifiers. Cost in the proof is the reason this replaced the archived GGM tree.
- Since and both sit behind , a spender held to a published commitment has one nullifier sequence, assuming Poseidon resists collisions. Reused values remain a danger that the wallet must detect.
- A spend accepted in epoch always publishes in epoch . This single fact, the exclusion proof, and a two-epoch window together catch every double spend.
- Commitments and nullifiers are published as one kind of value. Each action publishes two tachygrams and each bundle one more, derived so that an observer learns the number of actions and not the split between spends and outputs.
All of this is a design at one commit of a proposal that has not activated, and the steps above still run against a mock of Ragu. We have spoken of values being published in the pool without saying how a proof can refer to history that validators have forgotten. The next chapter builds that structure, starting from the hash chain of anchors.