skip to content
Skill Issue Dev | Dax the Dev
search
Part of series: relayerless-privacy

SPST: a self-paying shielded transaction model

Print view

Sections

In the previous post I argued that on account-model chains the fee paradox is what forces relayer dependence. The cleanest resolution — Approach A — extracts the transaction fee from inside the ZK proof itself. This post specifies that resolution.

The construction is called SPST (Self-Paying Shielded Transactions). It is the foundation that PPST, TAB, and UPEE build on. It also stands alone as a complete protocol for private value transfer with self-paying fees — the Solana analogue to Zcash’s Sapling spend description, but adapted to a smart-contract environment.

The setting

Work over a prime-order elliptic curve group G\mathbb{G} of order pp with two independent generators g,hGg, h \in \mathbb{G} for which no party knows loggh\log_g h. Let Fp\mathbb{F}_p denote the scalar field. We use:

  • Poseidon as the SNARK-friendly hash (width t=5t = 5, HADES rounds RF=8R_F = 8 full + RP=57R_P = 57 partial, S-box x5x^5, ~324 R1CS constraints per 2-to-1 compression).
  • PRF keyed by skFpsk \in \mathbb{F}_p, instantiated as a domain-separated Poseidon evaluation.
  • Groth16 over BN254 as the on-chain verifier (alt_bn128 syscalls on Solana).
  • Indexed Merkle Trees for nullifier non-membership (depth-32 over 254-bit values; ~10,948 R1CS constraints per non-membership proof, vs 82,296 for naive sparse Merkle).

Definitions

Definition 3.1 (Shielded Note). A shielded note is a tuple

note=(pk,v,ρ,r) \mathsf{note} = (\mathsf{pk}, v, \rho, r)

where pk=PRFsk(0)\mathsf{pk} = \mathsf{PRF}_{sk}(0) is the owner’s public spending key, v{0,,2641}v \in \{0, \ldots, 2^{64}-1\} is the note value, ρFp\rho \in \mathbb{F}_p is unique per-note serial randomness, and rFpr \in \mathbb{F}_p is the commitment trapdoor.

Definition 3.2 (Note Commitment).

cm  =  Poseidon(pk,v,ρ,r)    Fp. \mathsf{cm} \;=\; \mathsf{Poseidon}(\mathsf{pk},\, v,\, \rho,\, r) \;\in\; \mathbb{F}_p.

Note commitments are appended as leaves to a global Merkle tree T\mathcal{T} of depth d=32d = 32 (capacity 2322^{32} notes). The Merkle root at any epoch is rt\mathsf{rt}.

Definition 3.3 (Nullifier).

nf  =  PRFsk(ρ)  =  Poseidon(sk,ρ). \mathsf{nf} \;=\; \mathsf{PRF}_{sk}(\rho) \;=\; \mathsf{Poseidon}(sk, \rho).

Upon spending, nf\mathsf{nf} is published to a global nullifier set N\mathcal{N}. Double-spending is prevented by rejecting any transaction whose nf\mathsf{nf} already lives in N\mathcal{N}.

Definition 3.4 (SPST Transaction). With nn inputs and mm outputs:

tx  =  ({nfi}i=1n,  {cmj}j=1m,  rt,  f,  π) \mathsf{tx} \;=\; \bigl(\, \{\mathsf{nf}_i\}_{i=1}^{n},\; \{\mathsf{cm}_j\}_{j=1}^{m},\; \mathsf{rt},\; f,\; \pi \,\bigr)

where f{0,,2641}f \in \{0, \ldots, 2^{64}-1\} is the public fee and π\pi is a Groth16 proof of the SPST relation.

The validator accepts iff (i) π\pi verifies, (ii) rt\mathsf{rt} is a recent root, (iii) every nfiN\mathsf{nf}_i \notin \mathcal{N}, and (iv) ffminf \geq f_{\min}.

The SPST relation

The relation RSPST\mathcal{R}_{\mathsf{SPST}} is the set of (x,w)(x, w) pairs:

Public instance x=({nfi},{cmj},rt,f)x = \bigl(\{\mathsf{nf}_i\}, \{\mathsf{cm}_j\}, \mathsf{rt}, f\bigr).

Private witness w=({(notei,pathi,ski)},{notej})w = \bigl(\{(\mathsf{note}_i, \mathsf{path}_i, sk_i)\}, \{\mathsf{note}'_j\}\bigr).

Eight constraints, all enforced by the circuit:

#NameConstraint
C1Spending key validitypki=PRFski(0)\mathsf{pk}_i = \mathsf{PRF}_{sk_i}(0)
C2Nullifier correctnessnfi=PRFski(ρi)\mathsf{nf}_i = \mathsf{PRF}_{sk_i}(\rho_i)
C3Input commitment well-formednesscmi(in)=Poseidon(pki,vi,ρi,ri)\mathsf{cm}^{(\mathsf{in})}_i = \mathsf{Poseidon}(\mathsf{pk}_i, v_i, \rho_i, r_i)
C4Merkle membershipMerkleVerify(rt,cmi(in),pathi)=1\mathsf{MerkleVerify}(\mathsf{rt}, \mathsf{cm}^{(\mathsf{in})}_i, \mathsf{path}_i) = 1
C5Output commitment well-formednesscmj=Poseidon(pkj,vj,ρj,rj)\mathsf{cm}_j = \mathsf{Poseidon}(\mathsf{pk}'_j, v'_j, \rho'_j, r'_j)
C6Value conservation with feeivi=jvj+f\sum_i v_i = \sum_j v'_j + f
C7Non-negative output valuesvj{0,,2641}v'_j \in \{0, \ldots, 2^{64}-1\} (bit decomposition)
C8Non-negative feef{0,,2641}f \in \{0, \ldots, 2^{64}-1\}

C6 is the load-bearing constraint. It is what makes the transaction self-paying: the prover can only produce a valid proof if the input notes’ values sum to exactly the output values plus the fee.

The self-paying property (Theorem 3.1)

Theorem. Let tx=({nfi},{cmj},rt,f,π)\mathsf{tx} = (\{\mathsf{nf}_i\}, \{\mathsf{cm}_j\}, \mathsf{rt}, f, \pi) be a valid SPST transaction. Then:

  1. The fee ff is funded entirely from consumed shielded notes.
  2. No external account, relayer, or gas sponsor is required.
  3. Validators extract ff as inclusion compensation without learning the private inputs/outputs beyond ff itself and the validity of π\pi.

Proof sketch. (1) follows directly from C6. (2) follows because tx\mathsf{tx} is a self-contained data structure that any party can broadcast; the on-chain verifier decrements the privacy program’s lamport reserve by ff and credits the validator. (3) is the perfect zero-knowledge property of Groth16: the validator sees ff as a public input but learns nothing about viv_i or vjv'_j.

The full proof is in §3.1.3 of the paper. The takeaway: on Solana, the privacy program’s PDA holds a reserve. Each shield deposit increments it. Each SPST transaction’s proof authorises the validator to take ff from it. Replenishment is automatic.

Theorem 3.2 — Balance / value conservation

Statement. No PPT adversary can produce a valid SPST transaction that creates value (i.e., one for which jvj+f>ivi\sum_j v'_j + f > \sum_i v_i) except with negligible probability.

The proof gives two complementary arguments — one from the SNARK’s knowledge soundness, one from an independent Pedersen commitment cross-check that provides defense in depth.

Argument 1 (SNARK soundness)

C6 enforces ivi=jvj+f\sum_i v_i = \sum_j v'_j + f over Fp\mathbb{F}_p. C7 and C8 enforce vj,f[0,264)v'_j, f \in [0, 2^{64}). With at most n216n \leq 2^{16} inputs each bounded by 2642^{64}, ivi<280p2254\sum_i v_i < 2^{80} \ll p \approx 2^{254} — so field arithmetic faithfully represents integer arithmetic and no modular wraparound is possible.

By Groth16 knowledge soundness in the AGM, an extractor E\mathcal{E} can recover the witness ww^* satisfying all constraints C1–C8. C6 in the extracted witness gives ivi=jvj+f\sum_i v_i = \sum_j v'_j + f as an integer equation. Contradiction with the assumed inflation.

Argument 2 (Pedersen cross-check)

As defense in depth, attach Pedersen value commitments to each note. With Cin,i=vig+ri(vc)hC_{\mathsf{in},i} = v_i \cdot g + r^{(\mathsf{vc})}_i \cdot h and Cout,j=vjg+rj(vc)hC_{\mathsf{out},j} = v'_j \cdot g + r^{(\mathsf{vc})}_j \cdot h, the verifier checks

iCin,i  =  jCout,j  +  fg  +  rΔh\sum_i C_{\mathsf{in},i} \;=\; \sum_j C_{\mathsf{out},j} \;+\; f \cdot g \;+\; r_\Delta \cdot h

where rΔ=iri(vc)jrj(vc)r_\Delta = \sum_i r^{(\mathsf{vc})}_i - \sum_j r^{(\mathsf{vc})}_j.

Suppose an adversary passes this check but with jvj+fivi\sum_j v'_j + f \neq \sum_i v_i. Let δ=ivijvjf0\delta = \sum_i v_i - \sum_j v'_j - f \neq 0. Then δg=rΔh\delta \cdot g = r'_\Delta \cdot h for some rΔr'_\Delta, which yields loggh=δ/rΔ\log_g h = \delta / r'_\Delta — contradicting DLOG. ∎

Theorem 3.3 — Double-spend resistance

Game. A\mathcal{A} may adaptively deposit and spend; wins if it produces two accepted transactions consuming the same note note\mathsf{note}^*.

Cases.

  • Case 1: The two transactions publish the same nullifier. Rejected by the protocol’s nullifier-set check.
  • Case 2: They publish different nullifiers nfnf\mathsf{nf} \neq \mathsf{nf}' but consume the same note. By C4 both proofs authenticate the same commitment cm\mathsf{cm}^*. By C1 we have pk=PRFsk(0)=PRFsk(0)\mathsf{pk}^* = \mathsf{PRF}_{sk^*}(0) = \mathsf{PRF}_{sk'}(0).
    • If sk=sksk^* = sk', then nf=nf\mathsf{nf} = \mathsf{nf}'. Contradiction.
    • If sksksk^* \neq sk', then Poseidon(sk,0)=Poseidon(sk,0)\mathsf{Poseidon}(sk^*, 0) = \mathsf{Poseidon}(sk', 0) — a collision in Poseidon. Reduces to collision resistance.

Both cases reach a contradiction. ∎

Theorem 3.4 — Transaction unlinkability

Statement. Under perfect zero-knowledge of Groth16 and computational hiding of Pedersen commitments under DDH, the SPST scheme satisfies transaction unlinkability: no PPT adversary can determine which input notes fund which output notes with non-negligible advantage.

Proof structure. Hybrid argument:

  • Hybrid 0: real game.
  • Hybrid 1: replace all Groth16 proofs with simulated proofs. By perfect ZK of Groth16, indistinguishable.
  • Hybrid 2: in the simulated view, the multisets of nullifiers, commitments, roots, fees are identical for both branchings of the challenge. The fee is identical by construction. Each cmj=Poseidon(pkj,vj,ρj,rj)\mathsf{cm}_j = \mathsf{Poseidon}(\mathsf{pk}'_j, v'_j, \rho'_j, r'_j) with fresh random rjr'_j is computationally indistinguishable from a uniform field element. Each nullifier nfi=PRFski(ρi)\mathsf{nf}_i = \mathsf{PRF}_{sk_i}(\rho_i) with unique ρi\rho_i is pseudorandom. The Pedersen value commitments are computationally hiding under DDH.

Result: AdvAnegl(λ)\mathsf{Adv}_{\mathcal{A}} \leq \mathsf{negl}(\lambda). ∎

Theorem 3.5 — Non-malleability

Statement. No PPT adversary can take a valid SPST transaction and produce a distinct valid transaction with altered public inputs (e.g., a different fee), except with negligible probability.

Proof. Relies on the simulation-extractability of Groth16 in the Random Oracle Model — the Bowe-Gabizon construction (2019), refined by Ràfols-Baghery-Pindado (2023). An adversary mauling the proof to alter ff would need to extract a witness with a different ff' satisfying C6, but C6 plus the unchanged input commitments and output commitments uniquely determines ff. Contradiction.

Circuit complexity

For an SPST circuit with nn inputs and mm outputs:

Ctotal    11,500n  +  452m  +  64.C_{\mathsf{total}} \;\approx\; 11{,}500 \cdot n \;+\; 452 \cdot m \;+\; 64.

A canonical 2-in / 2-out transaction:

C2,2  =  23,000+904+64    24,000 constraints.C_{2,2} \;=\; 23{,}000 + 904 + 64 \;\approx\; 24{,}000 \text{ constraints}.
ComponentPer-inputPer-outputSubtotal
Note commitment (input)388388n388n
Merkle path (depth 32)10,40010,400n10{,}400n
PRF evaluations (pk + nf)648648n648n
Range proof (input value)6464n64n
Note commitment (output)388388m388m
Range proof (output value)6464m64m
Fee range proof64

On commodity hardware (Apple M2, 8-core), Groth16 proving for ~24,000 constraints takes 0.5–1.5 seconds with arkworks or snarkjs. Proof size is 128 bytes compressed (BN254 G1/G2 compression on Solana). Verification is ~150,000–200,000 CU via sol_alt_bn128_* syscalls.

AspectProsCons
Proof size 128 bytes (BN254 compressed) Fits the 1,232-byte Solana limit with 1,100+ bytes for other tx data
Verification cost < 200,000 CU (≈ $0.02 USD) ~14% of the 1.4M CU per-tx budget; leaves room for nullifier checks + state updates
Prover time (M2 laptop) 0.5–1.5 s for 2-in / 2-out Linear in circuit size; recursive proofs amplify this
Trusted setup Per-circuit Groth16 MPC (Powers of Tau-style) Separate ceremony per circuit shape; PLONK alternative awaits SIMD-0302 G2 syscall

What SPST is not

SPST handles private value transfer — the Solana analogue of Zcash’s Sapling. It does not:

  • Handle private computation. The next post (PPST) extends the relation to arbitrary arithmetic circuits over private state.
  • Hide the submitter. The transaction submitter is still publicly identified by their Ed25519 signature on the wrapping Solana transaction. TAB addresses that.
  • Hide the fee amount. ff is necessarily public for validator compensation.

But it does the load-bearing thing: the user becomes self-sovereign with respect to fee payment. Combined with TAB and PPST, that’s the whole framework.

Bibliography

Previous: The fee paradox ← · Next: PPST: private programmable state →

Hire me — book a 30-min call $ book →