Aggregate multiple SPHINCS+ signature verifications into a single succinct proof using Plonky2 ZK circuits with cyclic recursion.
This project implements a full SPHINCS+ signature verification pipeline in Plonky2 arithmetic circuits over the Goldilocks field, replacing SHA-256 with Poseidon as the internal hash function. Multiple signature verifications are then aggregated via cyclic recursion, producing a single constant-size proof regardless of the number of signatures verified.
SPHINCS+ Signature (7,856 bytes)
|
v
sphincsplus-poseidon (native Rust verification + witness generation)
|
v
sphincsplus-circuits (Plonky2 circuit for single signature verification)
|
v
aggregation (cyclic recursion to aggregate N verifications)
|
v
Single succinct proof (constant size, verifiable in O(1))
| Parameter | Value |
|---|---|
| Security level | 128-bit |
SPX_N |
16 bytes (= 2 Goldilocks elements) |
SPX_FULL_HEIGHT |
63 |
SPX_D |
7 (hypertree layers) |
SPX_TREE_HEIGHT |
9 |
SPX_WOTS_W |
16 |
SPX_WOTS_LEN |
35 (32 message + 3 checksum) |
SPX_FORS_HEIGHT |
12 |
SPX_FORS_TREES |
14 |
SPX_BYTES |
7,856 bytes (signature size) |
| Poseidon | width=12, rate=8, capacity=4, 30 rounds |
crates/
sphincsplus-params/ Constants, address struct, types (no plonky2 dependency)
sphincsplus-poseidon/ Native Poseidon sponge + SPHINCS+ verification
sphincsplus-circuits/ Plonky2 circuits for signature verification
poseidon_sponge/ Custom sponge (capacity-first, XOR-absorb)
address/ SPHINCS+ address manipulation
thash/ Tweakable hash
fors/ FORS verification (14 trees x 12 levels)
wots/ WOTS+ chain verification (35 chains x 15 steps)
merkle/ Merkle authentication path verification
hash_message/ Message hashing with index extraction
verification/ Full signature verification circuit
aggregation/ Cyclic recursion for proof aggregation
cli/ CLI tool (placeholder)
The SPHINCS+ specification uses a sponge construction that differs from Plonky2's built-in:
| Plonky2 Built-in | SPHINCS+ | |
|---|---|---|
| Capacity position | state[8..12] |
state[0..4] |
| Rate position | state[0..8] |
state[4..12] |
| Absorb mode | Overwrite | XOR (field addition) |
| Domain separation | None | Tag in state[0] |
We use builder.permute::<PoseidonHash>() to access the raw Poseidon permutation (which is identical in both cases) and build the SPHINCS+-compatible sponge on top.
Each WOTS+ chain applies 0–15 hash steps depending on the message. All 15 steps are unrolled in the circuit with one-hot encoding of the chain length and cumulative-sum is_active flags, using builder._if() for conditional application.
The aggregation circuit maintains:
- Running hash accumulator:
H(prev_acc || pk_hash || msg_hash) - Signature count: incremented per aggregation step
Each step verifies a new inner proof (single SPHINCS+ verification) and optionally the previous aggregation proof. Uses Plonky2's conditionally_verify_cyclic_proof_or_dummy for the base case.
Requires Rust nightly:
cargo build --workspace# All tests (fast, use release mode)
cargo test --workspace --release
# Individual crate tests
cargo test -p sphincsplus-poseidon --release # 13 tests: native verification + C cross-validation
cargo test -p sphincsplus-circuits --release # 12 tests: all circuit modules
cargo test -p aggregation --release # 2 tests: cyclic recursion (single + two signatures)| Crate | Tests | Description |
|---|---|---|
sphincsplus-params |
1 | Parameter derivation |
sphincsplus-poseidon |
13 | Poseidon permutation, sponge, thash, full signature verify, C cross-validation |
sphincsplus-circuits |
12 | Poseidon sponge circuit, address, thash, FORS, WOTS+, Merkle, hash_message |
aggregation |
2 | Single and multi-signature cyclic aggregation |
- plonky2 (branch:
wasm) — ZK proof system over Goldilocks field - hashbrown — HashMap compatible with plonky2 internals
See repository license.