Skip to main content

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 ρ\rho from its origin in the previous spend.

2. Definitions

Definition 2.1 (Note)

A note is the tuple

n=(d,pkd,v,ρ,ψ,rcm),n = (d,\, \mathsf{pk_d},\, v,\, \rho,\, \psi,\, \mathsf{rcm}),

with

  • d{0,1}88d \in \{0, 1\}^{88}: diversifier;
  • pkdEp\mathsf{pk_d} \in E_p: diversified transmission key;
  • v[0,264)v \in [0, 2^{64}): value;
  • ρFp\rho \in \mathbb{F}_p: nullifier seed (derived from the previous note's nullifier);
  • ψFp\psi \in \mathbb{F}_p: deterministic randomness derived from rseed\mathsf{rseed};
  • rcmFq\mathsf{rcm} \in \mathbb{F}_q: commitment trapdoor (also derived from rseed\mathsf{rseed}).

Definition 2.2 (Note Commitment)

cm=NoteCommitrcm(gd,pkd,v,ρ,ψ),\mathsf{cm} = \mathsf{NoteCommit}_{\mathsf{rcm}}\big(g_d,\, \mathsf{pk_d},\, v,\, \rho,\, \psi\big),

a Sinsemilla commit over the canonical encoding. The xx-coordinate extraction cm=ExtractP(cm)\mathsf{cm}^\star = \mathsf{Extract}_{\mathbb{P}}(\mathsf{cm}) is the value inserted into the Merkle tree.

Definition 2.3 (Nullifier)

nf=ExtractP([PRFnknfOrchard(ρ)]K+ψ+cm),\mathsf{nf} = \mathsf{Extract}_{\mathbb{P}}\Big( \big[\mathsf{PRF}^{\mathsf{nfOrchard}}_{\mathsf{nk}}(\rho)\big]\, \mathcal{K} + \psi + \mathsf{cm}\Big),

where KEp\mathcal{K} \in E_p is the nullifier base.

Invariant 2.4 (Rho Chaining)

A note's ρ\rho is the nullifier of the input note that funded it. The first note in a transaction chain seeds ρ\rho 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

src/note.rs
loading...

The struct has four private fields: the Address recipient, the NoteValue, the Rho creation identifier, and the RandomSeed. From the latter, ψ\psi and rcm\mathsf{rcm} 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

src/note.rs
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 ρ\rho. Allowing ρ\rho 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 vv, ρ\rho, ψ\psi 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 PRFnknfOrchard(ρ)\mathsf{PRF}^{\mathsf{nfOrchard}}_{\mathsf{nk}}(\rho) ever returns zero, the resulting nullifier degenerates to ExtractP(ψ+cm)\mathsf{Extract}_{\mathbb{P}}(\psi + \mathsf{cm}), leaking more structure. The probability is negligible but tests assert the property.
  • rseed reuse. Reusing rseed\mathsf{rseed} across two notes with the same recipient produces the same ψ\psi and rcm\mathsf{rcm}, which leaks information. The encoding rules in ZIP 212 forbid reuse.

5. Spec Pointers

6. Exercises

  1. Read src/note/commitment.rs and write down, in pseudocode, the byte encoding fed into SinsemillaCommit. Cite the bit-length constants (L_ORCHARD_BASE, L_VALUE, ...) you use.
  2. Identify the unit test in src/note/nullifier.rs that checks against an external test vector. What format do the test vectors arrive in?
  3. Code task. Add a unit test in src/note.rs that builds two notes with the same ρ\rho and confirms their nullifiers are distinct when nk\mathsf{nk} or the other fields differ. Place the test under #[cfg(test)] and run cargo test --lib note::.

7. Further Reading