Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

noir-nullifier

A zero-knowledge nullifier circuit for Noir that proves a note can be spent exactly once without revealing which note is being spent.

Architecture

The circuit proves the following in zero knowledge:

  1. Note existence — The prover knows a note commitment that exists in the global commitment tree (Sparse Merkle Tree membership proof)
  2. Nullifier correctness — The revealed nullifier is correctly derived from the prover's secret key and note randomness
  3. Double-spend prevention — The nullifier has NOT been revealed before (Sparse Merkle Tree non-membership proof against the nullifier set)

Public Inputs

Input Description
nullifier The nullifier being revealed (marks the note as spent)
commitment_root Root of the note commitment tree
nullifier_root Root of the nullifier set (already-spent nullifiers)

Private Inputs

Input Description
sk Secret key (owner's private spending key)
rho Randomness / nonce tied to the note at creation
r Note blinding factor
v Note value (amount)
token Token identifier
chain_id Chain identifier
commitment_path Merkle path proving note exists in commitment tree
commitment_index Index/key of the note in the commitment tree
nullifier_path Merkle path proving nullifier is NOT in the nullifier set

What the Circuit Checks

1. note_commitment = hash_note_commitment(sk, r, v, token, chain_id)
2. verify_membership(commitment_root, commitment_index, note_commitment, commitment_path)
3. computed_nullifier = hash_nullifier(sk, rho)
4. assert(computed_nullifier == nullifier)  // public input match
5. verify_non_membership(nullifier_root, computed_nullifier, nullifier_path)

Symbol

⊥ — The nullifier makes the statement "this note is still alive" false, permanently.

Dependencies

  • poseidon2-interop-kit — Domain-separated Poseidon2 hashing (nullifier domain tag = 3, note commitment domain tag = 2)
  • noir-sparse-merkle — Sparse Merkle Tree membership and non-membership proofs

Quick Start

[dependencies]
noir_nullifier = { git = "https://github.com/Mimir-Collective/noir-nullifier" }
use noir_nullifier::spend;

// In your circuit:
spend(
    nullifier,           // pub
    commitment_root,     // pub
    nullifier_root,      // pub
    sk,                  // private
    rho,                 // private
    r,                   // private
    v,                   // private
    token,              // private
    chain_id,           // private
    commitment_path,    // private
    commitment_index,   // private
    nullifier_path,     // private
);

How It Works

┌─────────────────────────────────────────────────────┐
│                  PRIVATE WITNESS                      │
│                                                      │
│  sk, r, v, token, chain_id                          │
│       │                                              │
│       ▼                                              │
│  note_commitment = Poseidon2(2, sk, r, v, token,    │
│                              chain_id)               │
│       │                                              │
│       ▼                                              │
│  ┌─────────────────────┐                            │
│  │ SMT Membership Proof │◄── commitment_path        │
│  │ (note exists)        │                            │
│  └──────────┬──────────┘                            │
│             │                                        │
│             ▼                                        │
│  commitment_root ══════════════════► PUBLIC          │
│                                                      │
│  sk, rho                                            │
│       │                                              │
│       ▼                                              │
│  nullifier = Poseidon2(3, sk, rho)                  │
│       │                                              │
│       ├──────────────────────────────► PUBLIC        │
│       │                                              │
│       ▼                                              │
│  ┌───────────────────────────┐                      │
│  │ SMT Non-Membership Proof  │◄── nullifier_path    │
│  │ (not yet spent)           │                      │
│  └──────────┬────────────────┘                      │
│             │                                        │
│             ▼                                        │
│  nullifier_root ═══════════════════► PUBLIC          │
│                                                      │
└─────────────────────────────────────────────────────┘

Security Properties

  • Privacy: No observer can link a nullifier to the original note commitment
  • Soundness: A valid proof guarantees the note exists and hasn't been spent
  • Binding: The nullifier is deterministically derived from sk and rho, so the same note always produces the same nullifier (preventing double-spend)

Project Status

  • Architecture design
  • Noir circuit implementation
  • Rust test fixture generation
  • Integration tests with noir-sparse-merkle
  • Gas benchmarks
  • Security review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages