EVM patterns
ERC1271 Signature Validation: Wallet State and Response Evidence
Contract wallets decide whether a signature is authorized in the state where they are checked. ERC1271 signature validation requires the expected return value, current wallet policy and a separately enforced application action.

Key facts
- Original response grid
- 24 distinct call-outcome/payload pairs from 30 labeled combinations
- Pinned helper
- 2 accepted pairs and 22 rejected pairs in OpenZeppelin v5.4.0
- Prefix comparison
- 7 accepted pairs, including 5 outside the pinned helper predicate
- Evidence boundary
- Synthetic predicates, no EVM or signature cryptography executed
Establish what the signature proves
A successful wallet check is an observation about a particular contract state.
That distinction is the starting point for ERC1271 signature validation. The same message and signature bytes can produce a different answer after the wallet changes its authorization policy. OpenZeppelin's fixed v5.4.0 verifier explicitly documents that possibility in either direction. A stored success flag does not become permanent permission to settle an order.
The signer is the wallet contract. An underlying owner may supply cryptographic material, but recovering that owner's address does not by itself prove that the wallet currently authorizes the action. Its policy may require additional signers, a permitted session or some other state. An application that replaces the wallet's answer with its own owner-address comparison has changed the authorization system.
ERC1271 defines the contract signature interface isValidSignature(bytes32,bytes). Successful validation returns the magic value 0x1626ba7e. The function must not modify state and must allow external calls. The standard leaves the actual signing policy to the implementation. It does not prescribe a universal signature length, a wallet ownership model or the business meaning of the supplied hash.
| Observation | What it establishes | What remains unresolved |
|---|---|---|
| Recovered owner address | A cryptographic signer under the chosen scheme | The wallet's current authorization policy |
| Successful contract check | Wallet acceptance in the observed execution context | Application replay and action constraints |
| Previously cached acceptance | A historical verification result | Acceptance at settlement |
| Expected return bytes | The verifier's response predicate passed | Whether the intended message was hashed |
Consider an order created before an owner rotation. The application may still possess the original bytes and an earlier positive result. The relevant question at fulfillment is whether the current wallet accepts that exact order hash under the intended policy. This is a constructed integration scenario, not a report of a particular exploit. It identifies the evidence that would be needed to approve settlement.
The ERC1271 specification defines the interface. Our signature replay analysis addresses a separate problem: preventing repeated or unintended use of an otherwise accepted message. Both boundaries belong in the review. Neither can substitute for the other.
Original response grid for a fixed verifier
Method
We reproduced the return-data predicate in OpenZeppelin Contracts v5.4.0 using a complete finite set of supplied call outcomes. This experiment asks which response bytes that particular helper accepts. It does not execute a wallet or establish what every ERC1271 implementation should return through an arbitrary low-level adapter.
- Source
- Fixed
SignatureChecker.solfrom OpenZeppelin Contractsv5.4.0, fetched on . - Population construction
- Both call-success flags, return lengths
0,4,31,32and64bytes, and canonical magic, wrong-selector or dirty-padding word shapes. - Exclusion rule
- Equal payloads produced by different labels at short lengths were deduplicated. The initial
30labeled combinations became12distinct payloads and24distinct success/payload pairs. - Artifacts
seo/research/integration-boundaries-2026-10-11/support-signatures/measurements.py,results.jsonanderc1271-result-grid.csv. These are repository artifacts, not public downloads.
OpenZeppelin's helper performs a low-level staticcall, requires successful execution and checks that at least a complete ABI word was returned. It then compares the first word with the selector extended by zero padding. Merely seeing the selector at the start of a short byte string is a different acceptance policy.
Results
| Predicate | Accepted pairs | Pairs outside pinned acceptance |
|---|---|---|
| Pinned helper | 2 of 24 | 0 |
| Successful call alone | 12 of 24 | 10 |
| Successful four-byte prefix match | 7 of 24 | 5 |
Accepted responses had canonical first words and lengths of 32 or 64 bytes. Extra trailing data did not change this first-word predicate. A raw four-byte selector did not pass it. Nonzero padding inside the first ABI word also failed the full-word comparison. These are implementation-specific outcomes; the table is not an estimate of wallet compatibility or malformed responses on a network.
Limits
- No EVM or signature algorithm ran. Call outcomes and returned bytes were supplied as artificial inputs.
- The grid covers the declared response shapes, not every possible byte sequence or adapter.
- Acceptance says nothing about message semantics, replay protection or the wallet's internal authorization logic.
A separate complete Boolean observation grid contains 4 historical/current pairs. Reusing the historical answer creates one modeled false acceptance and one modeled false rejection when the answers differ. That arithmetic illustrates why observation time belongs in the record. It does not count owner changes, measure block transitions or estimate their frequency.
Preserve the wallet state behind acceptance
A verification record needs enough context to distinguish a wallet-policy change from a broken encoding. Record the chain, wallet address, exact hash, signature bytes, verifier version and block reference. Preserve the raw call outcome separately from the interpretation. If a provider could not obtain historical state, a result from the latest block must not be labeled as a historical result.
This is an evidence practice derived from state-dependent validation. It is not an extra ERC1271 interface requirement. The goal is to let a reviewer reproduce the question the application actually asked, then compare that observation with the state in which it plans to use the answer.
- Message record
- The exact encoded action and the hash supplied to the wallet.
- Observation record
- The block reference, low-level outcome and raw response from the earlier check.
- Consumption record
- The authorization and application constraints enforced when the action executes.
A chain reorganization creates another reason to retain the block hash alongside a human-readable block number. An application should decide when an observation is sufficiently stable for its own workflow. ERC1271 does not supply a universal finality rule. The record can state that a result is provisional without inventing certainty about a chain's settlement conditions.
Caching still has a role. A user interface can reuse a recent observation to explain why an order looked acceptable, provided it names that observation and the remaining check. A settlement contract should enforce the intended current authorization. If the product deliberately grants durable application permission after a wallet check, the review must examine that separate grant and its revocation mechanism.
Wallet upgrades belong in the same change log as owner or policy changes. The address alone may remain constant while the validation implementation changes. Capture the relevant implementation and configuration evidence according to the actual wallet architecture; do not assume that every contract signer is a proxy.
A verification service also needs to state whose execution context it reproduced. A wallet policy that consults external contracts can encounter different surrounding state even when its own address and bytecode have not changed. Archive the call parameters and the assumptions behind the observation. If reproducing the required context is impossible, describe the missing evidence rather than translating a transport error into an invalid-signature verdict.
For a reconciliation example, imagine two services reporting opposite answers for the same bytes. Compare their chain identifiers and block references before examining signature parsing. Then compare the hash and the deployed verifier version. An earlier positive answer and a later negative answer may both be accurate observations. The product should investigate the policy transition, not select whichever service produces the preferred answer.
Bind the signed action before consuming it
A wallet can correctly approve the wrong hash. The application is responsible for deciding what the hash means. EIP712 provides structured message hashing and domain fields, but explicitly leaves application replay protection outside its scope. A valid contract response therefore cannot rescue an action schema that omits its intended recipient, amount, operation or freshness conditions.
Start with the business action, then derive the message. For a settlement authorization, identify which contract consumes the order, who receives value, which assets move and what bounds the user approved. The exact fields depend on the protocol. A reusable login message and a token-transfer authorization have different consequences and should not share an ambiguous purpose.
- Write the action's permitted effect in ordinary language before choosing its encoded fields.
- Compare the frontend's displayed values with the hash recomputed by the consuming contract. Include every field that the execution relies on.
- Check the domain against the intended chain and verifying contract, using the selected signing standard's actual encoding.
- Define how the application consumes or invalidates authorization and how a previously accepted message is rejected after that event.
- Test a neighboring action with one changed material field. Keep a valid control case so an unrelated revert cannot be mistaken for correct binding.
The EIP712 source describes domain separation. It does not require every application to adopt an identical nonce structure. Ordered nonces, unordered invalidation and state-dependent one-time effects can support different workflows. Review the implemented mechanism and its race conditions instead of assuming that the presence of a field named nonce is sufficient.
Do not shorten an arbitrary contract signature to fit an EOA-only format. ERC1271 uses a variable-length byte array so that the contract can interpret its own scheme. A wallet may use multisignature evidence or a signature policy that does not map to the conventional recovery input. Compatibility requires calling the correct verifier with the original bytes.
Undeployed wallets add a distinct boundary. ERC6492 describes wrapped verification for counterfactual contract wallets. A code-length branch alone cannot discover that policy before deployment. Supporting that case requires an explicit verifier design and its own review, rather than silently falling back to ordinary recovery. This article's response grid does not implement ERC6492 deployment or wrapper verification.
Keep login authorization separate from asset authorization in the acceptance record. A service may create a session after checking a wallet message; the later session permissions are then an application policy. Their lifetime, revocation and allowed operations need their own controls. Rechecking ERC1271 is one possible design choice, but the standard does not automatically revoke a session that another system already created.
Similarly, an off-chain order index can retain an order that the wallet no longer accepts. Its database entry is discovery evidence, not settlement authority. A useful interface can mark the last observed status and the time of that observation while the consuming contract performs its current check. This makes the indexer's responsibility legible without promising that an earlier validation will survive every later state change.
Test rejection without losing wallet compatibility
A test that only expects failure is weak evidence. The wallet may fail because the test used the wrong message encoding, lacked required state or exhausted a manually chosen gas budget. Each intended rejection needs a corresponding accepted fixture that reaches the relevant check. Keep the expected raw response and the application's final decision as separate assertions.
| Changed condition | Expected observation | Required control |
|---|---|---|
| Wallet policy changes | Current wallet answer governs consumption | Same message under the earlier authorized policy |
| Return word is malformed | Pinned response predicate rejects it | Canonical return word from a successful call |
| Material action field changes | Original authorization cannot approve that action | Unchanged approved action |
| Provider cannot read required state | Observation remains unknown | Successful read at an available block |
ERC1271's security discussion warns against hardcoding a validation gas amount because legitimate implementations can consume different amounts. This does not mean an off-chain service must accept unbounded work from arbitrary callers. A service can define operational limits, record timeouts and distinguish unsupported resource consumption from a cryptographic rejection. Its interface should preserve that distinction.
The contract-consuming path needs an equally explicit resource argument. If the implementation imposes a cap, assess the compatibility consequences and the denial-of-service boundary against the wallets it supports. Avoid publishing a universal gas recommendation unsupported by the standard or measured deployments. Our synthetic response study did not measure gas consumption.
Logs should also separate a call that reverted from a successful call that returned a nonmatching value. Both may produce a final negative decision, but they point to different investigations. Preserve enough return data for diagnosis within the application's storage and privacy policy. Do not expose private signing material or user secrets merely to make a debugging record convenient.
A broader transaction simulation review can help test the eventual consuming action. Simulation remains evidence about its chosen state and inputs. It cannot turn a cached signature result into an irrevocable grant or guarantee that a future execution will see the same wallet configuration.
Turn the response grid into explicit integration fixtures for the chosen adapter. Supply a successful raw selector response, a truncated word, canonical padding, dirty padding and an extra trailing word. Record the bytes and expected decision before running the integration. The permissive prefix predicate is useful as a comparison, but substituting it changes the pinned acceptance policy and needs a deliberate compatibility argument.
The helper's address overload also routes by deployed code presence. With no code it uses ECDSA recovery; with code it calls the contract branch. The inspected ECDSA bytes overload accepts a conventional 65-byte encoding, while that restriction does not define every contract signature. List the supported routes in the acceptance packet and explain what happens to an unsupported route instead of silently truncating its bytes.
Use distinct tests for wallet-policy change and application replay. In the first, retain the original hash and signature while changing the supported wallet's real authorization configuration. In the second, consume an accepted action, then submit it again under unchanged wallet policy. The wallet may still return acceptance while the application must enforce its own consumption rule. A single rejection test cannot establish both properties.
Preserve the route of the validation call when reproducing a disagreement. A direct query and a query through the consuming verifier can have different execution contexts under a context-dependent policy. That is a source-permitted scenario, not a claim that every wallet varies by caller. Reproduce the actual integration before declaring that a direct wallet query proves what the consuming contract would observe.
Close verification with a reproducible record
The closing artifact should answer a narrow question: which exact authorization did the application enforce, in which state, using which verifier? Attach the tested response policy, the message schema and the consumption rule. A positive result without those details is difficult to distinguish from an EOA-only recovery check or a permissive byte-prefix comparison.
Review the user-facing label as part of acceptance. A screen that says the wallet authorized an order should identify the action and observation it refers to. A label that instead implies permanent ownership or future execution goes beyond the interface's evidence. Correct the wording at the same time as the integration, so the public claim matches the implemented check.
For an external review, make the wallet types and supported verification routes explicit in the scope. The security firm directory and the Pharos Production member profile provide places to start evaluating a provider. The review request still needs its own technical acceptance criteria: policy changes, malformed responses, replay boundaries and current-state consumption are different test obligations.
Keep unresolved observations unresolved. A failed historical read does not establish that the wallet would have rejected the message. A passed low-level response predicate does not establish that the signed action had the right recipient. The complete ledger makes these limits visible instead of collapsing them into a single reassuring label.
Assign an owner to the evidence packet. That person should be able to retrieve the pinned code, reconstruct the supplied hash and explain why a test failed. Preserve accepted fixtures as well as rejected ones: removing the former makes a compatibility regression difficult to distinguish from stronger authorization. After a verifier upgrade, replay both populations and record which outcomes changed before approving the new integration.
The measured result here is precise: the pinned helper accepted 2 of 24 constructed response pairs. What would change the conclusion is equally precise: a different verifier, a different response population or execution evidence from the actual integration. Approval should name those dependencies and trigger a new review when one changes.
Frequently asked questions
Should an exchange support desk request a seed phrase to reproduce verification?
No. Ask for the public wallet address, exact message or hash, signature bytes and relevant block context. Signing secrets are not part of the public verification input and should not be collected for this investigation.
Does a public wallet check identify the human or organization behind that wallet?
The interface reports the contract's acceptance of a hash and signature. Establishing a legal or human identity requires separate evidence; do not turn wallet authorization into an identity claim.
Can one successful check authorize several application actions at once?
Only if the application's approved message and consumption policy deliberately grant that scope. A wallet's positive interface response does not create additional business permissions beyond the action the application defines.