# Partial signature replay/frontrunning attack on session calls

Bug Deep Dive #30 · 27 December 2025

Bug Deep Dives · [The Contest Academy](https://0xsimao.com/the-contest-academy) · 0xSimao

---

| Title | Partial signature replay/frontrunning attack on session calls |
| Reward | $19609, Unique |
| Contest | Sequence - 7 October 2025 on Code4rena |
| Author | montecristo |
| Context | Sig Replay |

When a session call with BEHAVIOR_REVERT_ON_ERROR behavior fails, the entire execution reverts but the signature remains valid, since nonce is not yet consumed. Attackers can forge a valid partial signature from failed multi-call session, executing partial calls that were never intended to run independently.

Moreover, if an attacker has access to mempool, they can frontrun a multi-call session to execute only a subset of calls to either grief the legitimate call, or inflict financial damage to the wallet owners.

The Calls contract consumes nonces before signature validation and execution. When a call fails with BEHAVIOR_REVERT_ON_ERROR, the entire transaction reverts, including nonce consumption.

```solidity
36:  function execute(bytes calldata _payload, bytes calldata _signature) external payable virtual nonReentrant {
37:    uint256 startingGas = gasleft();
38:    Payload.Decoded memory decoded = Payload.fromPackedCalls(_payload);
39:
40:@>  _consumeNonce(decoded.space, decoded.nonce);
41:    (bool isValid, bytes32 opHash) = signatureValidation(decoded, _signature);
42:
43:    if (!isValid) {
44:      revert InvalidSignature(decoded, _signature);
45:    }
46:
47:@>  _execute(startingGas, opHash, decoded);
48:  }

62:  function _execute(uint256 _startingGas, bytes32 _opHash, Payload.Decoded memory _decoded) private {
63:    bool errorFlag = false;
64:
65:    uint256 numCalls = _decoded.calls.length;
66:    for (uint256 i = 0; i < numCalls; i++) {
...
85:      if (call.delegateCall) {
...
99:      } else {
100:        (success) = LibOptim.call(call.to, call.value, gasLimit == 0 ? gasleft() : gasLimit, call.data);
101:      }
102:
103:      if (!success) {
...
110:        if (call.behaviorOnError == Payload.BEHAVIOR_REVERT_ON_ERROR) {
111:@>        revert Reverted(_decoded, i, LibOptim.returnData());
112:        }
...
120:      emit CallSucceeded(_opHash, i);
```

At this point, the signature is publicly visible and still valid, because nonce usage is not recorded on Calls contract yet.

Session signatures are validated per-call using individual call hashes, which makes partial signature replay attack possible:

```solidity
136:      for (uint256 i = 0; i < callsCount; i++) {
...
162:        // Read session signature and recover the signer
163:        {
164:          bytes32 r;
165:          bytes32 s;
166:          uint8 v;
167:          (r, s, v, pointer) = encodedSignature.readRSVCompact(pointer);
168:
169:@>        bytes32 callHash = hashCallWithReplayProtection(payload, i);
170:          callSignature.sessionSigner = ecrecover(callHash, v, r, s);
171:          if (callSignature.sessionSigner == address(0)) {
172:            revert SessionErrors.InvalidSessionSigner(address(0));
173:          }
174:        }
175:
176:        sig.callSignatures[i] = callSignature;
```

The vulnerability also opens a second attack vector: front-running. A malicious party can watch the mempool for a multi-call session and deliberately front-run it to execute only a subset of the expected calls, either griefing the session or causing real financial damage to the wallet owner.

**Alpha:** this is a common bug in signature schemes — check that a batch signature cannot be partially executed. Note that the transaction reverts if the signature fails, so the individual signatures can be executed after the reverting transaction; front-running is not even required.

**Conclusion**

This finding would earn you **\$19609**, and needs only knowledge of the partial-execution attack vector.

[**Full Report**](https://code4rena.com/reports/2025-10-sequence#h-02-partial-signature-replayfrontrunning-attack-on-session-calls)\
[**Codebase**](https://github.com/code-423n4/2025-10-sequence/tree/b0e5fb15bf6735ec9aaba02f5eca28a7882d815d?tab=readme-ov-file)

---

Newer: [Static signatures bound to caller revert under ERC-4337, causing DoS](https://0xsimao.com/the-contest-academy/bug-deep-dive-31) · Older: [Chained signature with checkpoint usage disabled can bypass all checkpointer validation](https://0xsimao.com/the-contest-academy/bug-deep-dive-29)
