はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、Merkle Treeの構造を活かして、チャンクごとの最適な値を選びデータ保有を証明する仕組みを提案しているERC7585についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
ここでは、Merkle Tree(多数のデータを階層的にまとめて改ざん検知を可能にする仕組み)を使い、特定のデータを本当に保持しているかどうかを確認する「ストレージ証明」を成立させるための仕組みを説明します。
ポイントは二つあります。
ひとつは、従来よく使われるハッシュ関数(Keccak256 や SHA256)を置き換えるためのMixHashという新しい計算方式です。
もうひとつは、特定の公開データを誰でも証明できるようにする「公開データストレージ証明」です。
これらを組み合わせることで、ネットワーク上の任意の参加者が「私はこのデータを確かに持っています」と公開ネットワークに対して示せるようになります。
さらに、これらの仕組みをNFT標準であるERC721やERC1155と組み合わせると、データそのものの存在証明を扱えるようになり、利用できるアプリケーションの幅が広がります。
ERC721については以下の記事を参考にしてください。
ERC1155については以下の記事を参考にしてください。
動機
ERC721とERC1155はNFTを扱う代表的な規格です。
しかし現在の規格では、NFTが参照している公開データが本当に存在するのか、あるいは誰かがそのデータを保持しているのかを、第三者が確かめる方法が用意されていません。
これは、データの正しさや存在が重要になる多くの分野で大きな課題になります。
代表的な例として、以下のような領域があります。
| 領域 | 必要となる理由 |
|---|---|
| 分散型データマーケット | データが確かに存在し、売り手が保持していることを証明できないと取引が成立しにくいからです。 |
| 分散型ストレージ | 保存を依頼したデータが本当に保存されているかを確認する仕組みが必要だからです。 |
| 分散型オラクル | 外部データを参照する際、そのデータの真正性を確かめる必要があるためです。 |
こうした領域では、「誰かが該当データを確実に保持している」という事実を第三者が検証できることが必須ですが、既存のERC721やERC1155だけではそれができません。
そこで、MixHashを使った新しいストレージ証明の仕組みを導入し、NFTが結びつくデータについても存在証明を行えるようにすることで、より信頼性の高いアプリケーションを構築できます。
さらに、この仕組みが導入されることで、NFTの表現対象が単なるメタデータや画像に限られず、実データの保持状況そのものまで含めて扱えるようになり、データ経済圏の形成を後押しすることが期待できます。
仕様
MixHash(ファイル長を含む Merkle Root ハッシュ)
MixHashの構造
MixHashは、ファイルサイズ情報を埋め込んだMerkle treeのルートハッシュです。
通常のKeccak256やSHA256と同じ256bit長さですが、上位ビットに「ハッシュアルゴリズム」と「ファイルサイズ」を詰め込み、残りをMerkle Rootの下位192bitとして使います。
構造は以下のようになります。
+-----------256 bits MixHash-----------+
High |-2-|----62----|-------192-------| Low
これを表にすると以下のようになります。
| ビット長 | 内容 | 説明 |
|---|---|---|
| 上位2bit | ハッシュアルゴリズム選択 |
0b00 が SHA256、0b10 が Keccak256(0b01 と 0b11 は予約)です。 |
| 次の62bit | ファイルサイズ | バイト単位のファイルサイズです。最大 2^62 - 1 まで扱えます。 |
| 下位192bit | Merkle Rootの下位192bit | 指定されたハッシュアルゴリズムで構築したMerkle treeのルートハッシュの下位192bitです。 |
こうすることで、MixHashだけを見れば「どのハッシュ関数で」、「どのサイズのファイルに対して」、「どんなMerkle Rootか」が分かるようになっています。
MixHashの生成手順
MixHashを生成する流れは次の通りです。
- 対象ファイルを1KB(1024バイト)ごとに分割します。最後のチャンクが1024バイト未満であれば、ゼロ埋め(
0x00)して 1024バイトぴったりにします。 - 各チャンクについてハッシュを計算し、そのハッシュ値の下位128bitをMerkle treeの葉ノードとして使います。
- これらの葉ノードを使ってMerkle treeを構築します。
ルートノードのハッシュは256bitをそのまま使い、それ以外の中間ノードはハッシュ結果の下位 128bitだけを使う、というルールです。 - 最後に、
- ハッシュ種別(上位 2bit)
- ファイルサイズ(次の 62bit)
- Merkle Rootの下位192bitを組み合わせて、256bitのMixHashを作ります。
ここでのポイントは以下の2点です。
- Merkle treeの内部ノードは128bitに切り詰め、ルートだけ256bitにすることで、Merkle tree全体のハッシュデータ量を半分に圧縮していること。
- それでもMixHash自体は256bitのままなので、既存のKeccak256やSHA256の代わりに入れ替えてもビット長による追加コストは発生しないことです。
ファイル長を62bitに詰めていることで厳密にはハッシュ空間が狭まり、理論上の安全性は少し落ちますが、下位192bitがあれば実用上の衝突耐性は十分という前提に立っています。
MixHash生成の疑似コード
仕様に掲載されている疑似コードは以下です。
def generateMixHash(blockHeight,hashType,file):
chunk_hash_array = []
for chunk in file:
if len(chunk) < 1024:
chunk = chunk + b'\x00' * (1024-len(chunk))
chunk_hash_array.append(getChunkHash(chunk,hashType))
merkle_tree_root = getMerkleTreeRoot(chunk_hash_array,hash_type)
return mix_hash(hash_type, len(file), merkle_tree_root)
主な処理の意味は以下のようになります。
-
fileを1KBごとに読み込み、最後のチャンクはゼロ埋めします。 -
getChunkHashで各チャンクのハッシュを計算し、配列chunk_hash_arrayに格納します。 -
getMerkleTreeRootでMerkle treeのルートハッシュを計算します。 -
mix_hash関数で、hash_type(ハッシュアルゴリズムの種別)、len(file)(ファイルサイズ)、merkle_tree_rootの情報を組み合わせて256bitのMixHashを返します。
公開データストレージ証明(Public Data Storage Proofs)
MixHashを「公開データの識別子」として使うことで、誰でも「自分はこのMixHashに対応する公開データのコピーを保持している」と証明できる仕組みが作れます。
ここでこの証明を行う参加者を**Supplier(データ提供者)**と呼びます。
ストレージ証明の基本フロー
公開データ D(MixHash を mix_hash_d とする)について、ブロック高さ h のタイミングでストレージ証明を行う典型的な流れは以下のようになります。
-
Supplier の定義
報酬の対象となるストレージ証明を提出できるユーザーをSupplierと呼びます。 -
ブロックから
Nonceを取得
Supplier は、ブロック高さhのブロックから256bitのnonce(ノンス)を取得します。
通常は単純にそのブロックハッシュを使います。
nonceは「一度限りのランダムな値」で、各証明に固有のランダム性を持たせるためのものです。 -
最適な葉ノード
mの探索
Supplierは、データDを1KBチャンクごとに順番に見ていきます。
各チャンクに対してnonceを末尾に連結し、そのチャンクだけを差し替えたMerkle treeを計算し、新しいMerkle Rootが最小になるチャンクmを探します。
こうして「**nonceを付けたときに最も小さいMerkle Rootを与えるチャンク」がmになります。
最終的に、そのチャンクmのインデックスと共に、Merkle tree上の経路m_path、その葉ノードのデータm_leaf_dataを抽出します。 -
ストレージ証明の構成と提出
Supplierは、データDに対するストレージ証明として{mix_hash_d, h, m, m_path, m_leaf_data}を構成し、パブリックネットワークに送信します。 -
ネットワークによる検証
パブリックネットワークは、mix_hash_dをもとにm,m_path,m_leaf_dataが正しいかどうかを検証します。
つまり「mが本当にデータDの一部であるか」を査定します。
さらに、hによって証明の期限内性(タイムリーかどうか)をチェックします。
両方を満たせば、ネットワークはnonceと証明内容からproof_result_mを計算して保存します。 -
最適性の検証とチャレンジ
ネットワークは、与えられた証明が最適かどうか(本当にMerkle Rootを最小化しているか)は自力では判定できません。
代わりに、同じデータセットDを持つ他のSupplierが、よりよい{mix_hash_d, h, better_m, better_m_path, better_m_leaf_data}を送ることでチャレンジできます。 -
チャレンジの判定
ネットワークはproof_result_mとproof_result_better_mを比較し、後者がより良ければチャレンジ成功とみなします。
これにより、以前の証明が「改ざんまたは偽装」であったと判断できます。 -
ゲーム理論的な正しさ
一定時間内に誰からもチャレンジが来なければ、その証明はゲーム理論的に正しいとみなすことができます。
「もし偽物なら、正しいデータを持つ誰かがチャレンジしたはず」という前提に基づきます。 -
経済モデル
ネットワークは、正しいストレージ証明を提出した Supplierに報酬を与え、虚偽の証明を提出した者にはペナルティを与えるような経済モデルを設計する必要があります。
ストレージ証明生成・検証の疑似コード
仕様で示されている疑似コードは以下です。
オフチェーンでの証明生成
# generate proof off chain
def generateProof(mixHash, blockHeight,file)
nonce = getNonce(blockHeight)
hash_type = getHashType(mixHash)
chunk_hash_array = buildChunkHashArray(file,hash_type)
min_index = 0
min_merkle_tree_root = MAX_UINT256
min_chunk = None
m_index = 0
for chunk in file:
new_chunk = chunk + nonce
chunk_hash_array[m_index] = getChunkHash(new_chunk,hash_type)
merkle_tree_root = getMerkleTreeRoot(chunk_hash_array,hash_type)
chunk_hash_array[m_index] = getChunkHash(chunk,hash_type)
if (merkle_tree_root < min_merkle_tree_root):
min_merkle_tree_root = merkle_tree_root
min_index = m_index
min_chunk = chunk
m_index = m_index + 1
ここでは、chunk_hash_array に元のファイルのチャンクハッシュを入れておき、各チャンクについて一時的に chunk + nonce を使ってMerkle Rootを計算し、その都度最小のMerkle Rootを更新していく、という手順で「最適なチャンク」とその位置を決めています。
オンチェーンでの検証
// verify on chain
function verifyDataProof(mixHash, blockHeight, m_index, m_path, m_leaf_data) {
if(current_block_height - blockHeight > MAX_BLOCK_DISTANCE) {
revert("proof expired");
}
hash_type = getHashType(mixHash);
merkle_tree_root = getMerkleTreeRootFromPath(m_path,m_leaf_data,hash_type);
if(low192(merkle_tree_root) != low192(mixHash)) {
revert("invalid proof");
}
nonce = getNonce(blockHeight);
proof_result = getMerkleTreeRootFromPath(m_path,m_leaf_data.append(nonce),hash_type);
last_proof_result,last_prover = getProofResult(mixHash, blockHeight);
if(proof_result < last_proof_result) {
emit ProofPunish(last_prover);
updateProofResult(mixHash, blockHeight, proof_result, msg.sender);
}
}
検証処理のポイントは以下です。
-
current_block_height - blockHeightがMAX_BLOCK_DISTANCEを超えていれば期限切れとして拒否します。 -
mixHashからハッシュ種別を取り出し、m_pathとm_leaf_dataに基づいてMerkle Rootを再計算します。 - 計算されたMerkle Rootの下位 192bit が
mixHashの下位192bitと一致しなければ不正な証明として拒否します。 - 同じブロック高さから
nonceを再計算し、m_leaf_data + nonceを使って改めてproof_resultを計算します。 - 過去に保存されている
last_proof_resultより小さければ、過去の証明者last_proverに対してProofPunishを発行し、新しいproof_resultと現在の送信者を記録します。
Merkle treeの圧縮
getMerkleTreeRoot の実装では、ルート以外のノードのハッシュ値を下位128bitに切り詰めています。
これにより、Merkle tree全体で保持するハッシュ値の合計サイズがほぼ半分になり、ストレージ証明のサイズも最小限に抑えられます。
考え方としては「ルートだけ256bitを維持し、それ以外は128bitに縮める」という方針です。
Sourcing Attack(外部データ取得攻撃)への防御
ここでいうSourcing Attackとは、Supplierがデータをローカルに保存せず、証明のたびにネットワーク経由で外部からデータを取得してストレージ証明を作ろうとする攻撃です。
MixHashベースのストレージ証明では、特定のタイミングで生成される nonce に依存して証明を作るため、「与えられた時間内にファイル全体を走査すること」が必須になります。
この性質をうまく利用して、Sourcing Attackを難しくしています。
時間制限による防御
Supplierは、限られた時間内にストレージ証明を作成し提出する必要があるように設計されます。
例としてEthereumを考えると、ブロックタイムは約15秒、MAX_BLOCK_DISTANCE = 2 とすると、有効な証明を提出できる時間は約30秒となります。
大きなファイルを30秒以内にネットワーク経由で全取得し、チャンクごとに nonce を追加して最適なMerkle Rootを探索するのは現実的には非常に困難です。
そのため、証明をまともに行いたいSupplierはデータをローカルに保持せざるを得ないという状況に誘導できます。
経済インセンティブによる防御
ストレージ証明に基づく経済モデルでは、一般的に「最初に正しい証明を出した人」に報酬が支払われます。
Sourcing Attackを仕掛けると、
- ネットワーク越しの取得に時間がかかるため提出が遅れやすい、
- その間にローカルにデータを持つ他のSupplierが先に正しい証明を出してしまう可能性が高い、
という事情から、経済的に見て割に合わない戦略になります。
つまり、「ローカル保存するコスト」より「ネット越しに毎回取りに行って負けるリスク」の方が高く評価されるように経済設計を行うことで、ローカル保存を自然に選ぶように誘導できます。
Sourcing Attack防御成功率
Sourcing Attackに対する防御の効き具合は、ローカルストレージからの読み込み速度とネットワークからの取得速度の差に大きく依存します。
ここでは以下のように記号を定義しています。
-
TNetwork
ネットワーク経由でファイルを取得する時間。 -
TLocal
ローカルストレージからファイルを読む時間。 -
AvgProofTime
平均的な「有効な証明を出せる時間枠」(ブロック時間などを含む)。
防御成功率 R は以下の式で定義されます。
R = (TNetwork - TLocal) / AvgProofTime
直感的には、ネット経由のほうがローカルよりどれくらい遅いか(分子)とどれくらいの時間余裕があるか(分母)の比率で、「外部データ取得攻撃が不利になる度合い」を見ています。
ブロック時間が長いネットワークでの問題
AvgProofTime が大きくなると、防御成功率 R は小さくなっていきます。
例えばBTCネットワークの場合、1ブロックあたり約 10 分、2ブロックの時間は約20分となり、AvgProofTime が大きくなります。
そうなると、ネット経由でファイルを持ってきてもまだ十分に時間が残っているため、結果として、Sourcing Attackが成功しやすくなるという問題が出てきます。
PoWを導入した場合の防御率
そこで、Sourcing Attackへの防御を強化する手段として、**Proof of Work(PoW)**を導入する案が示されています。
PoWを追加すると、防御成功率の式は以下のように変わります。
R = (TNetwork - TLocal) / (AvgProofTime - AvgPoWTime)
ここで、AvgPoWTime は平均的なPoW計算にかかる時間です。
PoWを導入すると、有効な証明時間枠の一部がPoW計算に消費されるため、実質的に「証明に割ける時間」が減り、ネットからデータを取ってきて証明を作る余裕がさらに削られます。
PoWを組み込んだストレージ証明
PoWを導入したときの証明戦略は、有効時間内にストレージ証明を作成して提出することと、そのうえで、なるべく多くのPoW計算をこなし「より良いPoW結果(例えば末尾ビット数が多くゼロになっているハッシュ)」を提出することになります。
仕様では、PoWを取り込むために先ほどのステップ2を以下のように変更する例が示されています。
- Supplierは、データ
Dを1KBチャンクごとに走査し、最適な葉ノードmを探します。その時、nonceと自分で作るノイズ値をチャンク末尾に連結し、新しい Merkle Root を最小化するとともに、PoW 難易度の条件(proof_result_mの末尾xビットが0であるなど)を満たすようにします。mとノイズが決まったら、対応するm_pathとm_leaf_dataを取り出します。
PoW対応版の疑似コード
PoWを組み込んだ証明生成の疑似コードは以下です。
# generate proof with PoW off chain
POW_DIFFICULTY = 16
def generateProofWithPow(mixHash, blockHeight,file)
nonce = getNonce(blockHeight)
hash_type = getHashType(mixHash)
chunk_hash_array = buildChunkHashArray(file,hash_type)
min_index = 0
min_merkle_tree_root = MAX_UINT256
min_chunk = None
m_index = 0
noise = 0
while True:
for chunk in file:
new_chunk = chunk + nonce + noise
chunk_hash_array[m_index] = getChunkHash(new_chunk,hash_type)
merkle_tree_root = getMerkleTreeRoot(chunk_hash_array,hash_type)
chunk_hash_array[m_index] = getChunkHash(chunk,hash_type)
if (merkle_tree_root < min_merkle_tree_root):
min_merkle_tree_root = merkle_tree_root
min_index = m_index
min_chunk = chunk
m_index = m_index + 1
if(last_zero_bits(min_merkle_tree_root) >= POW_DIFFICULTY):
break
noise = noise + 1
m_path = getMerkleTreePath(chunk_hash_array, min_index)
return storage_proof(mixHash, blockHeight, min_index, m_path, min_chunk,noise)
ここでは、noise を0からインクリメントしながら、chunk + nonce + noise を使ってMerkle Rootを計算し、そのルートハッシュの末尾ビット数 last_zero_bits(...) が POW_DIFFICULTY 以上になるまで繰り返します。
POW_DIFFICULTY = 16 なら「末尾16bitがゼロになるMerkle Root」を見つけるまでループします。
PoWが重くなるほど、ストレージ証明の生成コストは上がります。
PoW導入のデメリットと推奨方針
PoWを導入すると、確かにSourcing Attackには強くなりますが、以下のような問題もあります。
- ストレージ証明の生成コストが大きく増えるため、広範なノードにデータを保存してもらう目的(分散保存の促進)と逆行しやすいこと。
- PoWに特化したハードウェアを持つ参加者が有利になり、ゲームへの参加の敷居が上がって分散性が失われる可能性があること。
そのため仕様では、よほど必要なケースを除きPoW機構は有効化しないことを推奨しています。
制約事項(Limitations)
このストレージ証明の仕組みには、いくつかの制約や注意点があります。
-
ファイルが小さすぎる場合に不向き
とても小さなファイルについては、もともとSourcing Attackへの防御が難しいです。
ファイル全体が小さいとネットワーク越しでもすぐに取得できてしまい、ローカル保存しているかどうかの差が出づらくなるためです。 -
データが本当に公開データかは別問題
公開データストレージ証明は、MixHashに対応するデータが公開かどうかは保証しません。
例えば、あるSupplierが自分だけが知っているデータに対してMixHashを作り、「これは公開データだ」と主張し、そのMixHashに対するストレージ証明で報酬を得る、といった攻撃がありえます。
誰もそのデータを知らないためチャレンジできず、結果として「自分だけが持つデータ」を利用した報酬搾取が起き、エコシステム全体が崩壊しかねません。
そのため、各ユースケースでは、あるMixHashが本当に公開データを指しているのか、誰でもアクセスできるデータなのかを検証する仕組みや運用ルールを別途用意することが重要になります。
しかし、これは一般には簡単な問題ではない、という点が明示されています。
ERC 拡張提案:MixHashによる高価値公開データの追跡
MixHashが指すデータが本当に公開されているかどうか、またその価値をどう追跡するかを考えるとき、Ethereumの既存エコシステムを活用する案が示されています。
ERCPublicDataOwner インターフェース
構造化されていないデータ(例えば大きなファイルやバイナリ列)を扱うスマートコントラクトが、そのデータとMixHashの対応関係と「所有者」を公開するためのインターフェースが ERCPublicDataOwner です。
/// @title ERCPublicDataOwner Standard, query Owner of the specified MixHash
/// Note: the ERC-165 identifier for this interface is <ERC-Number>.
interface ERCPublicDataOwner {
/**
@notice Queries Owner of public data determined by Mixhash
@param mixHash Mixhash you want to query
@return If it is an identified public data, return the Owner address, otherwise 0x0 will be returned
*/
function getPublicDataOwner(bytes32 mixHash) external view returns (address);
}
getPublicDataOwner の役割は以下です。
- 引数
mixHashに対して、その MixHash が「このコントラクトに関連する公開データ」であれば所有者アドレスを返します。 - 該当しない場合は
0x0を返します。
これにより、ある MixHash がどのコントラクトに紐づく公開データか、誰がオーナーかをオンチェーンで問い合わせできるようになります。
ERC721/ERC1155のMixHash拡張
既に広く使われているNFTエコシステム(ERC721/ERC1155)においても、NFTとMixHashの関係を明示的に結びつける拡張が提案されています。
ERC721MixHashVerfiy(原文のスペルは Verfiy)という拡張インターフェースを、ERC721/ERC1155コントラクトが任意で実装できるようにします。
/// @title ERC721MixHashVerfiy Extension, optional extension
/// Note: the ERC-165 identifier for this interface is <ERC-Number>.
interface ERC721MixHashVerfiy{
/**
@notice Is the tokenId of the NFT is the Mixhash?
@return True if the tokenId is MixHash, false if not
*/
function tokenIdIsMixHash() external view returns (bool);
/**
@notice Queries NFT's MixHash
@param _tokenId NFT to be querying
@return The target NFT corresponds to MixHash, if it is not Mixhash, it returns 0x0
*/
function tokenDataHash(uint256 _tokenId) external view returns (bytes32);
}
この拡張の役割は以下です。
-
tokenIdIsMixHash()
NFTのtokenId自体がMixHashを意味する設計かどうかを返します。
例えば「tokenId = MixHashを整数として解釈したもの」というような設計をしている場合にtrueを返せます。 -
tokenDataHash(uint256 _tokenId)
特定のNFT(_tokenId)に対応するMixHashを返します。
もしそのNFTがMixHashに紐づいていない場合は0x0を返します。
このようにERC721/ERC1155がMixHashと結びつくことで、ある公開データ(MixHash)と、それを表すNFT、そのNFTの所有者という関係をオンチェーンで辿れるようになり、高価値な公開データの所有や取引をEthereum上で管理しやすくなります。
補足
ストレージ証明と従来アプローチの位置づけ
ストレージ証明(space-time proof とも呼ばれる)は、「どれくらいの容量を、どれくらいの期間保存しているか」を証明する仕組み全般を指します。
すでにさまざまな実装やプロジェクトが存在し、多くはゼロ知識証明(内部のデータ内容を明かさずに正しさだけを証明する暗号技術)に基づいたコピー証明を使っています。
それに対して、ここで説明しているストレージ証明は、ゼロ知識証明のように暗号的に「最適な証明かどうか」をチェーン上で厳密に検証するのではなく、ナッシュ均衡(Nash 合意)に基づくゲーム理論的な仕組みを採用しています。
ナッシュ均衡とは、ゲーム理論における「誰も自分だけ戦略を変えても得をしない状態」のことです。
ここでは、ストレージ証明に関わる参加者が、「正しくデータを保存して正しい証明を出す」以外の戦略を取ると損をするような状態を作り出す、という意味で使われています。
Nash合意に基づくストレージ証明の特徴
Nash合意ベースのストレージ証明には、以下の二つの核心原則があります。
| 観点 | 内容 |
|---|---|
| a. 最適性の検証方法 | パブリックネットワーク(オンチェーン)は、証明が本当に「最適」かどうかまでは自力で検証せず、経済的なゲーム設計に委ねます。これにより、証明の構築・検証コストを大きく下げます。 |
| b. データの価値に基づく自然淘汰 | 価値のないデータにはゲームとしての価値も生じません。そのため、そうしたデータは自然にシステムから排除され、際限のない永続保存を約束する必要がありません。 |
ゼロ知識証明ベースのコピー証明では、暗号的に厳密な検証を行うため、計算コストや実装の複雑さが高くなりがちです。
一方、Nash合意ベースのストレージ証明は、「他の参加者がより良い証明を出してチャレンジできる」という前提のもとで、シンプルな検証だけをチェーン上で行い、残りを経済インセンティブに任せる形になっています。
この方式の結果として、チェーン上での検証ロジックは「正しいかどうか」と「より良いかどうか」だけに絞られ、最適性の確証は「チャレンジが起きないこと」によってゲーム理論的に担保される、という構造になります。
スマートコントラクトだけで完結する設計
MixHashとストレージ証明の仕組みは、スマートコントラクトだけで完結する形で実装できます。
具体的には、オンチェーン側は以下のような役割に絞られます。
- 提出されたストレージ証明の正しさと有効期限内であることの検証
- より良い証明が出てきたときの比較と更新
- 正しい証明者への報酬、誤った証明者へのペナルティを行うためのフック
参考実装では GASコストがやや高めであることが明記されていますが、それでもスマートコントラクトの範囲内で完結します。
この構造の利点は、ストレージ証明の仕組みと経済モデルを分離できることです。
経済モデル(報酬・罰則のルール)はユースケースごとに自由に設計しつつ、基盤となる証明ロジックは共通で使い回せます。
Sybil攻撃に対するスタンス
ここでSybil攻撃とは、1人のSupplierが複数の偽名(アカウント)を使い、「同じデータ D を n コピー保存している」と主張しながら、実際には1コピーしか保存していないような攻撃を指します。
複数のアドレスから n 個のストレージ証明を出して報酬を水増ししようとするイメージです。
この設計では、公開データに対するSybil攻撃を厳密に防ぐことは目標にしていません。
理由は以下です。
- Sybil攻撃を完全に防ぐためには、「本当に
nコピー保存しているのか」を検証する追加コストが必要であり、データ保存そのものに余計な負担がかかってしまうこと。 - ストレージ証明の目的は、「公開データのコピー数を厳密に計測すること」ではなく、「公開データが一定の確率でどこかに複数存在している状態を経済的に実現すること」にあること。
つまり、「何コピー存在しているか」を厳密に知る必要はなく、「十分な数のコピーが存在している確率が高い状態」を作れればよいという設計思想です。
そのため、公開データストレージ証明の設計そのものでは、Sybil攻撃を完全に排除することを目指していません。
互換性
HashTypeによるマルチチェーン対応
MixHashの上位2bitにあるHashType(ハッシュアルゴリズム種別)は、EVM互換チェーンとBTC系チェーンの両方に対応可能にするための仕掛け**です。
- EVM互換チェーンでは、一般的にKeccak256が使われます。
- BTC系チェーンでは、SHA256が標準的に使われます。
MixHashは、HashTypeによって「どのハッシュアルゴリズムでMerkle treeを構築したか」を自己記述的に持っているため、異なるチェーンでも同じMixHashを共有しながら、それぞれのチェーンの標準的なハッシュアルゴリズムを使えるようになっています。
MixHashを用いたクロスチェーン価値アンカー
MixHashは、「同じ公開データを指す共通のハッシュ値」として振る舞います。
これを使うと、以下のようなことが可能になります。
あるMixHashに対応するデータが、EVM 系チェーンでは「あるNFTやトークンとしての価値」を持ち、BTC系チェーンでは、別のスクリプトやUTXO構造の中で「別の経済的価値」を持つといった形で、同一データに紐づく価値を複数チェーンにまたがって追跡できます。
このように、MixHashは「特定のデータに結びついた、チェーン横断の価値の基準点(アンカー)」として扱うことができます。
それぞれのチェーン上で異なるモデル・スマートコントラクト・スクリプトを使いつつも、MixHashという共通キーを通じて、「どのデータに対する価値なのか」を共有できる形です。
既存との互換性を意識した HashType 設計
後方互換性を確保するため、MixHashのHashTypeに関して次のような設計がされています。
| Bit パターン | 意味 |
|---|---|
0b00 |
SHA256(デフォルトのハッシュアルゴリズム) |
0b10 |
Keccak256 |
0b01 / 0b11
|
予約(将来の拡張用) |
特に、デフォルトのHashTypeをSHA256に設定することで、BTC系などSHA256を前提にしているチェーンとも自然に整合します。
一方で、0b01 と 0b11 の 2 つの種別はあえて利用せずに残してあり、将来別のハッシュアルゴリズムや別種の構造に拡張したい場合にも対応できるように余白を残しています。
このような設計により、既存のチェーン・ツールとの互換性を壊さず、新しいハッシュアルゴリズムや新チェーンが登場した場合にも対応できる余地を確保しつつ、MixHash を「永く使い続けられる共通フォーマット」として設計するという狙いが明確になっています。
セキュリティ
公開データを前提としたストレージ証明であること
ここで説明されているストレージ証明の仕組みは、公開データを前提に設計されています。
ストレージ証明のプロセスでは、データ全体の中から1KB単位のチャンクを取り出し、そのチャンク自体やチャンクに関する情報(Merkle パスなど)をパブリックネットワークに送信します。
仕組み上、以下のようなことが起こります。
| 観点 | 内容 |
|---|---|
| データ断片の公開 | 証明のために1KBのデータチャンクをそのままネットワーク上に流すことがあります。 |
| ネットワークの性質 | パブリックチェーンでは、一度送信されたデータは誰でも参照でき、基本的に消せません。 |
そのため、このストレージ証明の設計を秘密情報や個人情報などのプライベートデータに対してそのまま使うべきではないと明示的に注意喚起しています。
プライベートデータで同じ仕組みを使ってしまうと、証明に使われた1KBチャンクがそのままオンチェーンに残り、第三者に見られるリスクがあります。
暗号化前の生データや、復元可能な一部分が漏れるだけでも問題になるケースが多いため、ここで説明されている「公開データ向けストレージ証明」=プライベートデータには不適切という整理が重要になります。
MixHash自体はプライベートデータにも応用可能だが、別設計が必要
MixHashの設計そのものは、プライベートなファイルに対するストレージ証明にも使えるポテンシャルがあるとされています。
ただし、その場合は次のような追加設計や工夫が必要になります。
- 元データの扱い方(前処理の仕方)を変更すること
例として、データを暗号化したうえでチャンク化する、マスク処理を挟むなどが考えられます。 - ストレージ証明の構成方法を変更すること
チャンクそのものを直接オンチェーンに出さないようにしたり、チャンクから導出されるデータだけを使うようにしたりする必要があります。
つまり、MixHashは「256bitのハッシュフォーマット」としてはプライベートファイルにも対応できる柔軟さを持っていますが、そのままの手順で証明を作るとプライベートデータが漏れるため、設計を調整しなければなりません。
この論文・仕様では、プライベートファイル向けストレージ証明の詳細な設計については扱わない、とはっきり書かれています。
プライベートデータ向けの安全なストレージ証明は、公開データとは別の前提・要件・リスクを持つため、ここでは範囲外(out of scope)という扱いです。
公開データ用とプライベートデータ用の両方を利用するプロジェクトの存在
参考実装として挙げられているプロジェクトの中には、以下の両方を実装しているものも存在すると明記されています。
- 公開データストレージ証明
- プライベートデータストレージ証明
これは、MixHashやストレージ証明の考え方を少し拡張・応用することで、公開データとプライベートデータの両方に対応できる可能性があることを示しています。
引用
Liu Zhicong (@waterflier), William Entriken (@fulldecent), Wei Qiushi (@weiqiushi), Si Changjun (@photosssa), "ERC-7585: MixHash and Public Data Storage Proofs [DRAFT]," Ethereum Improvement Proposals, no. 7585, December 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7585.
最後に
今回は「Merkle Treeの構造を活かして、チャンクごとの最適な値を選びデータ保有を証明する仕組みを提案しているERC7585」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!