# [ERC5453] 1トランザクション内で複数関数呼び出しや署名関連拡張機能の仕組みを理解しよう！

> この提案は、Ethereumの改善提案（EIP）で、ERC5750を基盤としています。その主な目的は、同じトランザクション内で複数の関数呼び出しを承認できる一般的なプロトコルを設けることです。これは、従来のアプローチとは異なります。

- URL: https://cardene.dev/articles/6d4ffb1e979a6793f6d8
- 公開日: 2023-10-19
- 更新日: 2023-10-19
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、関数の動作を拡張する規格である**ERC5750**の機能に依存し、1トランザクション内で複数関数呼び出しなどの仕組みを提案している規格である**ERC5453**についてまとめていきます！

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

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

:::message alert
**5453**は現在（2023年10月17日）では「**Last Call**」段階です。
:::

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

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

## 概要

この提案は、Ethereumの改善提案（EIP）で、**ERC5750**を基盤としています。
その主な目的は、同じトランザクション内で複数の関数呼び出しを承認できる一般的なプロトコルを設けることです。
これは、従来のアプローチとは異なります。
以前のアプローチ（例えば、**ERC20**用の**ERC2612**や**ERC721**用の**ERC4494**）では、通常、単一の動作（**ERC20**の場合は`transfer`、**ERC721**の場合は`safeTransferFrom`）しか許可せず、単一の承認者が関与するため、2つのトランザクションが必要でした（最初に`permit(...)`トランザクション、次に転送のようなトランザクション）。

:::message
**ERC5750**については以下を参考にしてください。
:::

[\[ERC5750\] 各メソッドの引数にデータを追加して動作を拡張する仕組みを理解しよう！](/articles/216265154b75c1612904)

:::message
**ERC20**については以下を参考にしてください。
:::

https://chaldene.net/erc20

:::message
**ERC721**については以下を参考にしてください。
:::

https://chaldene.net/erc721


しかし、このEIPは、異なる動作を許可し、同じトランザクション内で任意の数の承認者からの複数の承認を一括して受け入れる方法を提供します。
これにより、マルチシグ（複数の署名が必要な契約）や閾値署名（特定の条件を満たす必要がある署名）など、多くのシナリオで役立ちます。

:::message
このプロトコルは、特定のコントラクト内の特定の関数呼び出しに対して、同じトランザクション内で複数の承認を受け入れることができます。
これにより、以前は2つのトランザクションが必要だった手続きが、1つのトランザクションで完了できるようになり、効率が向上します。
特に、コントラクトが特定のロールを持つ複数の承認者から、異なる操作を同じトランザクションで許可できるため、柔軟性とセキュリティが向上します。
:::

## 動機

このプロトコルは、次の機能をサポートします。

### 同時承認と関数呼び出しのサポート

ユーザーはコントラクト内の特定の操作を承認しながら、同じトランザクション内でその操作を実行できます。
これにより、複数の操作を1つのトランザクションで実行できます。

### 別のユーザーからの追加の承認サポート

別のユーザーからの追加の承認を受け入れることができます。
つまり、複数のユーザーが同じトランザクションに対して承認を行えます。

### 別のユーザーによる支払いのサポート

別のユーザーがトランザクションのガス料金を支払うことができます。
これにより、トランザクションのコストを複数のユーザーで分担できます。

### マルチシグのサポート

複数の承認者が必要な場合、マルチシグの機能を提供します。
複数のユーザーが協力してコントラクトの操作を承認できます。

### 共同行動のサポート

複数のユーザーが連携してトランザクションを実行できます。
複数の承認者が協力して、特定の操作を実行できます。

### 累積投票のサポート

承認を累積し、複数の承認を合算できます。
これにより、複数の承認が必要な場合に、それらをまとめて処理できます。

### オフライン署名のサポート

オフラインで署名を生成し、後でトランザクションに追加できます。
これにより、セキュリティと柔軟性が向上し、インターネット接続が必要ない環境でもトランザクションを実行できます。

このプロトコルは、コントラクトの操作をより柔軟に、セキュアに、効率的に管理するための多くの機能を提供します。

## 仕様

### インターフェース

ここで参照されるインターフェースと構造は以下になります。

```javascript
pragma solidity ^0.8.9;

struct ValidityBound {
    bytes32 functionParamStructHash;
    uint256 validSince;
    uint256 validBy;
    uint256 nonce;
}

struct SingleEndorsementData {
    address endorserAddress; // 32
    bytes sig; // dynamic = 65
}

struct GeneralExtensionDataStruct {
    bytes32 erc5453MagicWord;
    uint256 erc5453Type;
    uint256 nonce;
    uint256 validSince;
    uint256 validBy;
    bytes endorsementPayload;
}

interface IERC5453EndorsementCore {
    function eip5453Nonce(address endorser) external view returns (uint256);
    function isEligibleEndorser(address endorser) external view returns (bool);
}

interface IERC5453EndorsementDigest {
    function computeValidityDigest(
        bytes32 _functionParamStructHash,
        uint256 _validSince,
        uint256 _validBy,
        uint256 _nonce
    ) external view returns (bytes32);

    function computeFunctionParamHash(
        string memory _functionName,
        bytes memory _functionParamPacked
    ) external view returns (bytes32);
}

interface IERC5453EndorsementDataTypeA {
    function computeExtensionDataTypeA(
        uint256 nonce,
        uint256 validSince,
        uint256 validBy,
        address endorserAddress,
        bytes calldata sig
    ) external view returns (bytes memory);
}


interface IERC5453EndorsementDataTypeB {
    function computeExtensionDataTypeB(
        uint256 nonce,
        uint256 validSince,
        uint256 validBy,
        address[] calldata endorserAddress,
        bytes[] calldata sigs
    ) external view returns (bytes memory);
}
```

---

#### `ValidityBound`

```javascript
struct ValidityBound {
    bytes32 functionParamStructHash;
    uint256 validSince;
    uint256 validBy;
    uint256 nonce;
}
```

**概要**
スマートコントラクトの呼び出しの有効性に関する情報を格納する構造体。

**パラメータ**
- `functionParamStructHash`
    - 関数呼び出しのパラメータ構造のハッシュ値。
- `validSince`
    - 有効期間の開始を示すタイムスタンプ（Unix時間）。
- `validBy`
    - 有効期間の終了を示すタイムスタンプ（Unix時間）。
- `nonce`
    - ユニークな識別子として使用される数値。

---

#### `SingleEndorsementData`

```javascript
struct SingleEndorsementData {
    address endorserAddress;
    bytes sig;
}
```

**概要**
単一の承認データを格納する構造体。

**パラメータ**
- `endorserAddress`
    - 承認者のアドレス。
- `sig`
    - 署名データを格納する可変長のバイト列（通常は`65`バイト）。

---

#### `GeneralExtensionDataStruct`

```javascript
struct GeneralExtensionDataStruct {
    bytes32 erc5453MagicWord;
    uint256 erc5453Type;
    uint256 nonce;
    uint256 validSince;
    uint256 validBy;
    bytes endorsementPayload;
}
```

**概要**
一般的な拡張データを格納する構造体。
**ERC5453**に関連する情報を含みます。

**パラメータ**
- `erc5453MagicWord`
    - **ERC5453**プロトコルの識別子として使用される特定のハッシュ値。
- `erc5453Type`
    - **ERC5453**のタイプを示す数値。
- `nonce`
    - ユニークな識別子として使用される数値。
- `validSince`
    - 有効期間の開始を示すタイムスタンプ（Unix時間）。
- `validBy`
    - 有効期間の終了を示すタイムスタンプ（Unix時間）。
- `endorsementPayload`
    - 拡張データのペイロードを格納する可変長のバイト列。

---

#### IERC5453EndorsementCore

##### `eip5453Nonce`

```javascript
function eip5453Nonce(address endorser) external view returns (uint256);
```

**概要**
指定された承認者のユニークな識別子（`nonce`）を取得する関数。

**詳細**
指定された `endorser`アドレスに関連付けられたユニークな識別子（`nonce`）を返します。
ユニークな`nonce`は、承認者が特定の操作を実行するたびにインクリメントされる一意の値です。

**引数**
- `endorser`
    - ユーザーまたはアカウントのアドレス。

**戻り値**
- `nonce`
    - 承認者のユニークな識別子。

---

##### `isEligibleEndorser`

```javascript
function isEligibleEndorser(address endorser) external view returns (bool);
```

**概要**
指定されたアドレスが有効な承認者であるか確認する関数。

**詳細**
指定された `endorser`アドレスが有効な承認者である場合、`true`を返します。
有効な承認者は、特定の操作やトランザクションに署名する権限を持つアドレスです。

**引数**
- `endorser`
    - 承認者のアドレス。

**戻り値**
- `isEligible`
    - 指定されたアドレスが有効な承認者である場合は`true`、それ以外の場合は`false`。

---

#### IERC5453EndorsementDigest

##### `computeValidityDigest`

```javascript
function computeValidityDigest(
    bytes32 _functionParamStructHash,
    uint256 _validSince,
    uint256 _validBy,
    uint256 _nonce
) external view returns (bytes32);
```

**概要**
承認情報の有効性を確認するためのダイジェスト（digest）を計算する関数。

**詳細**
指定された `_functionParamStructHash`（関数パラメータのハッシュ）、`_validSince`（有効期限の開始）、`_validBy`（有効期限の終了）、`_nonce`（ユニークな識別子）を使用して、承認情報の有効性を確認するためのダイジェストを計算します。
ダイジェストは、特定の承認情報の一意の識別子であり、署名の検証に使用されます。

**引数**
- `_functionParamStructHash` 
    - 関数パラメータのハッシュ値。
- `_validSince`
    - 有効期限の開始時刻。
- `_validBy`
    - 有効期限の終了時刻。
- `_nonce`
    - ユニークな識別子。

**戻り値**
- `digest`
    - 承認情報の有効性を確認するためのダイジェスト。

##### `computeFunctionParamHash`

```javascript
function computeFunctionParamHash(
    string memory _functionName,
    bytes memory _functionParamPacked
) external view returns (bytes32);
```

**概要**
関数名とパラメータのパックされたバイト列から関数パラメータのハッシュを計算する関数。

**詳細**
`_functionName`と`_functionParamPacked`（パラメータのパックされたバイト列）を使用して、関数パラメータのハッシュを計算します
このハッシュは、関数パラメータの一意の識別子として使用され、承認情報の生成および検証に関与します。

**引数**
- `_functionName`
    - 関数名の文字列。
- `_functionParamPacked`
    - パラメータのパックされたバイト列。

**戻り値**
- `paramHash`
    - 関数パラメータのハッシュ。 

---

#### IERC5453EndorsementDataTypeA

##### `computeExtensionDataTypeA`

```javascript
function computeExtensionDataTypeA(
    uint256 nonce,
    uint256 validSince,
    uint256 validBy,
    address endorserAddress,
    bytes calldata sig
) external view returns (bytes memory);
```

**概要**

与えられた引数を使用して拡張データのデータタイプAを計算する関数。

**詳細**

コントラクト内でデータタイプAを計算するために使用されます。
拡張データタイプAは、ユーザーに関する情報を保持し、署名されたトランザクションの有効性を確認するのに役立ちます。
この関数は、特定の引数を取り、それらを使用してデータタイプAを生成し、その結果を返します。
これにより、トランザクションが有効であるかどうかを検証できます。

**引数**

- `nonce`
    - トランザクションのnonce値。
    - 一意のトランザクション識別子。
- `validSince`
    - トランザクションの有効開始時刻。
- `validBy`
    - トランザクションの有効終了時刻。
- `endorserAddress`
    - 署名者のアドレス。トランザクションを署名したアカウントのアドレス。
- `sig`
    - トランザクションの署名データ。トランザクションの正当性を確認するために使用される署名。

**戻り値**

計算された拡張データタイプAを`bytes`データ型で返します。

---

#### IERC5453EndorsementDataTypeB

##### `computeExtensionDataTypeB`

```javascript
function computeExtensionDataTypeB(
    uint256 nonce,
    uint256 validSince,
    uint256 validBy,
    address[] calldata endorserAddress,
    bytes[] calldata sigs
) external view returns (bytes memory);
```

**概要**

与えられた引数を使用して拡張データのデータタイプBを計算する関数。

**詳細**

コントラクト内でデータタイプBを計算するために使用されます。
拡張データタイプBは、複数の署名者によって署名されたトランザクションの有効性を確認するのに役立ちます。
この関数は、特定の引数を取り、それらを使用してデータタイプBを生成し、その結果を返します。
これにより、トランザクションが有効であるかどうかを検証できます。

**引数**

- `nonce`
    - トランザクションのnonce値。
    - 一意のトランザクション識別子。
- `validSince`
    - トランザクションの有効開始時刻。
- `validBy`
    - トランザクションの有効終了時刻。
- `endorserAddress`
    - 署名者のアドレスの配列。トランザクションを署名したアカウントのアドレスを含む。
- `sigs`
    - トランザクションの署名データの配列。各署名者の署名データを含む。

**戻り値**

計算された拡張データタイプBを`bytes`データ型で返します。

---

### 動作仕様

**ERC5750**の「**メソッド動作の一般的な拡張性**」では、特定のユーザーからの許可を示すための方法として、最後のメソッドとして`bytes extraData`を持つ準拠メソッドが**ERC5453**に従うことができると指定されています。

このEIPに準拠するメソッドは、必ず**ERC5750**に準拠したメソッドである必要があります。
呼び出し元は、最後のパラメータとして、**Section Interfaces**で指定されている`GeneralExtensonDataStruct`のソリッドステートエンコードされたレイアウトを持つ`bytes extraData`を提供する必要があります。
以下の説明は、`bytes extraDataをGeneralExtensonDataStruct`にデコードする場合に基づいています。

`GeneralExtensonDataStruct`にデコードされた`extraData`内で、呼び出し元は`GeneralExtensonDataStruct.erc5453MagicWord`の値を`keccak256("ERC5453-ENDORSEMENT")`に設定する必要があります。
呼び出し元は、`GeneralExtensonDataStruct.erc5453Type`の値をサポートされている値の1つに設定する必要があります。

:::message
このEIPは**ERC5750**に従うメソッドに対して、特定のユーザーからの許可を示す方法を提供します。
呼び出し元は、`extraData`内に特定の情報を設定し、それをデコードして正しい許可情報を提供する必要があります。
これにより、コントラクトは異なるメソッドを拡張し、許可情報を効果的に管理できるようになります。
:::

```javascript
uint256 constant ERC5453_TYPE_A = 1;
uint256 constant ERC5453_TYPE_B = 2;
```

`GeneralExtensonDataStruct.erc5453Type`の値が`ERC5453_TYPE_A`の場合、`GeneralExtensonDataStruct.endorsementPayload`は`SingleEndorsementDataのABI`エンコードされたバイト列である必要があります。
一方、`GeneralExtensonDataStruct.erc5453Type`の値が`ERC5453_TYPE_B`の場合、`GeneralExtensonDataStruct.endorsementPayload`は`SingleEndorsementData`の可変長配列のABIエンコードされたバイト列である必要があります。

各`SingleEndorsementData`には、2つの要素が含まれています。

- `endorserAddress`
    - このフィールドはアドレスであり、署名を行った承認者のアドレスを表します。
- `sig`
    - このフィールドは`65`バイトの署名で、`ECDSA（secp256k1）`署名です。
    - この署名は`endorserAddress`に対応する署名者のプライベートキーを使用して生成されます。

それぞれの`sig`署名は、`validityDigest`と呼ばれるハッシュに対して作成されます。
このハッシュは`ValidityBound`データ構造体の`hashStruct`に基づいて**EIP712**の`hashTypeDataV4`を使用して計算されます。

:::message
**ERC5453**では、特定のユーザーからの許可情報を持つ際に、署名者のアドレスと署名を含む`SingleEndorsementData`を使用し、それを`GeneralExtensonDataStruct`内にABIエンコードされた形式で格納します。
これにより、許可情報が正確に構造化され、検証されることが保証されます。
:::

```javascript
bytes32 validityDigest =
    eip712HashTypedDataV4(
        keccak256(
            abi.encode(
                keccak256(
                    "ValidityBound(bytes32 functionParamStructHash,uint256 validSince,uint256 validBy,uint256 nonce)"
                ),
                functionParamStructHash,
                _validSince,
                _validBy,
                _nonce
            )
        )
    );
```

`functionParamStructHash`は次のように計算されます。

```javascript
bytes32 functionParamStructHash = keccak256(
    abi.encodePacked(
        keccak256(bytes(_functionStructure)),
        _functionParamPacked
    )
);
return functionParamStructHash;
```

`_functionStructure`は、関数の`methodName(type1 param1, type2 param2, ...)`の形式で計算されます。
これは、関数名とそのパラメータの型を表します。
例として以下のようになります。

```javascript
transfer(address to, uint256 amount)
```
この構造は関数のシグネチャを表します。

`_functionParamPacked`は、関数の各パラメータをABIエンコードし、それらを連結したバイト列です。
例えば、関数に対するパラメータが`(address, uint256)`である場合、このフィールドはパラメータをABIエンコードしたバイト列になります。

`endorserAddressがecrecover(validityDigest, signature)`または`EIP1271(endorserAddress).isValidSignature(validityDigest, signature) == ERC1271.MAGICVALUE`と一致する場合、単一の承認は有効と見なされます。
つまり、署名が正当であることが確認されます。

準拠メソッドは、同じ`ERC5453_TYPE_B`の`endorsementPayload`内で有効な承認の数に対して閾値を設定できます。
つまり、複数の承認が必要な場合、その数を制限することができます。

`validSince`と`validBy`は、承認の有効期間を指定します。
これらはブロック番号またはタイムスタンプのどちらかで指定できます。
実装者は、有効期間がブロック番号またはタイムスタンプであるかどうかを明示する方法を見つける必要があります。
これにより、有効期間が明確に設定され、適切に検証できます。

:::message
**ERC5453**では、関数の構造、パラメータ、承認情報の検証に特定の規則が適用されます。
これにより、セキュアで信頼性の高い承認プロセスを実装することができます。
:::

## 補足

我々は**ERC5453**プロトコルにおいて、2つの異なるタイプ、つまり`ERC5453_TYPE_A`（単一の承認）と`ERC5453_TYPE_B`（複数の承認、コントラクト全体で同じ`nonce`）を選択しました。
これを採用することで、さまざまなユースケースに対応できます。

まず、`ERC5453_TYPE_A`では、単一の承認が行われます。
これは、特定のユーザーからの個別の承認を表します。
このタイプは、**ERC2612**や**ERC4494**などの既存のプロトコルでサポートされるユースケースに適しています。

次に、`ERC5453_TYPE_B`では、複数の承認が同じ`nonce`を共有し、コントラクト全体で協力して行われます。
これにより、閾値承認など、より複雑な承認パターンを実現できます。
また、新しい`ERC5453_TYPE_`を定義することで、さらに多様な承認タイプをサポートできます。

また、有効期限に関して、`validSince`と`validBy`の両方を含めました。
これにより、承認の有効期間を柔軟に設定できます。
**ERC5081**が採用されない可能性が高いため、プロトコル内にこれらの2つの数値を追加し、スマートコントラクトレベルで有効期限をサポートできるようにしました。

:::message
**ERC5453**プロトコルは、さまざまな承認タイプと有効期限の柔軟性を提供し、異なるユースケースに対応できるように設計されています。
:::

## 後方互換性

この設計では、将来の変更や拡張性を最大限に活用するために、`bytes calldata extraData`を前提としています。
この`extraData`は、コントラクトの関数呼び出し時に追加されるデータフィールドであり、将来の変更や機能追加に対応するための柔軟性を提供します。

このデザインの優れた点は、既存のERC規格に準拠している多くのコントラクトと互換性があることです。
たとえば、**ERC721**や**ERC1155**など、すでに存在する規格に従っているコントラクトは、`extraData`を活用して新しい機能を容易に統合できます。

さらに、このデザインに対応していないコントラクト（例：**ERC20**）も、ラッパーコントラクトやプロキシのアップグレードなどを介して、`extraData`のサポートを後から追加することができます。
これにより、多くの異なるコントラクトが将来の変更に対応し、新しい機能を取り入れることができます。

:::message
`extraData`を使用することで、コントラクトは将来の変更や拡張に対してオープンで柔軟なアプローチを提供し、様々なERC規格に適用できるようになります。
:::

## 参考実装

特定の署名者による署名の妥当性を確認するためのアルゴリズムに加えて、以下のリファレンス実装も提供されています。

```javascript
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import "./IERC5453.sol";

abstract contract AERC5453Endorsible is EIP712,
    IERC5453EndorsementCore, IERC5453EndorsementDigest, IERC5453EndorsementDataTypeA, IERC5453EndorsementDataTypeB {
    // ...

    function _validate(
        bytes32 msgDigest,
        SingleEndorsementData memory endersement
    ) internal virtual {
        require(
            endersement.sig.length == 65,
            "AERC5453Endorsible: wrong signature length"
        );
        require(
            SignatureChecker.isValidSignatureNow(
                endersement.endorserAddress,
                msgDigest,
                endersement.sig
            ),
            "AERC5453Endorsible: invalid signature"
        );
    }
    // ...

    modifier onlyEndorsed(
        bytes32 _functionParamStructHash,
        bytes calldata _extensionData
    ) {
        require(_isEndorsed(_functionParamStructHash, _extensionData));
        _;
    }

    function computeExtensionDataTypeB(
        uint256 nonce,
        uint256 validSince,
        uint256 validBy,
        address[] calldata endorserAddress,
        bytes[] calldata sigs
    ) external pure override returns (bytes memory) {
        require(endorserAddress.length == sigs.length);
        SingleEndorsementData[]
            memory endorsements = new SingleEndorsementData[](
                endorserAddress.length
            );
        for (uint256 i = 0; i < endorserAddress.length; ++i) {
            endorsements[i] = SingleEndorsementData(
                endorserAddress[i],
                sigs[i]
            );
        }
        return
            abi.encode(
                GeneralExtensionDataStruct(
                    MAGIC_WORLD,
                    ERC5453_TYPE_B,
                    nonce,
                    validSince,
                    validBy,
                    abi.encode(endorsements)
                )
            );
    }
}
```

#### `_validate`

```javascript
function _validate(
    bytes32 msgDigest,
    SingleEndorsementData memory endersement
) internal virtual {
    require(
        endersement.sig.length == 65,
        "AERC5453Endorsible: wrong signature length"
    );
    require(
        SignatureChecker.isValidSignatureNow(
            endersement.endorserAddress,
            msgDigest,
            endersement.sig
        ),
        "AERC5453Endorsible: invalid signature"
    );
}
```

**概要**

与えられたメッセージダイジェストと単一のエンドースメントデータを検証する関数。

**詳細**

**AERC5453Endorsible**コントラクト内で使用され、単一のエンドースメントデータが有効であるかどうかを検証します。
エンドースメントデータには、署名者のアドレスと署名データが含まれています。
検証は、署名の長さが正しいことと、与えられたメッセージダイジェストに対して署名が有効であることを確認します。

**引数**

- `msgDigest`
    - メッセージダイジェスト。
    - 検証対象のメッセージのハッシュ値。
- `endersement`
    - 単一のエンドースメントデータを表す構造体。
    - 署名者のアドレスと署名データが含まれています。

---

#### `onlyEndorsed`

```javascript
modifier onlyEndorsed(
    bytes32 _functionParamStructHash,
    bytes calldata _extensionData
) {
    require(_isEndorsed(_functionParamStructHash, _extensionData));
    _;
}
```

**概要**

指定された関数がエンドースメントされている場合にのみ実行を許可する修飾子。

**詳細**

関数が呼び出される前に`_isEndorsed`関数を使用してエンドースメントの有効性を検証します。
エンドースメントが有効である場合、関数の実行が許可されます。
エンドースメントが無効である場合、関数の呼び出しは失敗します。

**引数**

- `_functionParamStructHash`
    - 関数呼び出しのパラメータ構造体のハッシュ値。
- `_extensionData`
    - 拡張データ。

---

#### `computeExtensionDataTypeB`

```javascript
function computeExtensionDataTypeB(
    uint256 nonce,
    uint256 validSince,
    uint256 validBy,
    address[] calldata endorserAddress,
    bytes[] calldata sigs
) external pure override returns (bytes memory) {
    require(endorserAddress.length == sigs.length);
    SingleEndorsementData[] memory endorsements = new SingleEndorsementData[](endorserAddress.length);
    for (uint256 i = 0; i < endorserAddress.length; ++i) {
        endorsements[i] = SingleEndorsementData(
            endorserAddress[i],
            sigs[i]
        );
    }
    return abi.encode(
        GeneralExtensionDataStruct(
            MAGIC_WORLD,
            ERC5453_TYPE_B,
            nonce,
            validSince,
            validBy,
            abi.encode(endorsements)
        )
    );
}
```

**概要**

拡張データのデータタイプBを計算する関数。

**詳細**

指定された引数を使用してデータタイプBを生成します。
データタイプBには、ノンス、有効期間、署名者のアドレス、および署名データが含まれています。
これらの情報を使用してデータタイプBを生成し、それをバイトデータにエンコードして返します。

**引数**

- `nonce`
    - トランザクションのnonce値。
    - 一意のトランザクション識別子。
- `validSince`
    - トランザクションの有効開始時刻。
- `validBy`
    - トランザクションの有効終了時刻。
- `endorserAddress`
    - 署名者のアドレスの配列。
    - トランザクションを署名したアカウントのアドレスを含む。
- `sigs`
    - トランザクションの署名データの配列。各署名者の署名データを含む。

**戻り値**

この関数は計算されたデータタイプBを`bytes`データ型で返します。
データタイプBには、エンドースメントデータが含まれており、他のコントラクトで使用できる形式にエンコードされています。

---

### EndorsableERC721の参考実装

以下は、**ERC4494**と同様の動作を実現する**EndorsableERC721**の参考実装です。

```javascript
pragma solidity ^0.8.9;

contract EndorsableERC721 is ERC721, AERC5453Endorsible {
    //...

    function mint(
        address _to,
        uint256 _tokenId,
        bytes calldata _extraData
    )
        external
        onlyEndorsed(
            _computeFunctionParamHash(
                "function mint(address _to,uint256 _tokenId)",
                abi.encode(_to, _tokenId)
            ),
            _extraData
        )
    {
        _mint(_to, _tokenId);
    }
}
```

#### `mint`

```javascript
function mint(
    address _to,
    uint256 _tokenId,
    bytes calldata _extraData
)
    external
    onlyEndorsed(
        _computeFunctionParamHash(
            "function mint(address _to,uint256 _tokenId)",
            abi.encode(_to, _tokenId)
        ),
        _extraData
    )
{
    _mint(_to, _tokenId);
}
```

**概要**

新しいトークンを発行して`_to`アドレスに割り当てる関数。

**詳細**

**ERC721**コントラクトに新しいトークンを発行し、指定されたアドレス`_to`に割り当てます。
また、この関数はエンドースメントを要求し、指定された`_extraData`を検証します。
エンドースメントが有効である場合、新しいトークンが`_to`に発行されます。

**引数**

- `_to`
    - トークンを割り当てるアドレス。
- `_tokenId`
    - 新しいトークンのID。
- `_extraData`
    - エンドースメントの追加データ。

---

### ThresholdMultiSigForwarderの参考実装

**ThresholdMultiSigForwarder**（閾値マルチシグフォワーダー）の参考実装です。
この実装は、**Gnosis-Safeウォレットのように、複数の承認者が協力してリモートコントラクトの呼び出しを行うためのものです。

通常、このコントラクトを使用する場合、特定のトランザクションを実行するためには、ある条件（閾値）を満たす必要があります。
例えば、3人の承認者が必要な場合、3人の承認者がトランザクションに署名する必要があります。
一定数の承認者が署名した場合、トランザクションは実行されます。

この仕組みにより、重要なトランザクションや取引の実行において、複数の信頼性のある承認が必要な場合にセキュリティを向上させることができます。
つまり、一つの個人やエンティティだけでなく、複数の関係者が同意を示す必要があります。
これにより、トランザクションの不正使用や誤操作を防ぎ、信頼性とセキュリティを高めることができます。

```javascript
pragma solidity ^0.8.9;

contract ThresholdMultiSigForwarder is AERC5453Endorsible {
    //...
    function forward(
        address _dest,
        uint256 _value,
        uint256 _gasLimit,
        bytes calldata _calldata,
        bytes calldata _extraData
    )
        external
        onlyEndorsed(
            _computeFunctionParamHash(
                "function forward(address _dest,uint256 _value,uint256 _gasLimit,bytes calldata _calldata)",
                abi.encode(_dest, _value, _gasLimit, keccak256(_calldata))
            ),
            _extraData
        )
    {
        string memory errorMessage = "Fail to call remote contract";
        (bool success, bytes memory returndata) = _dest.call{value: _value}(
            _calldata
        );
        Address.verifyCallResult(success, returndata, errorMessage);
    }

}
```

#### `forward`

```javascript
function forward(
    address _dest,
    uint256 _value,
    uint256 _gasLimit,
    bytes calldata _calldata,
    bytes calldata _extraData
)
    external
    onlyEndorsed(
        _computeFunctionParamHash(
            "function forward(address _dest,uint256 _value,uint256 _gasLimit,bytes calldata _calldata)",
            abi.encode(_dest, _value, _gasLimit, keccak256(_calldata))
        ),
        _extraData
    )
{
    string memory errorMessage = "Fail to call remote contract";
    (bool success, bytes memory returndata) = _dest.call{value: _value}(
        _calldata
    );
    Address.verifyCallResult(success, returndata, errorMessage);
}
```

**概要**

指定されたアドレスにトークンとデータを転送し、外部コントラクトを呼び出す関数。

**詳細**

指定されたアドレス`_dest`に対して指定されたトークン`_value`とデータ`_calldata`を転送し、外部コントラクトを呼び出します。
エンドースメントを要求し、指定された`_extraData`を検証します。
呼び出し結果がエラーの場合、エラーメッセージがスローされます。

**引数**

- `_dest`
    - 転送先のアドレス。
- `_value`
    - トークンの転送量。
- `_gasLimit`
    - ガス制限。
- `_calldata`
    - 呼び出しデータ。
- `_extraData`
    - エンドースメントの追加データ。

---

## セキュリティ考慮事項

### リプレイ攻撃
リプレイ攻撃は、暗号認証に対する一種の攻撃です。
具体的には、既存の署名を再利用して、同じメッセージに対する署名の検証を回避する攻撃です。
この攻撃は、このEIPに依存する実装に影響を与える可能性があります。
なぜなら、ここで説明されているすべてのスマートエンドースメントは、誰でも取得できる公開の暗号的署名であるためです。
実装者は、リプレイ攻撃が同じスマートコントラクトの異なるデプロイメントや、類似のスマートコントラクトのデプロイメント、または別の`chainId`での同じコントラクトのバージョンなど、さまざまな攻撃シナリオで発生する可能性を考慮する必要があります。
`nonce`、`validSince`、`validBy`フィールドは攻撃の表面を制限するために使用できますが、すべての攻撃リスクを完全に排除することは難しいかもしれません。

### フィッシング攻撃
フィッシング（詐欺）攻撃にも注意が必要です。
フィッシング攻撃では、悪意のある攻撃者が、ユーザーを騙して、正当なように見せかけたスマートエンドースメントに署名させることがあります。
しかし、実際にはそのデータは悪意のあるアプリケーションに一致しています。

:::message
リプレイ攻撃は暗号認証を迂回する攻撃であり、フィッシング攻撃はユーザーを欺いて悪意のあるスマートコントラクトに署名させる攻撃です。
実装者はこれらの攻撃に対処するための対策を講じる必要があります。
:::

## 引用

Zainan Victor Zhou (@xinbenlv), "ERC-5453: Endorsement - Permit for Any Functions [DRAFT]," Ethereum Improvement Proposals, no. 5453, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5453.

## 最後に

今回は「関数の動作を拡張する規格である**ERC5750**の機能に依存し、1トランザクション内で複数関数呼び出しなどの仕組みを提案している規格である**ERC5453**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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
