Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how →
F-2026-0013·signature-validation

Signature validity is resolved from live state, so a code-bearing maker holds a revocable option on every order they sign

Fixednfterc-1155marketplace
TL;DR

Signatures are verified through live state, so a maker who acquires code between signing and settlement can switch to ERC-1271 and revoke an order a counterparty has already committed against.

Severity
LOW
Impact
LOW
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
LOW
02Section · Description

Description

_validateOrder resolves a maker's signature at settlement time through SignatureChecker.isValidSignatureNow:

solidity
if (!SignatureChecker.isValidSignatureNow(order.maker, digest, signature)) {
revert InvalidSignature(digest, order.maker);
}

OpenZeppelin selects the verification path from live state — signer.code.length == 0 chooses ECDSA, otherwise ERC-1271 — and the library's own header states the consequence:

Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus change through time. It could return true at block N and false at block N+1 (or the opposite).

The Order struct carries no field pinning which scheme the maker committed to, so for a code-bearing maker a signature is not a commitment but a standing offer they may withdraw at the moment of settlement. A contract maker whose isValidSignature reads a flag it owns can advertise at the top of the book, let a counterparty be matched, and flip the flag — one SSTORE — so the pair fails. The counterparty cannot cancel a matched order and is offline by design.

An EOA maker reaches the same position by submitting an EIP-7702 delegation, which makes code.length non-zero and invalidates all of their outstanding ECDSA signatures at once.

Rated Low because the running system's makers are reported as MPC EOAs with plain 65-byte ECDSA signatures, so the ERC-1271 path is contract-supported but unexercised today. It stops being latent if smart accounts are adopted, which is a documented pre-mainnet decision.

03Section · Impact

Impact

A code-bearing maker holds a revocable option on every order they have signed: they can advertise at the top of the book, let a counterparty be matched, and invalidate the signature at the moment of settlement. The counterparty cannot cancel a matched order and is offline by design, so they carry the failure. An EOA maker reaches the same position by submitting an EIP-7702 delegation, which invalidates all of their outstanding signatures at once.

04Section · Recommendation

Recommendation

Bind the validation scheme into the signature so it cannot change after signing:

diff
struct Order {
address maker;
Side side;
+ bool makerIsContract;
uint256 tokenId;
...
}

with ORDER_TYPEHASH, hashOrder and the JSON artifact updated to match, and in _validateOrder:

diff
-if (!SignatureChecker.isValidSignatureNow(order.maker, digest, signature)) {
+bool ok = order.makerIsContract
+ ? SignatureChecker.isValidERC1271SignatureNow(order.maker, digest, signature)
+ : ECDSA.recover(digest, signature) == order.maker;
+if (!ok) {
revert InvalidSignature(digest, order.maker);
}

The false branch removes revocability for the entire EOA maker population, which is the documented production case, and keeps working for a 7702-delegated EOA because ecrecover still resolves. The true branch leaves genuine contract makers on the revocable path, which is inherent to ERC-1271 and cannot be closed at this layer — bounding it there is an off-chain reputation question rather than a contract one.

05Section · Resolution

Resolution

The verification scheme is now a signed field of the order, so it is fixed at signing and cannot be swapped underneath an order a counterparty has already committed against. Verified at audit-v2.

06Section · Affected files

Affected files

  • src/PlakxioSettlement.sol#L605-L618 at commit 5c38893
Status
Fixed
F-2026-0013