Architecture
2.1 Poseidon Hashing & Merkle
Hash: Poseidon2 (field‑friendly).
Tree: Incremental append‑only Merkle tree, depth 32.
Path convention: pathIndex[i] == 0 means the leaf is left child (sibling on right); 1 means leaf is right child.
Root recency: pool keeps a ring of the last RECENT_ROOTS (default 64) roots; spends must reference a recent root.
2.2 Note, Nullifier & Commitment (v2)
We bind the asset to every note and compress secrets to reduce size and improve privacy:
Constants
DOMAIN_NOTE = 11
DOMAIN_NULL = 12
Per‑note randomizer: blind (unique per note)
Nullifier (per note):
nullifier = Poseidon([ nullifier_secret, blind, DOMAIN_NULL ])
Mix (compress (blind, nullifier)):
mix = Poseidon([ blind, nullifier ])
Commitment (asset‑bound):
commitment = Poseidon([ valueShares, ownerField, mix, assetId, DOMAIN_NOTE ])valueShares: amount in shares (not raw token units).
ownerField: note “owner tag” (any field value; application maps it to an in‑app key).
assetId: ERC‑20 address (field‑encoded), so notes are unambiguously scoped per asset.
Why this matters:
The receiver learns nullifier_secret from the memo, can precompute the nullifier off‑chain for quick “spent?” checks.
On‑chain observers cannot infer values/ownership.
Binding assetId removes cross‑asset ambiguity and future‑proofs multi‑token pools.
Last updated