Circuits

3.1 General Safety Rule (Critical)

Public values are exposed as signal input …_pub and constrained to the computed internal signals:

anchorRoot_pub === merkle.root;

This avoids “unconstrained public output” vulnerabilities.

3.2 Shield

(public → private)

  • Purpose: Consume an escrowed deposit and mint up to OUT_MAX=2 private outputs (fee is allowed but we default to 0).

  • Parameters (v2): DEP_MAX=1, OUT_MAX=2.

  • Public signals:

    • out_commit_pub[2]

    • dep_ids_lo_pub[1], dep_ids_hi_pub[1], dep_amts_pub[1]

    • fee_pub, fee_recipient_pub

  • Checks:

    • Output commitments recomputed in‑circuit and must match.

    • Deposit pass‑through & value conservation in shares (sum(outputs) == sum(deposits) − fee).

    • Fee enforced to be zero unless you intentionally enable it.

  • Asset: outputs are asset‑bound (commits include assetId); you keep assetId private in shield, the pool infers asset from deposit.

3.3 Spend Anchor

(private → private/withdraw)

  • Purpose: Spend up to IN_MAX=3 inputs; produce up to OUT_MAX=2 outputs; optionally one withdrawal and a fee (both in shares).

  • Parameters (v2): IN_MAX=3, OUT_MAX=2, WDR_MAX=1.

  • Public signals:

    • anchorRoot_pub

    • nullifiers_pub[IN_MAX]

    • out_commit_pub[OUT_MAX]

    • wdr_to_pub[1], wdr_values_pub[1]

    • fee_pub, fee_recipient_pub

    • assetId_pub

  • Checks:

    • Each active input’s commitment recomputed from (value, owner, blind, assetId) and its Merkle path; all inputs must bind to the same anchorRoot_pub.

    • Output commitments recomputed and pad‑rules enforced.

    • Value conservation in shares: sum(inputs) == sum(outputs) + withdrawals + fee.

    • Nullifiers recomputed and exposed as public signals (replay‑proof).

    • No deposits in a spend—deposit arrays removed from v2.

Asset binding: assetId_pub is public and must match all in/out commitments; the pool enforces that any withdrawal/fee is in that same asset.


Last updated