Skip to main content

Spend Authorization

Value conservation (the binding signature) is not enough: a malicious party who saw a note's public data could still try to spend it. Spend authorization proves the note's owner approved this specific spend. In Platus this is a BabyJubJub Schnorr signature, rerandomized per input and verified inside the Action circuit.

Rerandomization: hiding the key

Publishing the owner's verifying key ak on every spend would link all of a user's spends. Platus rerandomizes it per input:

rk=ak+αG,rsk=ask+α(modr)rk = ak + \alpha\cdot G, \qquad rsk = ask + \alpha \pmod r

with a fresh random α per input. rk = rsk·G, so a signature under rsk verifies against rk — but rk is unlinkable to ak because α is fresh. The circuit derives rk from the private (ak, α) (it is not a free public input), and rejects α = 0 (which would republish ak verbatim).

The signature, verified in-circuit

The Schnorr equation is s·G = R + e·rk, with a Poseidon2 challenge:

e=low_251(Poseidon2(SPEND_AUTH_DOMAIN,Rx,Ry,rkx,rky,authCtx))e = \text{low\_251}\big(\text{Poseidon2}(\text{SPEND\_AUTH\_DOMAIN}, R_x, R_y, rk_x, rk_y, \text{authCtx})\big)

The SDK signs s=rnonce+ersk(modr)s = r_{nonce} + e\cdot rsk \pmod r, rejection-sampling the nonce until e ∈ [1, r) (the 251-bit reduction can exceed r ≈ 2^{250.6} ~25% of the time). The circuit (verify_spend_auth):

  • recomputes e identically, asserts e and s are canonical BJJ scalars,
  • subgroup-checks rk and R,
  • checks s·G = R + e·rk as a single 2-point Straus MSM (s·G + e·(−rk) = R), and
  • gates the equality on enable_spend — a dummy input carries an in-range dummy signature that is not enforced, mirroring the on-chain enableSpends gate.