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