Notes, Nullifiers, and Commitments
1. Why This Chapter Exists
A note is the Orchard analogue of a UTXO. Two cryptographic objects make it usable: the note commitment (deposited into the tree) and the nullifier (revealed on spend). Both must be deterministic from the note and the spender's nullifier-deriving key, and both must be tightly bound to prevent double-spend and forgery. After this chapter the reader can locate every contributor-relevant constant of the note encoding and trace from its origin in the previous spend.
2. Definitions
Definition 2.1 (Note)
A note is the tuple
with
- : diversifier;
- : diversified transmission key;
- : value;
- : nullifier seed (derived from the previous note's nullifier);
- : deterministic randomness derived from ;
- : commitment trapdoor (also derived from ).
Definition 2.2 (Note Commitment)
a Sinsemilla commit over the canonical encoding. The -coordinate extraction is the value inserted into the Merkle tree.
Definition 2.3 (Nullifier)
where is the nullifier base.
Invariant 2.4 (Rho Chaining)
A note's is the nullifier of the input note that funded it. The first note in a transaction chain seeds from the spent note's nullifier; subsequent notes do the same. This binds note creation to a specific spend and prevents two distinct notes from sharing a nullifier without one of them being a forgery.
3. The Code
3.1 The Note Type
loading...
The struct has four private fields: the Address recipient, the
NoteValue, the Rho creation identifier, and the
RandomSeed. From the latter, and are
derived deterministically (ZIP 212). Note equality is defined by
the commitment, not by structural equality of the fields, which
prevents two distinct field encodings of the "same" note from
appearing distinct.
3.2 Note Commitment
src/note/commitment.rs
encodes the note canonically into a bit string and calls
SinsemillaCommit under the Orchard note commitment domain.
3.3 Nullifier
src/note/nullifier.rs
implements the formula of Definition 2.3 step by step. The
in-circuit version is in
src/circuit.rs.
3.4 The Rho Newtype
loading...
Rho is a newtype around pallas::Base with a tightly
controlled construction surface. The public API exposes
Rho::from_nf_old (chaining from a previous nullifier) and
Rho::from_bytes (used only inside parsing); a free constructor
taking arbitrary field elements does not exist.
4. Failure Modes
- Forged . Allowing to be sampled freely instead of being chained to a spent nullifier breaks Invariant 2.4 and enables double-spends of distinct notes.
- Non-canonical encoding. The fixed bit-length encoding of
, , is enforced by range checks in
src/circuit/note_commit.rs. Missing one range check is the textbook unintended ambiguity bug. - PRF identity collision. If ever returns zero, the resulting nullifier degenerates to , leaking more structure. The probability is negligible but tests assert the property.
rseedreuse. Reusing across two notes with the same recipient produces the same and , which leaks information. The encoding rules in ZIP 212 forbid reuse.
5. Spec Pointers
- Zcash Protocol Specification, Section 4.16: Note commitments.
- Zcash Protocol Specification, Section 4.17: Nullifier derivation for Orchard.
- ZIP 212: standardised note randomness derivation.
src/test_vectors.rs: the wiring of JSON test vectors that exercises notes and nullifiers.
6. Exercises
- Read
src/note/commitment.rsand write down, in pseudocode, the byte encoding fed intoSinsemillaCommit. Cite the bit-length constants (L_ORCHARD_BASE,L_VALUE, ...) you use. - Identify the unit test in
src/note/nullifier.rsthat checks against an external test vector. What format do the test vectors arrive in? - Code task. Add a unit test in
src/note.rsthat builds two notes with the same and confirms their nullifiers are distinct when or the other fields differ. Place the test under#[cfg(test)]and runcargo test --lib note::.
7. Further Reading
- Zcash Protocol Specification, Section 4.1: the higher-level structure that notes / commitments / nullifiers fit into.
- The Orchard Book, Notes walkthrough.