Executive Summary
Extractable value from transaction ordering - front-running, sandwich attacks, and back-running - is a structural problem for any on-chain trading venue that broadcasts pending transactions before they execute. JIL Sovereign's CommitReveal contract removes the information an attacker needs to profit from ordering by splitting every gated transaction into two phases: an opaque commitment phase, where only a cryptographic hash is visible, and a delayed reveal phase, where the transaction executes exactly as originally committed.
The design combines three mechanisms that are each individually known in the literature but are bound together here into a single enforceable contract: (1) a keccak256 commitment binding transaction content and ordering to a specific block before any content is visible; (2) an economically bonded, time-bounded reveal window that prevents both premature reveal and indefinite griefing; and (3) an authorized-executor allowlist that restricts what a revealed transaction is permitted to call, closing off the arbitrary-call surface that a naive commit-reveal reveal step would otherwise open.
Problem Statement
On a transparent mempool, a pending trade is visible to every observer before it is included in a block. A searcher or block builder can insert their own transactions immediately before and after the victim's trade - buying ahead of it to capture the price impact, then selling into it - extracting value the original trader never agreed to give up. This class of attack, broadly termed MEV (maximal extractable value), degrades execution quality on every AMM-based venue that does not specifically defend against it.
Why Generic Mitigations Fall Short
- Private relay / "protected" RPC submission: Moves trust to a third-party relay operator who can still see and reorder transactions before they reach the chain; the protection is a promise, not a contract-enforced constraint.
- Plain slippage limits: Bounds the damage of a sandwich attack but does not prevent it - the attacker still profits, just less per trade, and the defense does nothing against pure front-running of a profitable signal.
- Naive commit-reveal without an executor allowlist: Solves the visibility problem but turns the reveal call into an arbitrary
(target, calldata)dispatcher, which is itself a dangerous primitive if any contract can be named as the target.
Technical Architecture
Commit-Reveal Lifecycle
| Phase | Caller Action | On-Chain State |
|---|---|---|
| Commit | commit(commitment) with an optional bond as msg.value | Stores sender, commitmentHash, commitBlock, and a computed expireBlock = commitBlock + commitDelay + revealWindow |
| Reveal | reveal(commitId, data, secret) after commitDelay blocks have elapsed | Recomputes keccak256(data, secret); must equal the stored hash or the call reverts with InvalidCommitment |
| Execute | Contract-internal, same transaction as reveal | Decodes (target, callData) from data; requires authorizedExecutors[target] == true or reverts |
| Expire | claimExpired(commitId), callable by anyone after expireBlock | Marks the commitment expired and returns any bond to the original sender |
Commitment Binding
The commitment hash is computed client-side as keccak256(abi.encode(target, callData), secret) and submitted opaquely. Because the hash is a one-way function of the exact target, calldata, and a random secret, no on-chain observer can recover the transaction's economic content - which pool, which side, what size - from the commitment alone. The commit ID itself is further derived from keccak256(sender, commitment, block.number, nonce), giving every commitment a unique, replay-resistant identifier tied to the block in which it was made.
Reveal-Window Economics
Reveal is only valid in the half-open window [commitBlock + commitDelay, expireBlock]. Revealing too early (RevealTooEarly) or too late (RevealTooLate) both revert, and a failed swap execution automatically refunds the locked bond before reverting the outer call. The contract enforces a hard floor on parameters (commitDelay >= 1, revealWindow >= 10) so an administrator cannot configure the window down to zero and silently disable the protection.
Executor Allowlist as a Second Control Plane
Concealing a transaction's content is only half of the protection - the reveal step still has to execute an arbitrary call somewhere. JIL's implementation binds every revealed call to a registry of pre-approved executor contracts, set through setExecutorAuthorization and gated by DEFAULT_ADMIN_ROLE. This means the commit-reveal contract cannot be used to launder an arbitrary call through concealment; it can only be used to conceal calls into contracts the operator has already vetted (the AMM router, the batch-auction settlement contract, and similar trade-execution surfaces).
| Control | Enforcement Point | Failure Mode Blocked |
|---|---|---|
| Executor allowlist | reveal(), checked before the external call | Reveal used as a generic arbitrary-call relay |
| Reentrancy guard | reveal(), claimExpired() | Recursive reveal/claim during the external call |
| Pausable circuit breaker | commit(), reveal() | New commitments or reveals during an active incident |
Prior Art Differentiation
| Approach | Ordering Concealed? | Trust Assumption | Executor-Scoped? |
|---|---|---|---|
| Private RPC / "MEV-protect" relay | Partial (relay sees content) | Off-chain relay operator | N/A |
| Slippage-limit-only defense | No | None (attack still occurs) | N/A |
| Generic commit-reveal (academic) | Yes | Fully on-chain | Typically no |
| JIL Sovereign | Yes | Fully on-chain, bonded | Yes - allowlisted targets only |