transaction2 (spend, upstream)#
Privacy Cash's circuit, unchanged: two inputs, two outputs, amounts range-checked on 248 bits, sum(inputs) + publicAmount = sum(outputs). Public inputs: root, publicAmount, extDataHash, two input nullifiers, two output commitments.
lock#
Spends two main-tree notes and creates one change note (main) and one locked note (stake). Proves: both inputs are in the tree and owned, their nullifiers are correct and different, sum(inputs) + publicAmount = change + denom with publicAmount = -fee, change on 248 bits, denom on 64 bits, lockEpoch on 32 bits.
Template Lock(26). Public inputs, in order:
rootpublicAmountextDataHashinputNullifier[2]changeCommitmentlockedCommitmentdenomlockEpoch
unlock#
Spends one locked note and creates one main note. Proves ownership and membership in the stake tree, the stake nullifier, lockEpoch < currentEpoch, and denom + publicAmount = outAmount. The size and the end epoch stay private.
Template Unlock(26). Public inputs, in order:
stakeRootcurrentEpochpublicAmountextDataHashnullifieroutputCommitment
claimStake#
Proves: I own a locked note that was already in the stake tree when epoch E opened (index < epochStartIndex) and whose lock covers E (E <= lockEpoch); here is rewardNullifier = Poseidon(signature, E); and the reward note holds floor(denom * rate / 2^32). No "not spent" proof is needed: a note locked before E with lockEpoch >= E could not be unlocked during E.
Template ClaimStake(26). Public inputs, in order:
stakeRootepochepochStartIndexraterewardNullifierrewardCommitmentextDataHash
claimVolume#
Spends up to four credits of epoch E (all different, each on 64 bits) and creates a reward note of floor(sum * rate / 2^32), which must be above zero (IsZero(reward) === 0).
Template ClaimVolume(26, 4). Public inputs, in order:
creditRootepochrateboostQ32boostRootcreditNullifier[N]rewardCommitmentextDataHash
Shared building blocks#
- Reward arithmetic:
product = reward * 2^32 + remwithremon 32 bits andrewardon 96 bits. Amount and rate are both below 2^64, so the product fits in the field. - Dummy inputs: an input of amount 0 has its Merkle check disabled (
enabled = 0), as in the core circuit; a non-zero amount outside the tree is refused (tested). - Path indices go through
Num2Bits(26)in the Merkle proof, which bounds them for the whole circuit.
Tested cheating inputs#
Each rule has a test with a cheating witness that must fail on the targeted constraint, among them: a negative change that wraps around the field to lock more than one has, a dummy input with a non-zero amount outside the tree, four empty credits, a reward that rounds to zero, a changed public input on a real proof.