Replay Protection
Replay Protection
In a zero-knowledge system where trade intents are submitted off-chain and only proven on-chain during settlement, preventing intent reuse becomes critical. Without proper replay protection, a malicious relayer or actor could attempt to resubmit the same valid proof multiple times, settle a trade more than once, or inject stale intents into future matching rounds.
SnarkSide enforces strict replay protection using a multi-layered cryptographic commitment system composed of:
Salted intent hashes
Unique per-intent nullifiers
A globally-verified on-chain nullifier set
This ensures that every trade intent — no matter how private or encrypted — can only be used once, and only once, in a successful settlement. Any attempt to reuse an old intent results in proof rejection at the protocol boundary.
Why Replay Protection Matters
Traditional transaction-based systems naturally prevent replays via nonces and state-based mutation. Once a wallet submits a transaction, its internal nonce increments and further use of that exact message is rejected.
However, SnarkSide’s architecture is intentionally stateless and does not tie intents to wallet addresses. This means:
There is no native account to track nonces
There is no storage slot that enforces uniqueness
There are no transactions being executed at the time of submission
As such, replay protection must be implemented cryptographically and enforced at proof verification time — not transaction submission.
Cryptographic Components
Replay protection is achieved using the following layered structure.
1. Salt
Each intent includes a high-entropy salt field — a random 256-bit scalar value generated locally during intent creation.
The salt is included in the Poseidon commitment used to produce the intent_commitment:
intent_commitment = Poseidon(side, notional, slippage, leverage, expiry, salt, margin_commitment, nullifier)This ensures that:
Even if a user submits two intents with identical trade parameters, their commitment hashes will differ
Relayers cannot pattern-match across submissions
Correlation of similar orders is computationally infeasible
The salt prevents semantic intent collisions, ensuring that every commitment is unique at the cryptographic level.
2. Nullifier
Each intent includes a nullifier, which serves as its global replay guard.
It is constructed as:
nullifier = Poseidon(stealth_key, intent_salt)Where:
stealth_keyis a user-generated keypair not linked to their walletintent_saltis a private, per-intent random scalar
The nullifier is never revealed directly until settlement. At the time of match settlement, the nullifier is:
Exposed as a public input
Inserted into the global
nullifierSetcontractChecked for duplication
If the nullifier is already present in the set, the proof is rejected and the transaction reverts.
The cryptographic construction ensures that:
Only the holder of the private
stealth_keycan produce a valid proof with that nullifierNullifiers are not guessable from previous trades
The same stealth key used in multiple intents results in different nullifiers due to salt variation
This model is analogous to nullifiers in Zcash and Semaphore, ensuring non-linkable single-use action boundaries.
3. Global Nullifier Set (On-chain)
The SnarkSide settlement contract maintains a global nullifier Merkle set, representing all intents that have been successfully settled and consumed.
During settlement:
Each intent’s nullifier is passed as a public input to the verification circuit
The contract checks whether it exists in the current
nullifier_rootIf not present, it is inserted as a leaf
If already present, the transaction reverts
This set forms the cryptographic backbone of replay defense. It ensures that:
Even valid proofs cannot be reused
No relayer or matcher can resettle an old trade
Each intent is consumed atomically and irrevocably
Because the nullifier set is Merkle-based, it supports:
Efficient proof-of-inclusion for external systems
Gas-efficient batching
Stateless validation by light clients and indexers
Nonce vs Nullifier: A Key Distinction
It is important to distinguish traditional nonce-based replay protection from SnarkSide’s nullifier-based system.
Identity-bound
Yes (wallet address)
No (stealth address)
State-dependent
Yes (stored per account)
No (computed per intent)
Deterministic
Yes
No (includes entropy)
Collision risk
High if mismanaged
Cryptographically negligible
Replay surface
Transactions
Proofs/Intents
SnarkSide cannot rely on nonce tracking due to its stateless architecture and emphasis on unlinkability. Nullifiers provide the necessary one-time-use guarantee while preserving privacy.
Integration in Circuits
The nullifier is treated as an input to the intent circuit and is also used as a constraint in the match circuit. Each circuit includes a hash check to ensure that:
The nullifier was computed correctly from the user’s stealth key and salt
It matches the intent commitment
It is not present in the global nullifier root
Example constraint snippet (Circom):
component hash = Poseidon(2);
hash.inputs[0] <== stealth_key;
hash.inputs[1] <== salt;
nullifier <== hash.output;The verifier contract then uses this nullifier to query the nullifierSet root and reject duplicates at runtime.
Summary
Replay protection in SnarkSide is enforced without requiring accounts, transaction history, or on-chain identity. Instead, it is implemented as a cryptographic guardrail embedded in the proof system itself, using:
Randomized salts to avoid hash collisions
Deterministic, unlinkable nullifiers to mark consumption
A global Merkle-based nullifier registry to enforce single use
This ensures that every intent — no matter how private, encrypted, or stateless — can only be used once, and never reused or settled twice. It is one of the most essential layers in the system's zero-trust architecture.
Last updated

