[ERC6492] デプロイされていないコントラクトアカウントで署名検証する仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、まだデプロイされないコントラクトアカウントで署名検証する仕組みを提案している規格である**ERC6492**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-6492

他にも様々なERCについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

**ERC1271**は、ブロックチェーン上のコントラクトで署名を確認するためのルールです。
このルールにより、コントラクトは「**この署名は本当に自分が承認したものか？**」ということを、`isValidSignature` という関数を通じてチェックできます。

ただし、コントラクトがまだブロックチェーン上に存在しない（つまり、デプロイされていない）場合、この`isValidSignature` 関数は使えないという問題があります。

そこで、新たな提案がされています。
これは、まだデプロイされていないコントラクト（別名「**counterfactual contract**」）の署名が本物かどうかを確認する新しい方法です。
この方法は、**ERC1271**を基にして拡張されたものです。

この新しい提案により、これからデプロイされるコントラクトが署名の検証を行い、本当に正しいかどうかをチェックできるようになります。

:::note info
**ERC1271**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/da237b1b8cc185491512

# 動機

ブロックチェーンの世界では、「**アカウント抽象化**」という考え方が注目されています。
これは、コントラクトウォレットの使い勝手を良くするための方法です。
通常、コントラクトウォレットを使うには、まずブロックチェーン上にコントラクトをデプロイする必要があります。
しかし、アカウント抽象化を使うと、ユーザーが最初にトランザクション（取引）を行うまでデプロイを遅らせることができます。
これにより、ユーザーはコントラクトウォレットをすぐに使い始めることができ、デプロイのステップを省くことが可能になります。

しかし、この方法には問題点があります。
多くの分散型アプリケーション（dApps）は、ユーザーがログインする時や他のやり取りをする時に署名を要求します。
コントラクトウォレットが実際にデプロイされるまでは（通常、最初のトランザクション時）、これらの署名を行うことができません。

さらに、**ERC1271**標準では、デプロイされていないコントラクト（**counterfactual contract**）からのメッセージに署名することはできません。
これは、コントラクトウォレットを使う上での大きな制限の一つとされています。

:::note info
アカウント抽象化については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/8c97ca7c93c4557ebfc2

# 仕様

**ERC1271**は、ブロックチェーン上のコントラクトが署名を検証するためのルールです。

以下は**ERC721**からの引用です。

> isValidSignature can call arbitrary methods to validate a given signature, which could be context dependent (e.g. time based or state based), EOA dependent (e.g. signers authorization level within smart wallet), signature scheme Dependent (e.g. ECDSA, multisig, BLS), etc.
> 
> This function should be implemented by contracts which desire to sign messages (e.g. smart contract wallets, DAOs, multisignature wallets, etc.) Applications wanting to support contract signatures should call this method if the signer is a contract.

> `isValidSignature` 関数は、与えられた署名を検証するために任意の方法を呼び出すことができます。これは、コンテキストに依存する場合があります（例えば、時間ベースや状態ベース）、外部所有アカウント（EOA）に依存する場合があります（例えば、スマートコントラクトウォレット内の署名者の認証レベル）、署名スキームに依存する場合があります（例えば、ECDSA、マルチシグ、BLSなど）。
> 
> この関数は、メッセージに署名することを望むコントラクトによって実装されるべきです（例えば、スマートコントラクトウォレット、DAO、マルチシグネチャウォレットなど）。コントラクトの署名をサポートしたいアプリケーションは、署名者がコントラクトである場合にこの方法を呼び出すべきです。

新しい提案では、`isValidSignature`関数をそのまま使いながら、デプロイされる前のコントラクトでも使用できる新しいラッパー署名形式を追加します。
これにより、未デプロイのコントラクトの署名もサポートできるようになります。

このラッパー署名形式が使われているかどうかは、署名が特定の「**magicBytes**」（`0x6492649264926492649264926492649264926492649264926492649264926492`）で終わっているかをチェックすることで判断します。
署名検証者は、この形式を検出した場合、`isValidSignature`を呼び出す前にコントラクトのデプロイを行う必要があります。

**CREATE2**コントラクトとこの規格を一緒に使用することが推奨されています。
これは、**CREATE2**コントラクトのデプロイアドレスが常に予測可能であるため、より効率的に署名を検証できるからです。

:::note info
### CREATE2

**CREATE2**コントラクトは、イーサリアムのスマートコントラクトをデプロイするための特別な方法です。
通常のコントラクトデプロイ方法との大きな違いは、CREATE2を使用すると、コントラクトのアドレスを事前に計算し、決定することができる点です。
通常のコントラクトデプロイ方法との大きな違いは、**CREATE2**を使用すると、コントラクトのアドレスを事前に計算し、決定することができる点です。

イーサリアムでは、通常のコントラクトデプロイ（**CREATE**と呼ばれる）では、コントラクトのアドレスはデプロイするトランザクションのデータとデプロイするアカウントのアドレスに基づいて生成されます。
これに対し、**CREATE2**では、特定の「`salt`」（独自の値）とコントラクトのバイトコードを使って、コントラクトのアドレスを事前に計算することができます。
これにより、コントラクトがデプロイされる前でも、そのアドレスを知ることが可能になります。

この特性は、特にカウンターファクチャル（未デプロイ）コントラクトや、より予測可能なコントラクトの動作が必要なアプリケーションで有用です。
たとえば、ユーザーがまだ存在しないコントラクトに資金を送る場合、**CREATE2**を使用すると、コントラクトがデプロイされた後にその資金を受け取ることができます。

CREATE2はイーサリアムにおいて、コントラクトのアドレスを事前に決定し、予測可能にするための方法であり、さまざまな高度なアプリケーションや特定のユースケースに適しています。
:::

## 署名者側

このテキストは、署名を行うコントラクトに関して説明しており、特に**ERC1271**標準を採用しているコントラクトに焦点を当てています。
ここでのコントラクトは主にコントラクトウォレットですが、**ERC1271**を実装し未デプロイ状態（**counterfactual contract**）で存在する他の種類のコントラクトも含まれます。

### デプロイ済みコントラクト
すでにブロックチェーン上にデプロイされている場合、通常通り**ERC1271**の署名を生成します。

### 未デプロイコントラクト
まだデプロイされていない場合、特別な方法で署名をラップします。
これは、`create2Factory`（コントラクトを作成するファクトリーのアドレス）、`factoryCalldata`（ファクトリーへのコールデータ）、`originalERC1271Signature`（元の**ERC1271**署名）を組み合わせ、特別なバイト（`magicBytes`）を付加することで行います。

### デプロイ済みだが準備未完了コントラクト
デプロイはされているものの、まだ**ERC1271**検証に必要な準備が整っていない場合、署名は別の方法でラップされます。
これには、`prepareTo`（準備に必要なアドレス）と`prepareData`（準備に必要なデータ）、`originalERC1271Signature`を組み合わせ、`magicBytes`を付加します。
`prepareTo`と`prepareData`には、コントラクトが**ERC1271**検証の準備ができるようにするためのトランザクションが含まれます。

このアプローチでは、`create2Factory`、`salt`、`bytecode`を使用する代わりに`factoryCalldata`を使います。
これは、さまざまなファクトリーインターフェースに対応する検証を可能にするためです。
**ERC1271**検証では、署名を検証する際には署名者のアドレスが既知であると想定されているため、アドレスを計算する必要はありません。

## 検証者側

以下は署名検証の手順になります。

1. **`magicByte`のチェック**
    - まず、署名が特定のバイト（`magicByte`）で終わっているかどうかを確認します。
    - このバイトがある場合、マルチコールコントラクトに対して`eth_call`を行い、ファクトリーに`factoryCalldata`を送ります。
    - これにより、まだデプロイされていないコントラクトがあれば、デプロイされます。
    - その後、署名の通常の検証を`contract.isValidSignature`で行います。
2. **コードの存在チェック**
    - 次に、対象アドレスにコントラクトコードがあるかどうかをチェックします。
    - コードがあれば、**ERC1271**に従って`isValidSignature`で検証を行います。
3. **検証の失敗と再試行**
    - もし**ERC1271**検証が失敗し、かつファクトリーへのデプロイ呼び出しが、ウォレットにすでにコードが存在するためスキップされた場合、`factoryCalldata`トランザクションを実行してから、もう一度`isValidSignature`で検証を試みます。
4. **`ecrecover`検証**
    - 最後に、アドレスにコントラクトコードがない場合は、`ecrecover`を使った検証を行います。

これらの手順により、コントラクトの署名が正しいかどうかを正確に検証でき、特に未デプロイや既存のコントラクトに対して効果的に検証作業を行うことが可能です。

# 補足

このテキストは、コントラクトの署名を特定の方法でラップすることについて説明しています。
この方法はコントラクトに依存しないため、どのようなコントラクトでも適用可能であり、検証も容易です。

ラッパー形式の署名には`magicBytes`という特定のバイト列が含まれています。
この`magicBytes`は`0x92`で終わり、これは`r`、`s`、`v`形式での有効な`ecrecover`署名とは異なります。
`0x92`は`v`の有効な値ではないため、通常の`ecrecover`署名とは区別がつきます。
また、`magicBytes`は長い（`bytes32`）ので、通常の**ERC1271**署名との衝突を避けることができます。

:::note info
`ecrecover`は、イーサリアム上でデジタル署名を検証するための関数です。
この関数を使用して、あるメッセージに署名したアカウントのアドレスを復元することができます。
`ecrecover`関数は、特に`r`、`s`、`v`という三つの要素から成る署名データを必要とします。

- **`r`と`s`**
    - これらは署名の主要な部分であり、署名プロセスの結果として生成されます。
    - `r`と`s`は、メッセージに対して特定の秘密鍵を使用して生成されたユニークな値です。
- **`v`**
    - これは署名の回復識別子として機能し、署名が生成された特定のブロックチェーンの状況（例えば、ネットワークIDやチェーンID）に関連しています。
    - `v`は通常、`27`または`28`のいずれかの値を取りますが、チェーンIDが導入された後は、他の値を取ることもあります。

署名プロセスでは、署名者は自分の秘密鍵を使用してメッセージに署名し、`r`、`s`、`v`の3つの値を生成します。
その後、これらの値と元のメッセージ（またはそのハッシュ）を`ecrecover`関数に入力することで、署名したアカウントの公開アドレスが復元されます。

`ecrecover`を使用する主な利点は、メッセージが特定のアカウントによって署名されたことを、秘密鍵を明かすことなく証明できることです。
これにより、ブロックチェーン上での取引や契約の検証が安全に行われます。
:::

正しい検証プロセスを確実にするためには、以下のルールに従う必要があります。

1. **counterfactual contract**（未デプロイ）のコントラクトの署名がコントラクトのデプロイ後も有効であるように、`magicBytes`のチェックは通常の**ERC1271**検証の前に行う必要があります。
2. 明らかに識別可能な**counterfactual contract**コントラクトの署名を誤って`ecrecover`で検証しようとするのを避けるために、`magicBytes`のチェックは`ecrecover`の前に行うべきです。
3. コントラクトが有効な`ecrecover`署名でもある可能性のある署名形式を使用している場合があるため、`ecrecover`のチェックは**ERC1271**検証の前には行わないべきです。

また、なぜある署名が「**deploy prefix**」でエンコードされたかは、対応するウォレットに既にコードがある場合、明確ではありません。
これは、コントラクトがデプロイされる前に署名が作成されたか、またはデプロイされているがまだ署名を検証する準備ができていないためかもしれません。
そのため、両方の可能性を考慮して検証を試みる必要があります。

:::note info
「**deploy prefix**」でエンコードされた署名は、コントラクトがまだブロックチェーンにデプロイ（配備）されていない状態で生成されたものかもしれません。
または、コントラクトはデプロイされているものの、まだ署名を検証する準備が整っていない可能性もあります。
つまり、このタイプの署名は、コントラクトのデプロイ状態が不明確な状況で使用されることがあります。

このため、署名を検証する時には両方のシナリオを考慮する必要があります。
もし署名が「**deploy prefix**」でエンコードされている場合、以下の２つのステップを踏むことが重要です。

1. **コントラクト未デプロイの場合の検証**
    - 署名がコントラクトがデプロイされる前に生成されたものであるかどうかを確認します。
    - これは、コントラクトがまだ存在しないため、署名を検証するためにはコントラクトのデプロイが必要かもしれません。
2. **コントラクトデプロイ済みだが検証準備未完了の場合の検証**
    - もう一方で、コントラクトが既にデプロイされているが、署名を検証する機能がまだ稼働していない可能性もあります。
    - この場合、コントラクトが署名を検証できる状態になるまで追加の手順が必要かもしれません。

このように、検証プロセスでは、コントラクトのデプロイ状況に応じて異なるアプローチを取る必要があり、署名が正確に検証されるように様々なシナリオを考慮することが重要です。
:::

# 後方互換性

この**ERC6492**は、署名の検証方法に関して、特に**ERC1271**との互換性を持ちつつより進化した形です。
これは、外部所有アカウント（EOA）の署名やタイプ付きデータ（**EIP712**など）を含む、あらゆる種類の署名を簡単に検証することが可能です。

:::note info
**EIP712**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/f54bc85cb66de5a54257

**ERC6492**を利用すると、以下のような利点があります。

- **迅速な署名タイプの識別**
    - このERCには「**MaficByte**」という特別なバイト列が含まれています。
    - これにより、ブロックチェーンを調べることなく、署名がコントラクトのものかどうかを直ちに判断できます。
- **アドレスの復元機能**
    - `create2Factory`と`factoryCalldata`を使って、署名だけからコントラクトのアドレスを特定できます。
    - これは`ecrecover`機能と同じように働きます。

このように、**ERC6492**は、通常の**ERC1271**よりも幅広い種類の署名を扱うことができ、コントラクト署名の処理をより効率的かつ簡単にするための手段を提供します。

# 参考実装

ブロックチェーン上（オンチェーン）とブロックチェーン外（オフチェーン）で利用可能な汎用的な署名検証コントラクトについて説明しています。
このコントラクトは、さまざまなタイプの署名（この規格、**ERC1271**、`ecrecover`による署名）を検証することができます。
さらに、**EIP712**によるタイプ付きデータの署名も、最終的なダイジェスト（`_hash`）を検証することでサポートされています。

```javascript
// As per ERC-1271
interface IERC1271Wallet {
  function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
}

error ERC1271Revert(bytes error);
error ERC6492DeployFailed(bytes error);

contract UniversalSigValidator {
  bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492;
  bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;

  function isValidSigImpl(
    address _signer,
    bytes32 _hash,
    bytes calldata _signature,
    bool allowSideEffects,
    bool tryPrepare
  ) public returns (bool) {
    uint contractCodeLen = address(_signer).code.length;
    bytes memory sigToValidate;
    // The order here is strictly defined in https://eips.ethereum.org/EIPS/eip-6492
    // - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed; if the contract is deployed we will check the sig against the deployed version, this allows 6492 signatures to still be validated while taking into account potential key rotation
    // - ERC-1271 verification if there's contract code
    // - finally, ecrecover
    bool isCounterfactual = bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX;
    if (isCounterfactual) {
      address create2Factory;
      bytes memory factoryCalldata;
      (create2Factory, factoryCalldata, sigToValidate) = abi.decode(_signature[0:_signature.length-32], (address, bytes, bytes));

      if (contractCodeLen == 0 || tryPrepare) {
        (bool success, bytes memory err) = create2Factory.call(factoryCalldata);
        if (!success) revert ERC6492DeployFailed(err);
      }
    } else {
      sigToValidate = _signature;
    }

    // Try ERC-1271 verification
    if (isCounterfactual || contractCodeLen > 0) {
      try IERC1271Wallet(_signer).isValidSignature(_hash, sigToValidate) returns (bytes4 magicValue) {
        bool isValid = magicValue == ERC1271_SUCCESS;

        // retry, but this time assume the prefix is a prepare call
        if (!isValid && !tryPrepare && contractCodeLen > 0) {
          return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true);
        }

        if (contractCodeLen == 0 && isCounterfactual && !allowSideEffects) {
          // if the call had side effects we need to return the
          // result using a `revert` (to undo the state changes)
          assembly {
           mstore(0, isValid)
           revert(31, 1)
          }
        }

        return isValid;
      } catch (bytes memory err) {
        // retry, but this time assume the prefix is a prepare call
        if (!tryPrepare && contractCodeLen > 0) {
          return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true);
        }

        revert ERC1271Revert(err);
      }
    }

    // ecrecover verification
    require(_signature.length == 65, 'SignatureValidator#recoverSigner: invalid signature length');
    bytes32 r = bytes32(_signature[0:32]);
    bytes32 s = bytes32(_signature[32:64]);
    uint8 v = uint8(_signature[64]);
    if (v != 27 && v != 28) {
      revert('SignatureValidator: invalid signature v value');
    }
    return ecrecover(_hash, v, r, s) == _signer;
  }

  function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature)
    external returns (bool)
  {
    return this.isValidSigImpl(_signer, _hash, _signature, true, false);
  }

  function isValidSig(address _signer, bytes32 _hash, bytes calldata _signature)
    external returns (bool)
  {
    try this.isValidSigImpl(_signer, _hash, _signature, false, false) returns (bool isValid) { return isValid; }
    catch (bytes memory error) {
      // in order to avoid side effects from the contract getting deployed, the entire call will revert with a single byte result
      uint len = error.length;
      if (len == 1) return error[0] == 0x01;
      // all other errors are simply forwarded, but in custom formats so that nothing else can revert with a single byte in the call
      else assembly { revert(error, len) }
    }
  }
}

// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton
contract ValidateSigOffchain {
  constructor (address _signer, bytes32 _hash, bytes memory _signature) {
    UniversalSigValidator validator = new UniversalSigValidator();
    bool isValidSig = validator.isValidSigWithSideEffects(_signer, _hash, _signature);
    assembly {
      mstore(0, isValidSig)
      return(31, 1)
    }
  }
}
```

---

### ERC6492_DETECTION_SUFFIX

```javascript
bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492;
```

**概要**

**ERC6492**の検出に使用される特定のバイト列。

**詳細**

この定数は、署名が**ERC6492**に準拠しているかを検出するために使用されるバイト列です。
署名がこのバイト列で終わる場合、それは**ERC6492**の署名であると判断されます。

**パラメータ**

- `bytes32`
    - 32バイトの固定長のバイト列。
    - 署名検出に使用される特定の値を表します。

---

### ERC1271_SUCCESS

```javascript
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;
```

**概要**

**ERC1271**に準拠した署名が正常に検証されたことを示すために使用される定数。

**詳細**

この定数は、**ERC1271**に準拠したコントラクトが`isValidSignature`関数を呼び出した際に、署名が有効であることを示すために返される値です。
この値が返されると、署名が正常に検証されたとみなされます。

**パラメータ**

- `bytes4`
    - 4バイトの固定長のバイト列。
    - **ERC1271**の署名が正常に検証されたことを示す値を表します。

---

### isValidSigImpl

```javascript
function isValidSigImpl(
    address _signer,
    bytes32 _hash,
    bytes calldata _signature,
    bool allowSideEffects,
    bool tryPrepare
) public returns (bool) {
    // 関数の実装コード
}
```

**概要**

指定された署名が有効かどうかを判断する関数。

**詳細**

この関数は、署名が**ERC6492**に準拠しているかどうかを検出し、その後**ERC1271**または`ecrecover`による検証を行います。
署名が**ERC6492**に準拠している場合、関連するコントラクトがデプロイされていない場合にデプロイを試みます。
その後、**ERC1271**に準拠しているか、または`ecrecover`によって署名者を復元できるかどうかをチェックします。

**引数**

- `_signer`
    - 署名者のアドレス。
- `_hash`
    - 検証する署名のハッシュ値。
- `_signature`
    - 検証する署名データ。
- `allowSideEffects`
    - サイドエフェクト（状態変更）を許可するかどうかの`bool`値。
- `tryPrepare`
    - デプロイ準備処理を試みるかどうかの`bool`値。

**戻り値**

- `bool`
    - 署名が有効であるかどうかを示す`bool`値。

---

### isValidSigWithSideEffects

```javascript
function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature)
    external returns (bool)
{
    return this.isValidSigImpl(_signer, _hash, _signature, true, false);
}
```

**概要**

サイドエフェクト（状態変更）を許可して署名の有効性を検証する関数。

**詳細**

この関数は、`isValidSigImpl`を呼び出して署名の有効性を検証しますが、サイドエフェクトを許可する点が異なります。
これにより、特定のケースでガス効率が向上する可能性があります。

**引数**

- `_signer`
    - 署名者のアドレス。
- `_hash`
    - 検証する署名のハッシュ値。
- `_signature`
    - 検証する署名データ。

**戻り値**

- `bool`
    - 署名が有効であるかどうかを示すブール`bool`値。

---

### isValidSig

```javascript
function isValidSig(address _signer, bytes32 _hash, bytes calldata _signature)
    external returns (bool)
{
    try this.isValidSigImpl(_signer, _hash, _signature, false, false) returns (bool isValid) { return isValid; }
    catch (bytes memory error) {
        // エラー処理
    }
}
```

**概要**

サイドエフェクトを許可せずに署名の有効性を検証する関数。

**詳細**

この関数は`isValidSigImpl`を使用して署名の有効性を検証しますが、サイドエフェクトは許可しません。
これにより、リエントランシー攻撃に対して安全になりますが、ガス効率は`isValidSigWithSideEffects`よりも低い可能性があります。

**引数**

- `_signer`
    - 署名者のアドレス。
- `_hash`
    - 検証する署名のハッシュ値。
- `_signature`
    - 検証する署名データ。

**戻り値**

- `bool`
    - 署名が有効であるかどうかを示す`bool`値。

---

## オンチェーンでの検証方法
このコントラクトには、以下の二つの異なる検証方法があります。

- `UniversalSigValidator.isValidSig(_signer, _hash, _signature)`
    - この関数は、署名が有効かどうかを`bool`値で返します。
    - この方法はリエントランシー攻撃に対して安全です。
- `UniversalSigValidator.isValidSigWithSideEffects(_signer, _hash, _signature)`
    - この関数は、前述の関数と同様の機能を提供しますが、特定のケースでよりガス効率が良い一方、リエントランシー攻撃には安全ではありません。

これらの関数は、基になる呼び出しがエラーで終了した場合、エラーを返す可能性があります。

:::note info
リエントランシー攻撃については以下の記事を参考にしてください。
:::

https://chaldene.net/solidity-reentrancy-attack

## オフチェーンでの検証方法
`ValidateSigOffchain`ヘルパーを使用すると、事前にデプロイされたコントラクトなしで、一回の`eth_call`で汎用的な署名検証が可能です。
たとえば、`ethers`ライブラリを使用して以下のようなコードを実行することで、署名が有効かどうかを確認できます。

```javascript
const isValidSignature = '0x01' === await provider.call({
  data: ethers.utils.concat([
    validateSigOffchainBytecode,
    (new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature])
  ])
})
```

また、[Ambireの`signature-validator`](https://www.npmjs.com/package/@ambire/signature-validator)のようなライブラリを使って、この汎用的な署名検証を行うことも可能です。

# セキュリティ考慮事項

## リエントランシーの問題
コントラクトをデプロイするには`CALL`が必要ですが、これにはリエントランシー攻撃のリスクが伴います。
この問題は、副作用がある場合には検証メソッドが常に`revert`し、その結果を`revert`データから取得することで軽減されています。
リエントランシー攻撃のリスクがない場合に使用できる`isValidSigWithSideEffects`メソッドも提供されています。

## オフチェーンでの使用頻度の高さ
このERCは、オフチェーンでの検証により頻繁に使用されると予想されます。
多くの場合、オンチェーンでの署名検証は、ウォレットが既にデプロイされていることを前提としています。

## セキュリティ上の考慮事項
コントラクトがデプロイ時に正しい権限で設定されるかどうかが重要です。
**CREATE2**の仕組みにより、署名内のバイトコードやコンストラクタコールコードを変更しても、デプロイアドレスが変わるため、権限を不正に拡大することはできません。

## 動的な認証方法の変更
コントラクトアカウントは認証方法を変更できますが、このERCの設計により、**counterfactual contract**署名を検証する時も、コントラクトが既にデプロイされていれば現行のコントラクトバージョンに対してチェックを行います。

## 異なるネットワークでのリプレイ保護
通常の署名においてはリプレイ保護が重要ですが、このERCでは特に異なるネットワーク間での署名の有効性に注意を払う必要があります。
署名がデプロイ時に有効であった場合、または同じファクトリーアドレス/バイトコードでウォレットがデプロイできる場合には、その署名が検証可能です。

# 引用

Ivo Georgiev (@Ivshti), Agustin Aguilar (@Agusx1211), "ERC-6492: Signature Validation for Predeploy Contracts," Ethereum Improvement Proposals, no. 6492, February 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6492.

# 最後に

今回は「まだデプロイされないコントラクトアカウントで署名検証する仕組みを提案している規格である**ERC6492**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58


https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4