Action Circuit
The Action circuit is the heart of Platus. Every private transaction is a sequence of Actions, and each Action is one UltraHonk proof. All Actions share one verification key and emit one public field — the two properties that make the recursive aggregation tree uniform.
What one Action proves
A single Action spends up to two input notes and creates up to two output notes, under one owner key triple (ak, nk, rivk) and one net value commitment cv_net:
Per-input constraints
For each input, gated on enable_spend:
| # | Constraint | Purpose |
|---|---|---|
| 1 | v_old·(1 − enable_spend) == 0 then v_old < 2^128 | dummy ⇒ zero value; real value in range |
| 2 | recompute cm_old = action_note_commit(...) | bind the witnessed note fields |
| 3 | gd_old in subgroup; pkd_old == ivk·gd_old (gated) | ownership — prover holds the ivk that owns the note |
| 4 | nf == derive_nullifier(nk, ρ_old, ψ_old, cm_old) | the published nullifier is correct (always enforced) |
| 5 | commitment_root == binary_merkle_root(cm_old, pos, sibs) (gated) | membership in a depth-16 tree |
| 6 | rk = ak + α·G | rerandomized spend-auth key returned to main |
Per-output constraints
For each output, gated on enable_output, with rho_new = the paired input's nullifier:
| # | Constraint | Purpose |
|---|---|---|
| 1 | v_new·(1 − enable_output) == 0 then v_new < 2^128 | dummy ⇒ zero value; real value in range |
| 2 | gd_new, pkd_new in subgroup; ψ_new ≠ 0 | no fund lockup; no ECDH coset leak; preserve nullifier entropy |
| 3 | cm_new == action_note_commit(..., rho_new, ...) (gated) | output commitment is correct, with chained ρ |
| 4 | esk ≠ 0, canonical; epk == esk·gd_new (always enforced) | scanner can always recompute the shared secret |
Spend-authorization (in main.nr)
action() returns (rk1, rk2); main then calls verify_spend_auth for each input, gated on enable_spend, against the bundle's auth_ctx:
e = low_251(Poseidon2(SPEND_AUTH_DOMAIN, R.x, R.y, rk.x, rk.y, auth_ctx))
verify s·G == R + e·rk # as one 2-point Straus MSM: s·G + e·(−rk) == R
auth_ctx is folded into the digest, so the on-chain publicsDigest check enforces that the proof verified the signatures against the same context the contract reconstructs. Full rationale: Spend Authorization.