Nullifier Right-Object Canonicalization Discipline

  • Name: nullifier right-object canonicalization discipline
  • Category: zk-integration reference / replay-prevention design note / nullifier review checklist / witness-uniqueness discipline
  • Summary: Healthy privacy systems do not derive nullifiers from whatever witness artifact happens to prove authorization. They pick one canonical semantic right object first — note secret, claim slot, note position, identity secret, or another uniquely constrained protocol-native object — and derive replay-prevention state from that object rather than from a malleable signature, widened witness value, alternate encoding, or signer-chosen envelope. This note exists to preserve the positive-design target exposed by nullifier-derivation nondeterminism and by 0xPARC StealthDrop: one semantic right should have one admissible nullifier story.

Why this note exists

  • Replay incidents around nullifiers are often explained as range check missing, low-s missing, or one weird signature edge case, but those descriptions often stop one layer too late.
  • The more durable design failure is that the protocol never made the replay-prevention object explicit. It asked a witness to prove ownership, then quietly reused that witness itself as if it were also a canonical identity for the right being spent or claimed.
  • StealthDrop is the preserved clean exemplar: a valid ECDSA signature proved wallet control, but signature validity did not imply one unique signature object for one claim right, so nullifier uniqueness drifted before the contract ever saw calldata.
  • The healthy-side question is therefore sharper than did we prevent known malleability branches? It is what exact underlying right does this nullifier represent, and what proves that right has only one admissible nullifier derivation?

What this note means by right-object canonicalization

  • A semantic right object is the actual protocol thing that should only be consumed once: a claim slot, a note, a vote right, an identity secret bound to one scope, or another uniquely defined object.
  • Right-object canonicalization means the protocol can point to one representation of that right that is fully constrained before replay-prevention state is derived.
  • The nullifier should be a deterministic function of that canonical right object, not of a looser witness that merely proves possession or authorization.
  • Signature validity, Merkle membership, or witness satisfiability may still be necessary, but those are authorization checks. They are not, by themselves, uniqueness proofs.

The six questions a healthy nullifier design should answer

1. What exact thing is being consumed once?

  • Reviewers should be able to name the one semantic right in plain language: one note, one claim slot, one membership action in one scope, one output position, or similar.
  • If the answer is instead a valid signature or a valid witness, the design is already suspicious because it describes a proof artifact, not the underlying right.

2. Where does that right become one canonical object?

  • The protocol should identify the exact fields that define the right and where they are range-constrained, normalized, and bound together.
  • If membership logic consumes one representation while nullifier derivation consumes a wider or differently encoded representation, the protocol already has two rights.

3. What remaining witness degrees of freedom can vary while the right stays constant?

  • Signer-chosen randomness, alternate signature encodings, widened field elements, optional recovery ids, path aliases, and unconstrained high bits all belong in the review inventory.
  • The reviewer question is brutally literal: if I hold the real right constant, what can I still wiggle that might change the nullifier?

4. Why is the nullifier derived from this object instead of from a witness artifact?

  • Teams should justify why the chosen nullifier input is the right itself, not just a convenient thing already available inside the proof.
  • We already had the signature in-circuit or the witness was easy to hash is not a security argument.

5. What would a duplicate claim actually have to collide on?

  • A healthy design makes duplicate redemption require reusing the same canonical right object, which should reproduce the same nullifier or fail.
  • If a duplicate claimant can present a different admissible authorization envelope and get a fresh nullifier without changing the underlying right, replay protection is illusory.

6. What test proves one right cannot fan out into many nullifiers?

  • Good test suites hold the underlying right fixed while varying every admissible witness choice: signature randomness, alternate encodings, high-bit junk, path shape, or library-specific normalization behavior.
  • All such attempts should either produce the same nullifier or fail before proof acceptance.

What sound design looks like

  • Replay prevention is derived from protocol-native state.
    • Nullifiers come from note secrets, canonical indices, scoped identity secrets, or another object the protocol already means to treat as unique.
  • Privacy-preserving uniqueness still uses a protocol-owned hidden right.
    • If a public claim index, address hash, or note position would leak too much, the answer is not hash whatever hidden witness we already have. The answer is to define a hidden but still protocol-canonical right object — for example a scoped secret, deterministic signer-derived nullifier primitive, or other uniquely constrained hidden identifier — and then derive replay protection from that object.
  • Authorization artifacts stay authorization artifacts.
    • Signatures, Merkle paths, and witness envelopes prove control or inclusion, but they are not silently promoted into uniqueness state unless the protocol can prove they are canonical for its purposes.
  • Membership, ownership, and nullifier derivation consume the same constrained representation.
    • If one gadget sees low bits and another sees the whole field element, or one layer normalizes signatures while another hashes raw bytes, the design is still split.
  • Nullifier derivation is reviewed as a first-class security object.
    • The team can name the right, the exact derivation formula, the remaining degrees of freedom, and the tests that rule them out.
  • Replay prevention survives wallet and library diversity.
    • The protocol does not assume every signer, wallet, API, or proving adapter will happen to canonicalize witnesses the same way unless that behavior is explicitly forced.

Useful positive design shapes

1. Derive nullifiers from canonical secrets or indices

  • The cleanest pattern is to derive the nullifier from a protocol-native object that already has one intended representation: a note secret, note commitment opening, canonical claim index, or scoped identity secret.
  • This avoids overloading witness artifacts into replay-prevention identity.

2. Separate proof of control from uniqueness state

  • A zk circuit can verify a signature, ownership proof, or membership witness while still deriving the nullifier from a different canonical object.
  • The design should be comfortable saying this proves you control the right and separately this is the one object whose consumption we track.

3. If privacy matters, hide the right without surrendering canonicality

  • The stronger design move is to replace public right objects with hidden but protocol-owned ones, not to reuse opaque witness artifacts as if opacity alone implied uniqueness.
  • In practice this means designs like scoped secret -> nullifier, canonical claim slot committed under a private opening, or a deterministic signature / nullifier primitive whose whole purpose is to give one account one hidden replay-prevention object.
  • StealthDrop is the warning label here: public address hash leaks too much was a real problem, but therefore hash an ECDSA signature was not a safe replacement because the hidden object was still a signer-chosen envelope rather than a canonical right.

4. Canonicalize before hashing into nullifier state

  • If a witness component must influence the nullifier, canonicalization and range constraints should happen before nullifier derivation, not after verification or only in one call path.
  • This is the design answer to we fixed one alternate encoding but forgot the others.

5. Treat signer-produced envelopes as hostile to uniqueness by default

  • ECDSA signatures, wallet-returned byte arrays, recovery ids, and library-formatted proofs should be presumed non-canonical until proven otherwise.
  • The burden is on the protocol to show that its workflow yields one admissible witness object per right, not on the attacker to find the second one later.

5. Keep the derivation object close to the replay map

  • The code and audit story should make it obvious that the same canonical object used to derive the nullifier is the object the contract means to consume once.
  • Large gaps between witness construction, nullifier derivation, and onchain replay checks are where multiplicity tends to hide.

Design questions worth asking first

  • What exact semantic right is supposed to be consumed once here?
  • Which fields define that right, and where are those fields fully constrained before nullifier derivation?
  • Could two different valid witnesses represent the same right while changing the bytes or field elements hashed into the nullifier?
  • If signatures are involved, what signer-choice, malleability, encoding, or wallet-normalization degrees of freedom remain?
  • Does the protocol derive the nullifier from the right itself or from a proof artifact that merely demonstrates control of the right?
  • What property test proves that holding the right constant leaves no remaining freedom that can change the nullifier?

Red flags

  • We hash the signature so it is private.
  • The witness is valid, so it is unique enough.
  • We already fixed low-s used as if that alone proved one-signature-per-right.
  • Membership checks, ownership checks, and nullifier derivation consuming different widths, encodings, or helper libraries.
  • Replay maps that only ever see the nullifier while nobody can explain what exact underlying right that nullifier was supposed to represent.
  • Review arguments that rely on wallet behavior or signer UX rather than on protocol-enforced canonicalization.

Failure-prevention checklist

  • Name the exact semantic right that should only be consumed once, and document the canonical fields that define it.
  • Derive nullifiers from that canonical right object rather than from a merely valid authorization witness whenever possible.
  • Range-constrain, normalize, and encode every field that feeds nullifier derivation before hashing; membership logic, ownership logic, and nullifier logic should consume the same constrained representation.
  • Treat signatures and wallet-returned authorization envelopes as non-canonical by default. Inventory low-/high-s, nonce choice, recovery id, alternate serialization, signer randomness, and library normalization behavior explicitly.
  • If privacy requirements rule out a public claim slot, address hash, or note position, replace it with a hidden but still protocol-canonical right object rather than with an opaque witness artifact chosen by the signer or prover.
  • Add property tests that keep the underlying right constant while varying all admissible witness choices; require those tests to collapse to one nullifier or fail before verification.
  • Keep replay-protection logic and nullifier derivation logically adjacent so reviewers can trace one right into one consumed state key without reconstructing hidden assumptions.
  • During wallet, library, or circuit upgrades, re-prove the one-right / one-nullifier claim. A new signer adapter can silently widen witness multiplicity even if verification still succeeds.
  • Prefer protocol-native unique objects over convenience artifacts. Already available in the proof is not a reason to promote a witness into replay-prevention state.
  • If privacy goals make the underlying right sensitive, derive a hidden canonical object for replay prevention rather than substituting an opaque but nondeterministic witness.
  • Keep a regression corpus of prior nullifier-multiplicity failures such as StealthDrop-style signature-derived nullifiers and range/alias siblings like Semaphore and ZkDrops.

Audit prompts

  • What exact right does this nullifier stand for, and where is that right made canonical in code?
  • Can two different valid witnesses correspond to the same right while producing different nullifiers?
  • Are any fields partially constrained for membership but fully consumed for nullifier derivation?
  • If a signature or wallet-produced envelope feeds the nullifier, what proves that envelope is unique for one authorization in this protocol?
  • Which witness degrees of freedom remain after all explicit canonicalization steps run?
  • Could a wallet upgrade, signature-library swap, or alternate prover backend change the set of admissible witness encodings without the contract noticing?
  • What test demonstrates one right, many witnesses cannot become one right, many nullifiers?
  • If privacy requirements ruled out a public claim index or raw address hash, what hidden canonical object replaced it and why is that replacement truly canonical?
  • If the proposed hidden replacement is a signature, witness blob, or wallet-returned envelope, what protocol-level argument proves it is a unique right object rather than merely a private authorization artifact?

Lessons that should already be standard

  • Replay protection is not hash something hard to invert. It is commit to one canonical right object and consume it once.
  • Privacy pressure does not weaken the canonicality requirement. A hidden right still has to be protocol-owned and unique; opacity alone is not a replay defense.
  • A valid authorization witness is not automatically a safe uniqueness witness.
  • Signature malleability is only the obvious part of the problem. The broader risk is witness multiplicity anywhere the protocol confuses proof-of-control with replay-prevention identity.
  • Nullifier reviews should start by fixing the underlying right in place, then asking what can still vary. If the answer is anything meaningful, the design is not done.
  • Wallet UX and library defaults are not canonicalization proofs. If uniqueness matters, the protocol must force it.
  • The healthiest nullifier designs are boring: one semantic right, one constrained representation, one derived nullifier, one replay key.

Negative exemplar cluster

  • 0xPARC StealthDrop is the preserved signature-backed canonical case: the protocol used a valid ECDSA signature as nullifier source, but one claim right admitted multiple verifier-acceptable signatures, so replay prevention tracked witness multiplicity instead of right consumption.
  • nullifier-derivation nondeterminism preserves the broader exploit family where the derivation path itself is many-to-one before storage ever sees a nullifier.
  • nullifier canonicalization failure and the related Semaphore / a16z ZkDrops incidents are useful contrast cases: there the same logical nullifier crossed the boundary under multiple raw encodings rather than one right generating multiple nullifiers at derivation time.

How this note complements the exploit corpus

Internal linkages

Control surface