ERC-1271とは?Safeなどのコントラクト署名を正しく検証する
ERC-1271のisValidSignature、magic value、revert、Safe型thresholdを整理し、EOA・1-of-1・2-of-3・state変更をOpenZeppelinとFoundryで検証します。

ERC-1271は、contract account自身へ「このdigestとsignatureは、今このaccountにとって有効ですか」と問い合わせる標準です。
EOAは秘密鍵で署名するため、ECDSAで復元したaddressを期待するsignerと比較できます。一方、Safeのようなcontract accountはcontract自体の秘密鍵を持ちません。複数owner、threshold、session key、pauseなどの現在stateを使って有効性を判定します。
そのため、すべてのsignerへecrecoverだけを使う実装はcontract accountの正しい署名を拒否します。反対に、「contractだから有効」と扱うこともできません。ERC-1271のcallが成功し、厳密なmagic valueを返したかまで確認します。
この記事ではOpenZeppelin Contracts 5.6.1のSignatureCheckerを使い、EOA、1-of-1、2-of-3、wrong owner、threshold不足、署名順序、wrong digest、期限切れ、wrong magic、revert、state変更をFoundryのlocal環境で再現します。実在Safe、外部RPC、wallet接続、mainnet署名、実資産は使いません。
EOA署名とcontract署名の違い
最初に、検証する主体を分けます。
| signer | 誰が署名の有効性を決めるか | 基本の検証 | 結果が後から変わるか |
|---|---|---|---|
| EOA | 1つの秘密鍵 | digestとsignatureからECDSAでaddressを復元 | 同じdigest・signature・addressなら通常は変わらない |
| deployed contract account | account contractの現在のlogicとstate | isValidSignature(digest, signature)をstaticcall |
owner、threshold、pauseなどで変わり得る |
| undeployed counterfactual account | 将来deployされるaccountとfactory情報 | ERC-1271だけでは直接検証できない | ERC-6492などdeploy-awareな経路が必要 |
ここで検証するのは「このsignerにとって、このdigestのsignatureが有効か」です。署名対象が安全か、同じ署名を再利用できないか、期限内か、処理が成功するかは別の確認です。
ERC-1271のinterfaceとmagic value
ERC-1271の中心は次のinterfaceです。
interface IERC1271 {
function isValidSignature(bytes32 hash, bytes calldata signature)
external
view
returns (bytes4 magicValue);
}
有効なときに返す値は、次の4 bytesです。
bytes4(keccak256("isValidSignature(bytes32,bytes)"))
= 0x1626ba7e
true、1、空のreturn data、似た別の4 bytesでは成功になりません。呼び出しがrevertした場合も、有効なsignatureとして扱いません。
ERC-1271の仕様では、この関数はstateを変更してはいけません。ただし、stateを読んで判定することは禁止されていません。たとえば現在のowner集合、必要署名数、時刻、許可済みhashを読めます。外部callも許されるため、実装ごとの処理量を想定せず、独自の小さなgas上限を決め打ちしないことも重要です。
OpenZeppelin SignatureCheckerで検証入口を揃える
OpenZeppelinのSignatureCheckerは、address型のsignerについて次の分岐を行います。
- signerにcodeがない: ECDSAで復元したaddressとsignerを比較
- signerにcodeがある: ERC-1271の
isValidSignatureをstaticcall
利用側は次のように1つの入口へまとめられます。
import {SignatureChecker} from
"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
function isValid(
address signer,
bytes32 digest,
bytes calldata signature
) public view returns (bool) {
return SignatureChecker.isValidSignatureNowCalldata(
signer,
digest,
signature
);
}
この記事のsampleは@openzeppelin/contractsをexact versionの5.6.1へ固定しています。major versionだけ、^5.6.1、latestではありません。signature validationはsecurity境界になるため、更新時はchangelogとtest結果を別途確認します。
SignatureCheckerはcontract callがrevertした場合、return dataが短い場合、返り値が0x1626ba7eでない場合をfalseへまとめます。利用APIをboolに揃えられますが、backendで障害を調べるときは、低level callの成功、return data、対象blockも別に記録すると原因を分けられます。
code.length == 0はEOAの証明ではない
この分岐は便利ですが、「今codeがないaddressは必ずEOA」という身元証明ではありません。
- まだdeployされていないcounterfactual account
- constructor実行中のcontract
- 将来同じaddressへdeployされる前の状態
なども、その時点ではcodeが見えない場合があります。さらにEIP-7702で委任codeを持つEOAは、従来の「EOAにはcodeがない」という前提から外れます。
したがって、code.lengthは検証methodを選ぶ現在stateの入力として扱い、account種別や永続的な本人性の結論にはしません。
local sampleで1-of-1を検証する
再現環境は次の通りです。
| 項目 | version・設定 |
|---|---|
| OpenZeppelin Contracts | 5.6.1 exact pin |
| Solidity | 0.8.36 |
| Foundry | 1.8.0 |
| chain / RPC | Foundry内のlocal EVMのみ |
| key / account | deterministic local test keyとdeployした検証用contractだけ |
| asset transfer | なし |
Threshold1271Walletはowner addressとthresholdを保存し、signature payloadを次の独自形式で受け取ります。
abi.encode(
address[] signers,
bytes[] ownerSignatures
)
1-of-1では、1人のownerが同じdigestへ署名し、wallet contractがowner membershipとECDSA signatureを確認します。条件を満たせば0x1626ba7eを返し、外側のSignatureCheckerもtrueになります。
bytes memory signature = abi.encode(signers, ownerSignatures);
require(
wallet.isValidSignature(digest, signature)
== IERC1271.isValidSignature.selector
);
require(verifier.isValid(address(wallet), digest, signature));
このpayloadは学習用sampleの形式で、Safeのpacked signature形式ではありません。production accountへそのまま渡せる互換形式だとは考えないでください。
2-of-3のthresholdと署名順序を確認する
2-of-3では3人のownerのうち2人以上が、同じdigestへ署名する必要があります。sampleは次を順番に確認します。
- signature数がthreshold以上か
- signer数とsignature数が一致するか
- signerが現在のownerか
- signer addressが厳密な昇順か
- 各signatureが同じdigestに対して有効か
Safe本体も必要数のsignatureを確認し、owner addressが前のownerより大きいことを要求して、無効ownerとduplicateを拒否します。SafeのGS020はsignature data不足、GS026は無効・重複ownerや順序などを調べる目印です。
ただし、この記事のThreshold1271Walletはowner・threshold・順序・state依存だけを切り出したSafe型の最小再現です。Safeのcontract signature、approved hash、P-256、module、executor context、実際のpacked encodingを再実装したものではありません。資産管理には使えません。
正常系と失敗系をFoundryで再現する
local sampleは、成功だけでなく同じ入口から次のfailureを作ります。
| test | 入力・state | 期待結果 | 主に分かること |
|---|---|---|---|
| EOA success | ownerのkeyで正しいdigestへ署名 | true |
ECDSA経路 |
| EOA wrong signer | 別ownerとして検証 | false |
address一致が必要 |
| 1-of-1 | 現owner 1人の正しい署名 | magic / true |
ERC-1271正常系 |
| 2-of-3 | 現owner 2人を昇順で提出 | magic / true |
threshold正常系 |
| insufficient threshold | 2-of-3へ1署名だけ提出 | false |
signature数不足 |
| wrong owner | outsiderを含める | false |
owner membership |
| unsorted | 正しい2人を降順で提出 | false |
順序・duplicate防止 |
| wrong digest | signature後にdigestを変更 | false |
全ownerが同じdigestへ署名する必要 |
| expired | application deadline後に検証 | false |
期限はapplication側の条件 |
| wrong magic | contract call成功、0xffffffffを返す |
false |
call成功だけでは不足 |
| revert | isValidSignatureがrevert |
false |
revertを承認にしない |
| owner change | ownerを外して同じsignatureを再検証 | false |
state依存 |
| validation pause | walletを停止して同じsignatureを再検証 | false |
block間で結果が変わる |
| undeployed account | codeのない予定address | false |
ERC-6492境界 |
repository rootで次を実行します。
npm ci
CI_REQUIRE_FOUNDRY=1 npm run test:contracts
完全なcontractとtestは3MIKANの公開repositoryで確認できます。mainnet / testnet RPC環境変数は使いません。
deadlineはERC-1271の自動機能ではない
local sampleの期限確認は、統一verifier側へ明示的に追加しています。
function isValidBefore(
address signer,
bytes32 digest,
bytes calldata signature,
uint256 deadline
) external view returns (bool) {
return block.timestamp <= deadline
&& SignatureChecker.isValidSignatureNowCalldata(
signer,
digest,
signature
);
}
ERC-1271へdeadline引数はありません。deadlineを署名対象へ含めず、別入力としてだけ受け取ると、提出者が値を変えられる設計になり得ます。実際のapplicationではdeadline、nonce、action、chain、検証先をdigestへ結びつけたうえで、現在stateも確認します。
contract signatureは「今」の答え
OpenZeppelinの説明にある通り、contract signatureはblock Nでtrue、block N+1でfalseになる場合があります。逆に、ownerが追加されたり事前承認stateが変わったりして、後から有効になる実装も考えられます。
viewは「このcall自身がstateを変更しない」という意味です。返り値が永続的に固定される意味ではありません。
off-chain backendで検証結果を証拠として残す場合は、少なくとも次を一緒に記録します。
- chainId
- signer contract address
- block numberまたはblock tag
- digest
- signatureまたはその安全な参照
- low-level callの成功・revert
- return value
- 検証時点のaccount implementation / codeを追える情報
長期sessionや注文を有効にする場合は、owner変更、account upgrade、pause、nonce消費などの後に再検証する条件も決めます。「一度trueだったsignature」を無期限の本人証明としてcacheしません。
EIP-712はdigest、ERC-1271はsignerを担当する
ERC-1271へ渡す最初の引数は、意味の説明がないbytes32 hashです。ERC-1271だけでは、その値が注文、投票、ログイン、Permitのどれか分かりません。chainId、verifyingContract、nonce、deadlineが入っている保証もありません。
役割を分けると次のようになります。
| 層 | 主な役割 | 自動では保証しないもの |
|---|---|---|
| EIP-712 | type、domain、messageから最終digestを作る | nonce消費、deadline判定、effectの正当性 |
| ERC-1271 | contract accountがdigestとsignatureの現在有効性を答える | digestの意味、replay防止、transaction成功 |
| application | nonce、deadline、権限、effect、state遷移を検証する | account内部のowner判定 |
domain separator、hashStruct(message)、nonce、deadlineを同じdigestへ結びつける手順は、EIP-712署名の読み方とreplay対策でlocal codeと一緒に確認できます。
ABIからisValidSignatureのselector、引数、return dataを追う場合は、ABI・function selector・event logsの確認方法を参照してください。
EIP-7702とcounterfactual accountの境界
EIP-7702ではEOA addressがdelegation codeを持てます。そのaddressをSignatureCheckerへ渡すと、現在codeがあるためERC-1271側へ進みます。delegated implementationがaccountの文脈でERC-1271を正しく提供しているかが必要です。
address、保存領域、delegation code、msg.senderの境界は、EIP-7702でEOAはどう変わるかでFoundry testを使って確認できます。
反対に、counterfactual accountがまだdeployされていなければisValidSignatureをcallできません。ERC-6492は、factoryとdeploy calldataをsignatureへ包み、必要ならdeploy処理を考慮してからERC-1271を試す検証順を定義します。
ERC-6492はdeploy side effect、wrapper解析、reentrancyなど追加の境界を持つため、この記事のsampleには混ぜていません。未deploy accountへ単純なcode.length → ECDSAだけを使うのではなく、counterfactual signatureに対応するvalidatorを別途設計・検証します。
backend・contract reviewチェックリスト
- 期待するsigner addressをdigest外の入力と混同していない
- EOAはECDSA、deployed contractはERC-1271で検証する
- ERC-1271 call成功だけでなく
0x1626ba7eを厳密に確認する - wrong magic、短いreturn、revertを有効として扱わない
-
code.length == 0を永続的なEOA証明にしない - 使用するsignature libraryのversionをexact pinした
- account固有のsignature encodingとthreshold規則を公式sourceで確認した
- Safe型signatureではowner順序とduplicateを確認した
- EIP-712などでchain、contract、action、nonce、deadlineをdigestへ結びつけた
- nonce消費とdeadline判定をapplication stateで行う
- 検証blockとaccount stateを記録し、変更後の再検証条件を決めた
- counterfactual accountはERC-6492等の別経路として扱う
- signature有効をtransaction成功、安全性、監査済みの証明にしない
ERC-1271対応で大切なのは、「contract walletも受け付ける」という一行を足すことではありません。EOAのECDSA、contract accountの現在state、digestを作るapplication規則を同じ認可フローの別レイヤーとして照合することです。
この記事にスポンサー、affiliate、wallet作成、資産移動のCTAはありません。将来、署名基盤の開発相談やsponsor表示を置く場合も、広告であることを明示し、上の仕様・test結果・review結論から独立させます。production実装には、このsampleとは別の設計review、account固有test、脅威分析が必要です。
確認した一次情報
- ERC-1271 Standard Signature Validation Method for Contracts確認日: 2026/08/30
- OpenZeppelin Contracts 5.x SignatureChecker確認日: 2026/08/30
- OpenZeppelin Contracts v5.6.1確認日: 2026/08/30
- Safe Smart Account Safe.sol確認日: 2026/08/30
- Safe Smart Account error codes確認日: 2026/08/30
- EIP-712 Typed structured data hashing and signing確認日: 2026/08/30
- ERC-6492 Signature Validation for Predeploy Contracts確認日: 2026/08/30
- EIP-7702 Set Code for EOAs確認日: 2026/08/30
- Solidity 0.8.36 documentation確認日: 2026/08/30
- Foundry v1.8.0確認日: 2026/08/30



