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_key is a user-generated keypair not linked to their wallet

  • intent_salt is 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 nullifierSet contract

  • Checked 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_key can produce a valid proof with that nullifier

  • Nullifiers 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_root

  • If 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.

Property
Nonce-Based System
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