# [ERC6358] 複数チェーンでトークンを同期する仕組みを理解しよう！

> ERC6358は、ブロックチェーン間での資産のやり取りを安全かつ検証可能に行うための共通インターフェースを定義する提案です。

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

## はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、複数のブロックチェーン間でトークンの状態を安全に同期させ、どのチェーンでも同じ資産として扱えるようにする仕組みを提案している**ERC6358**についてまとめていきます！

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

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

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

## 概要

**ERC6358**は、ブロックチェーン間での資産のやり取りを安全かつ検証可能に行うための共通インターフェースを定義する提案です。
**ERC6358**の目的は、特定のコンセンサスメカニズム（ブロックチェーンの合意形成方法）に依存しない形で、スマートコントラクト層で動作するクロスチェーンブリッジ（異なるブロックチェーン間の接続手段）を標準化することです。

この仕組みによって、既存の**ERC20**（トークンの共通規格）や**ERC721**（NFTの共通規格）を基にした、マルチチェーン上で動作する新しい「**グローバルトークン**」を定義できるようになります。
つまり、複数のブロックチェーン上に分散して存在するトークンを、ひとつの統一的なトークンとして扱える仕組みを構築できます。

#### アーキテクチャ

![image.png](/images/qiita-fd0428bd-881c-4ecf-ad31-fe146c84d008.webp)
https://eips.ethereum.org/EIPS/eip-6358

図は、**ERC6358**の全体構造を示しています。
**ERC6358**によって、複数のブロックチェーン上でトークンの状態を同期的に記録できる「**グローバルトークンプロトコル**」が実現されます。
ここでいう同期的に記録とは、各チェーンで発生したトークンの状態（所有者や残高など）が、他のチェーンでも一致した状態で保たれることを意味します。

この同期は、ブロックチェーンの外部で動作するトラストレスなオフチェーン・シンクロナイザー（信頼を必要としない外部同期システム）によって実現されます。
この仕組みを使えば、チェーン間で資産を直接「`transfer`」することなく、すべてのチェーンが同じ状態を共有することが可能になります。

## 動機

現在のブロックチェーンの資産移動の仕組みでは、トークンブリッジ（チェーン間の資産移転を担う仕組み）によって資産が分断される問題があります。
例えば、Ethereum（ETH）を他のブロックチェーンへブリッジを通して移動させた場合、その移動先のチェーンが何らかの理由で停止・破損すると、ユーザーの資産は失われてしまいます。

**ERC6358**の中心的な考え方は「**`transfer` ではなく同期**」です。
つまり、実際に資産を別のチェーンへ移動させるのではなく、各チェーンが同じ資産状態を共有しているように見せることを目的としています。
この方式であれば、仮に他のすべてのチェーンが停止したとしても、Ethereumが稼働している限りユーザーの資産は失われません。

このアプローチによって、以下のような効果が得られます。

| 問題点                | **ERC6358**による解決                |
| ------------------ | ------------------------- |
| ブリッジ利用時に資産が分断される   | 同期によって一元的に扱えるため、資産が分断されない |
| 移動先チェーンの故障による資産喪失  | `transfer` ではなく同期のため、元の資産は安全に保持される |
| マルチチェーン環境での資産管理が複雑 | 統一的なトークンプロトコルにより、簡潔に管理可能  |

結果として、資産の断片化問題が解消され、マルチチェーン上でのユーザー資産の安全性が大幅に向上します。


## 仕様

### オムニバースアカウント

**ERC6358**では、すべてのユーザーをグローバルに識別するための **オムニバースアカウント（Omniverse Account, 略称：o-account）** を定義します。
これは、全ブロックチェーンで共通して使えるユーザー識別の役割を持ちます。

o-account は、楕円曲線暗号 secp256k1（セックp256k1）によって生成される公開鍵として表現することが推奨されています。
異なるブロックチェーン環境間でこのアカウントを対応づけるためのマッピング機構を設けることも推奨されます。

### データ構造

オムニバーストランザクション（Omniverse Transaction, 略称：o-transaction）は、以下のデータ構造で表されます。

#### `ERC6358TransactionData`

```javascript
/**
 * @notice Omniverse transaction data structure
 * @member nonce: The number of the o-transactions. If the current nonce of an omniverse account is `k`, the valid nonce of this o-account in the next o-transaction is `k+1`. 
 * @member chainId: The chain where the o-transaction is initiated
 * @member initiateSC: The contract address from which the o-transaction is first initiated
 * @member from: The Omniverse account which signs the o-transaction
 * @member payload: The encoded business logic data, which is maintained by the developer
 * @member signature: The signature of the above informations. 
 */
struct ERC6358TransactionData {
    uint128 nonce;
    uint32 chainId;
    bytes initiateSC;
    bytes from;
    bytes payload;
    bytes signature;
}
```

オムニバーストランザクションを表す基本的な構造体。
o-transaction に関するすべての必要情報を保持するデータ形式です。
この構造体を使うことで、チェーン間で一貫したトランザクション情報の管理と署名検証が可能になります。

**パラメータ**

* `nonce`
  * トランザクション番号。o-account の現在の nonce が `k` の場合、次のトランザクションでは `k+1` になります。
* `chainId`
  * トランザクションを開始したブロックチェーンの識別子。
* `initiateSC`
  * トランザクションを開始したスマートコントラクトのアドレス。
* `from`
  * トランザクションを署名したオムニバースアカウント。
* `payload`
  * 開発者が定義するビジネスロジックのデータをエンコードしたもの。
* `signature`
  * 上記すべての情報を署名したデータ。

#### Fungible Token（代替可能トークン）の場合

トランザクションの `payload` は以下のようなデータ構造にエンコードされることが推奨されます。

##### `Fungible`

```javascript
struct Fungible {
    uint8 op;
    bytes exData;
    uint256 amount;
}
```

代替可能トークンの操作内容を表す構造体。
`payload` にエンコードされるトークン操作情報です。
操作種別（op）、対象アカウント（exData）、操作量（amount）を保持します。

**パラメータ**

* `op`
    * 操作の種類。
    * `0〜31` は予約値、`32〜255` は開発者が自由に定義可能。
    * `0`
        o-account `from` が `exData` へ `amount` のトークンを送信。
    * `1`
        o-account `from` が `exData` に `amount` のトークンを新規発行。
    * `2`
        o-account `from` が自身の `amount` トークンをBurn）。
* `exData`
    * 操作対象の追加データ。操作によって内容が異なります。
    * op = 0, 1 の場合
        受取アカウントを格納。
    * op = 2 の場合
        空。
* `amount`
    * 操作されるトークンの数量。

署名対象の生データは、`op`、`exData`、`amount` を順に連結したバイト列で構成することが推奨されます。

#### Non-Fungible Token（非代替トークン）の場合

##### `NonFungible`

```javascript
struct NonFungible {
    uint8 op;
    bytes exData;
    uint256 tokenId;
}
```

NFT（非代替トークン）の操作内容を表す構造体。
NFTに関する操作情報を `payload` に格納します。
操作種別（op）、対象アカウント（exData）、対象トークンID（tokenId）を含みます。

**パラメータ**

* `op`
    * 操作の種類。
    * `0`
        `from` が `exData` に `tokenId` を送信。
    * `1`
        `from` が `exData` に `tokenId` を発行。
    * `2`
        `from` が `tokenId` を焼却。
* `exData`
    * 受取先アカウント。`op` によって内容が異なります。
* `tokenId`
    * 操作対象となるNFTの識別番号。

署名対象データは、`op`、`exData`、`tokenId` のバイト列を連結して生成することが推奨されます。

##### `decodeData`

```javascript
function decodeData(bytes memory _data) internal pure returns (Fungible memory) {
    (uint8 op, bytes memory exData, uint256 amount) = abi.decode(_data, (uint8, bytes, uint256));
    return Fungible(op, exData, amount);
}
```

バイトデータを `Fungible` 構造体に変換する関数。
トランザクションの `payload` フィールドから、実際の操作データ（op, exData, amount）を復元します。

**引数**

* `_data`
  * `payload` のバイトデータ。

**戻り値**

* `Fungible`
  * 復号化されたトークンデータ構造。

##### `getTransactionHash`

```javascript
function getTransactionHash(ERC6358TransactionData memory _data) public pure returns (bytes32) {
    Fungible memory fungible = decodeData(_data.payload);
    bytes memory payload = abi.encodePacked(fungible.op, fungible.exData, fungible.amount);
    bytes memory rawData = abi.encodePacked(_data.nonce, _data.chainId, _data.initiateSC, _data.from, payload);
    return keccak256(rawData);
}
```

トランザクションデータ全体のハッシュ値を計算する関数。
トランザクションのすべての主要フィールドを連結し、`keccak256` でハッシュ化して署名検証に用います。

**引数**

* `_data`
  * トランザクションデータ構造 `ERC6358TransactionData`。

**戻り値**

* `bytes32`
  * トランザクションハッシュ値。

### インターフェース

**ERC6358**に準拠するすべてのコントラクトは、以下のインターフェース `IERC6358` を実装する必要があります。

#### `IERC6358`

```javascript
interface IERC6358 {
    event TransactionSent(bytes pk, uint256 nonce);
    function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external;
    function getTransactionCount(bytes memory _pk) external view returns (uint256);
    function getTransactionData(bytes calldata _user, uint256 _nonce) external view returns (ERC6358TransactionData memory, uint256);
    function getChainId() external view returns (uint32);
}
```

##### `TransactionSent`

```javascript
event TransactionSent(bytes pk, uint256 nonce);
```

トランザクションが送信されたときに発行されるイベント。
`sendOmniverseTransaction` が呼び出された時に、署名者の公開鍵とトランザクション番号をログとして出力します。

**パラメータ**

* `pk`
  * 署名者の公開鍵。
* `nonce`
  * トランザクションの連番。

##### `sendOmniverseTransaction`

```javascript
function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external;
```

オムニバーストランザクションを送信する関数。
この関数はトランザクションの署名、nonce、payload の検証を行い、`TransactionSent` イベントを発行します。
実際のトランザクション処理は、別の関数で実行することが推奨されます（非同期実行も可）。

**引数**

* `_data`
  * トランザクションデータ構造 `ERC6358TransactionData`。

##### `getTransactionCount`

```javascript
function getTransactionCount(bytes memory _pk) external view returns (uint256);
```

指定した `o-account` の送信済みトランザクション数を取得する関数。
これにより、次のトランザクションで使用すべき `nonce` を確認できます。

**引数**

* `_pk`
  * 取得対象のオムニバースアカウント。

**戻り値**

* `uint256`
  * 現在のトランザクション数（次の有効な `nonce`）。

##### `getTransactionData`

```javascript
function getTransactionData(bytes calldata _user, uint256 _nonce) external view returns (ERC6358TransactionData memory, uint256);
```

特定のユーザーと `nonce` に対応するトランザクション情報を取得する関数。
ユーザーが指定したトランザクションの内容と、そのタイムスタンプを取得します。

**引数**

* `_user`
  * ユーザーのオムニバースアカウント。
* `_nonce`
  * 取得したいトランザクション番号。

**戻り値**

* `ERC6358TransactionData`
  * トランザクションの詳細データ。
* `uint256`
  * トランザクション発生時のタイムスタンプ。

##### `getChainId`

```javascript
function getChainId() external view returns (uint32);
```

現在のブロックチェーンの識別子を取得する関数。
この関数は、コントラクトが稼働しているブロックチェーンのIDを返します。

**戻り値**

* `uint32`
  * チェーンID。

### 拡張仕様

#### Fungible Token

```javascript
interface IERC6358Fungible is IERC6358 {
    function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256);
}
```

##### `omniverseBalanceOf`

指定したアカウントのグローバル残高を取得する関数。
全ブロックチェーンでのトークン残高を統合的に確認できます。

**引数**

* `_pk`
  * 残高を確認したいオムニバースアカウント。

**戻り値**

* `uint256`
  * アカウントのグローバル残高。

#### Non-Fungible Token

```javascript
interface IERC6358NonFungible is IERC6358 {
    function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256);
    function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory);
}
```

##### `omniverseBalanceOf`

NFTを保有する数を取得する関数。
指定したオムニバースアカウントが保有しているNFTの総数を返します。

**引数**

* `_pk`
  * アカウント識別子。

**戻り値**

* `uint256`
  * 保有NFT数。

##### `omniverseOwnerOf`

指定されたNFTトークンの所有者を取得する関数。
`tokenId` に対応するNFTを所有するオムニバースアカウントを返します。

**引数**

* `_tokenId`
  * NFTトークンの識別番号。

**戻り値**

* `bytes`
  * 所有者のオムニバースアカウント。

## 補足

#### アーキテクチャ

**ERC6358**では、複数チェーンにデプロイされたスマートコントラクトが、信頼不要（trustless）なオフチェーン・シンクロナイザーを介して、同一の `o-transaction`（オムニバーストランザクション）を同期実行します。
ここで「**信頼不要**」とは、シンクロナイザー自身を特別に信用しなくても安全性が保たれる設計を指し、シンクロナイザーは他者の署名済みデータのみを運搬し、内容の恣意的な改変を行わない前提で成り立ちます。

このとき、各チェーンにデプロイされる**ERC6358**コントラクトは **Abstract Node（抽象ノード）** と呼ばれます。
抽象ノードは、それぞれが同じグローバル状態のコピーを保持しており、最終的に整合（最終的整合性）します。
つまり、一時的に差異が生じても、同期が進むにつれて同じ状態へ収束します。

オフチェーン・シンクロナイザーは、あるチェーン上の抽象ノードに公開された `o-transaction` を他のチェーンへ運搬する実行プログラムです。
シンクロナイザーは署名付き `o-transaction` を搬送するだけなので、検証（署名、nonce、残高・所有権チェック）は各チェーンの抽象ノード側で行われ、ここに安全性の担保があります。

**主要コンポーネントの対応関係**

| コンポーネント | 役割       |
| ------------ | ----------------- |
| 抽象ノード（Abstract Node） | 各チェーン上で**ERC6358**の状態を保持し、`o-transaction` の検証・受付・実行を行います。 |
| オフチェーン・シンクロナイザー      | 署名済みの `o-transaction` を別チェーンへ配送するだけの運搬レイヤーです。           |
| `o-account`  | secp256k1 公開鍵で表すグローバルなユーザー識別子です。 |
| `o-transaction` | グローバルに同期される操作要求で、署名、nonce、payload などを含みます。    |

#### 原理

設計のコアはグローバル一意のアカウント（`o-account`）と `nonce` による順序制御、そしてチェーン間の最終的整合性です。

まず、`o-account` がユーザーを一意に表し、同じアカウントが全チェーンで同じ意味を持ちます。
次に、2種類の `nonce` が整合性を担保します。
1つは `o-transaction` 内の `nonce`、もう1つは各チェーンの抽象ノードが保持するアカウントごとの `nonce`（アカウント `nonce`）です。
同期時には、`o-transaction` の `nonce` が、そのチェーンに記録されているアカウント `nonce` と一致（＝次に受理されるべき値）するかを照合します。
これにより、トランザクション順序の食い違いや二重実行を防ぎ、各チェーンの状態は最終的に一致します。

この仕組みによって、残高（FT）や所有権（NFT）の検証が全チェーンで同じ規則のもとに行われ、同期＝一貫した状態管理が実現します。

#### ワークフロー

以下では、ユーザーAを例に、Ethereumでの送信から複数チェーンへの同期、最終整合までの一連の流れを説明します。
`nonce` は、Aの `o-account` に対して各チェーンが保持するアカウント `nonce`を指します。

1. **送信準備**
    Aの現在のアカウント `nonce` がある状態で、AはEthereum上の抽象ノードに対し、`IERC6358::sendOmniverseTransaction` を呼び出します。
    `o-transaction` に含める `nonce` は、その時点のアカウント `nonce` と一致する値（次に受理されるべき値）でなければなりません。

2. **送信と検証（Ethereum 側）**
    Ethereum上の抽象ノードは、`o-transaction` の署名を検証します。
    加えて、FTなら残高、NFTなら所有権が十分にあるか、そして `o-transaction` の `nonce` がアカウント `nonce` と一致するかをチェックします。
    検証に成功すると、`o-transaction` はEthereum側で「**公開**」されます。
    ただし、即時実行は行わず、一定時間の待機を推奨します。
    これは全チェーンにメッセージが行き渡るためのバッファとして機能します。

3. **一時的不一致の発生**
   送信直後、Ethereum側では「**A が最新 `nonce` を提出済み**」の状態になりますが、他チェーンでは未反映のため、チェーン間で一時的に `nonce` と状態に差が生じます。

4. **シンクロナイザーによる配送**
    オフチェーン・シンクロナイザーは、Ethereum上で新たに公開された `o-transaction` を検知し、未反映の他チェーンへ配送します。
    ここでは報酬メカニズムを用意できます。
    例えば、サービス手数料からの分配や独自のマイニング方式など、**ERC6358**トークンのデプロイヤーが任意に設計できます。
    これにより、シンクロナイザーが競って迅速に配送する誘因が生まれます。

5. **他チェーンでの受理・検証・実行**
    他チェーンの抽象ノードは、受け取った `o-transaction` について、署名・残高/所有権・`nonce` の検証を行います。
    待機時間が経過したら、同一のロジックを実行します。
    実行が完了すると、各チェーンでAのアカウント `nonce` は `+1` され、残高や所有権などの状態も一致します。

6. **最終的整合**
    すべてのチェーンで実行が終わると、Aのアカウント `nonce` は全チェーンで同じ値となり、関連アカウントの残高や所有権も同一のグローバル状態に収束します。
    これが 最終的整合性の達成です。

## 参考実装

#### オムニバースアカウント（Omniverse Account）

オムニバースアカウントは、楕円曲線暗号 secp256k1の公開鍵で表現される、全チェーンで共通のユーザー識別子です。
参考として、以下のサンプルが提示されています。

* 公開鍵（o-account の表現例）
  `3092860212ceb90a13e4a288e444b685ae86c63232bcb50a064cb3d25aa2c88a24cd710ea2d553a20b4f2f18d2706b8cc5a9d4ae4a50d475980c2ba83414a796`

* 対応する秘密鍵（検証用サンプル）
  `cdfa0e50d672eb73bc5de00cc0799c70f15c5be6b6fca4a1c82c35c7471125b6`

以下では、サンプル値をそのまま定数として掲示します（実運用には絶対に使用しないでください）。

##### `OMNIVERSE_PUBLIC_KEY_EXAMPLE`

```javascript
const OMNIVERSE_PUBLIC_KEY_EXAMPLE =
  "3092860212ceb90a13e4a288e444b685ae86c63232bcb50a064cb3d25aa2c88a24cd710ea2d553a20b4f2f18d2706b8cc5a9d4ae4a50d475980c2ba83414a796";
```

オムニバースアカウントを表す secp256k1 の公開鍵のサンプル値。
チェーン横断で同一ユーザーを指し示すための識別子として公開鍵を用います。
公開鍵は検証に使用され、署名の正当性を各チェーンのコントラクトで確認できます。

**パラメータ**

* 文字列（hex）
  * secp256k1 公開鍵を 16 進文字列で表現したもの。

##### `OMNIVERSE_PRIVATE_KEY_EXAMPLE`

```javascript
const OMNIVERSE_PRIVATE_KEY_EXAMPLE =
  "cdfa0e50d672eb73bc5de00cc0799c70f15c5be6b6fca4a1c82c35c7471125b6";
```

上記公開鍵に対応する secp256k1 秘密鍵のサンプル値。
署名の生成に使用される秘密鍵です。
実運用では絶対に公開してはいけません。
ここでは仕様理解のための参考として提示されています。

**パラメータ**

* 文字列（hex）
  * secp256k1 秘密鍵を 16 進文字列で表現したもの。

#### 異なる環境向けのマッピング機構

単純な実装として、2種類のマッピングを用意します。
1つは「**secp256k1 公開鍵 → 対象環境のアカウントアドレス**」、もう1つは「**対象環境のアカウントアドレス → secp256k1 公開鍵**」です。
これにより、グローバルな `o-account` と各チェーン固有のアドレス体系を相互に参照できます。

##### `pkToAddress` / `addressToPk`

```javascript
// 環境ごとの対応例（ダミー）
const pkToAddress = {
  // "secp256k1 公開鍵（hex）": "環境固有のアカウントアドレス"
  "3092860...a796": "0xAbCd...1234"
};

const addressToPk = {
  // "環境固有のアカウントアドレス": "secp256k1 公開鍵（hex）"
  "0xAbCd...1234": "3092860...a796"
};
```

secp256k1 の公開鍵と、特定環境のアカウントアドレスを相互参照するためのシンプルなマッピング。
`o-account` をグローバル識別子としつつ、各チェーンのアドレス体系に自然に紐づけます。
コントラクトや周辺ツールは、どちらの表現からでも相手側へ変換できます。

**パラメータ**

* `pkToAddress`
  * キーに secp256k1 公開鍵（hex）、値に環境固有のアドレスを持つ連想配列。
* `addressToPk`
  * キーに環境固有のアドレス、値に secp256k1 公開鍵（hex）を持つ連想配列。

#### Flowにおけるマッピング

Flowには、**アカウントアドレス → 公開鍵**を取り扱う仕組みが標準で備わっています。
公開鍵はアカウント（Flow の組み込みデータ構造）にバインドされ、アドレスから直接公開鍵を取得できます。
また、**公開鍵 → アドレス** の参照を作る場合は、`{String: Address}` のように、公開鍵（文字列表現）をキー、Flow のアドレス型を値とするマッピングを用意します。

##### `flowPkToAddress`

```javascript
// Flow 向けの公開鍵→アドレスの対応例（型イメージ）
const flowPkToAddress /* : { [publicKeyString: string]: Address } */ = {
  // "公開鍵（文字列）": Address
  "3092860...a796": "0x1cf0e2f2f715450"
};
```

Flow環境における公開鍵からアドレスを引くための対応表の例。
Flowはアドレスから公開鍵を取り出す機構を標準で備えています。
逆方向の参照（公開鍵→アドレス）をアプリ側で保持することで、`o-account` 起点の解決が容易になります。

**パラメータ**

* `flowPkToAddress`
  * キーに公開鍵（文字列表現）、値に Flow のアドレス型を持つマップ。

#### ERC6358トークン（ERC-6358 Token）

**ERC6358**トークンは、前節までに示したインターフェース（`IERC6358`、`IERC6358Fungible`、`IERC6358NonFungible`）を用いて実装できます。
さらに、既存の**ERC20**や**ERC721**と組み合わせて利用することも可能です。
これにより、「**状態の同期**」を中核に据えたマルチチェーン運用と、既存トークン規格の資産表現を両立できます。

参考として、以下の実装例が示されています（本稿ではコード本文は提示されていません）。

* インターフェース実装例
    [`IERC6358`](https://eips.ethereum.org/assets/eip-6358/src/contracts/interfaces/IERC6358.sol)、[`IERC6358Fungible`](https://eips.ethereum.org/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol)、[`IERC6358NonFungible`](https://eips.ethereum.org/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol)。
* 操作用の共通ツール
    **ERC6358**の `o-transaction` を扱うための汎用ユーティリティ群。
    https://eips.ethereum.org/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol
* トークン実装例
    [**ERC6358**代替可能トークン実装例](https://eips.ethereum.org/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol)、[**ERC6358**非代替トークン実装例](https://eips.ethereum.org/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol)。

これらの実装例は、署名検証、`nonce` 検証、チェーン間同期（待機時間やオフチェーン配送の前提）といった**ERC6358**の要点を踏まえて構築されます。**ERC20**/**ERC721**と組み合わせる場合は、送受信・発行・Burnなどの資産操作を `o-transaction` の `payload` としてエンコードし、インターフェースで定義された検証手順に従って各チェーンのコントラクトが同一の結果へ収束するように実行します。

## セキュリティ

#### 攻撃ベクトル分析

**ERC6358**では、二つの明確なロールが存在します。

| ロール  | 説明       |
| ---------------------- | ---------- |
| 一般ユーザー（common user）    | o-transaction（オムニバーストランザクション）を発行する当事者。      |
| シンクロナイザー（synchronizer） | チェーン間の差分を検出し、`o-transaction` データを他のチェーンに運ぶ役割。 |

これらのロールにおいて、考えられる攻撃の可能性を順に検討します。

#### シンクロナイザーは不正を行うか？

##### シンクロナイザーの立場

シンクロナイザーはトランザクションを生成する権限を持たず、あくまで他者の署名済みデータを運搬するだけの存在です。
署名の生成には各ユーザーの秘密鍵が必要であり、シンクロナイザー自身が他人の署名を偽造することは不可能です。
そのため、もし不正が発生するとすれば、それは「**署名を渡した一般ユーザー側の問題**」であり、
シンクロナイザーが単独で不正を行うことは設計上できません。

##### シンクロナイザーの動作と検証

シンクロナイザーが運ぶ `o-transaction` データは、受け取り側のコントラクトで必ず署名検証を行います。
つまり、シンクロナイザーが改ざんや偽造を試みても、署名が一致しないため却下されます。
報酬は「**有効な（署名とデータが正しい）o-transaction を配信した場合のみ**」に発生するため、不正を行っても利益が得られず、また成功もしません。

##### シンクロナイザーの行動原理

シンクロナイザーは、以下のような場合に動作します。

| 条件  | 説明              |
| ------------ | --------------------------- |
| チェーンAのアカウントnonceが、チェーンBで公開された `nonce` より小さい場合 | チェーンB側の `o-transaction` をAへ届ける。 |
| 同じ `nonce` で異なるトランザクションデータが存在する場合             | 署名済みデータの不整合を検知し、双方に通知・転送する。 |

これらはチェーン間の整合性を自動的に補完するための動作です。

#### 一般ユーザーは不正を行うか？

##### 攻撃シナリオ（ダブルスペンド攻撃の試み）

理論的には、一般ユーザーが同じ `nonce` を使って異なるトランザクションを複数のチェーンに送る、
いわゆる二重支払い（Double Spend）攻撃を試みることが可能です。
以下のような状況を想定します。

| 状況          | 内容                 |
| -------------------------- | ------------------ |
| ユーザーAのアカウントnonceが全チェーンで同じ値 | 例えば `nonce = 5`     |
| 所有トークン                     | 100 X（ERC6358トークン） |

1. AがPolkadotのパラチェーンで10 XをBに送るトランザクション（`ot-P-ab`）を発行。
   `nonce` は6となり、署名・検証後にPolkadotで公開されます。
2. 同時に、AがEthereumで別のトランザクション（`ot-E-ac`）を作成し、
   同じ `nonce = 6` で10 XをCに送る内容を発行。

一見すると、Aは同じ資産を二重に送信し、PolkadotとEthereumで異なる残高状態を作り出せたように見えます。

##### システムの反応と防御

この状況では、シンクロナイザーが以下の動作を行います。

* Polkadotで公開された `ot-P-ab` をEthereumに配信。
* Ethereumで公開された `ot-E-ac` をPolkadotに配信。

どちらも署名が有効であるため、最初に提出したシンクロナイザーには報酬が与えられます。
両チェーンのコントラクトは、同じ `nonce` で異なるトランザクションが存在することを検知し、ユーザーAが不正を試みたことを明確に特定します。
署名はA本人のものなので、否認は不可能です。

さらに、**ERC6358**ではトランザクションの即時実行を禁止し、一定の「**待機時間**」を設けています。
この遅延によって、チェーン間で同期が完了するまでに不整合や不正を検出・遮断する時間を確保できます。
したがって、Aが同時に複数チェーンへ送信しても、いずれも最終的には1つだけが有効化されて他方は無効化または巻き戻しされます。

##### 極端なケースへの対処

万が一、ネットワーク障害などで全シンクロナイザーが機能しなかった場合でも、
次のようなフェイルセーフ機構が用意されています。

| 状況                         | 対応策                            |
| -------------------------- | ------------------------------ |
| 一部シンクロナイザーが悪意のあるノードに接続している | 複数のネイティブノードに接続してリスクを分散。        |
| 全シンクロナイザーのネットワークが遮断された     | ネットワーク復旧後に自動的に同期を再開。           |
| 待機時間経過後に不正トランザクションが実行された   | `nonce` の不一致をもとに履歴を特定し、元の状態へ巻き戻し。 |

また、開発者やトークン発行者は、不正ユーザーに対してアカウントのロックなどの懲罰的措置を設けることも可能です。
これらは各トークンプロジェクトのトークノミクスに応じて設定されます。

## 引用

Shawn Zheng (@xiyu1984), Jason Cheng <chengjingxx@gmail.com>, George Huang (@virgil2019), Kay Lin (@kay404), "ERC-6358: Cross-Chain Token States Synchronization [DRAFT]," Ethereum Improvement Proposals, no. 6358, January 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6358.

## 最後に

今回は「複数のブロックチェーン間でトークンの状態を安全に同期させ、どのチェーンでも同じ資産として扱えるようにする仕組みを提案している**ERC6358**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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