メインコンテンツへスキップ

EIP-1271: スマート・コントラクトの署名の作成と検証

eip-1271
スマート・コントラクト
検証
署名
中級
ネイサン・H・リョン
2023年1月12日
12 分で読めます

EIP-1271 (opens in a new tab)標準により、スマート・コントラクトは署名を検証できるようになります。

このチュートリアルでは、デジタル署名、EIP-1271の背景、およびSafe (opens in a new tab)(旧Gnosis Safe)で使用されているEIP-1271の具体的な実装の概要を説明します。これらはすべて、独自のコントラクトにEIP-1271を実装するための出発点として役立ちます。

署名とは何か?

この文脈における署名(より正確には「デジタル署名」)とは、メッセージに加えて、そのメッセージが特定の人物/送信者/アドレスから送信されたことを示す何らかの証明を組み合わせたものです。

例えば、デジタル署名は次のようになります。

  1. メッセージ:「自分のイーサリアムウォレットでこのウェブサイトにログインしたい。」
  2. 署名者:私のアドレスは0x000…です。
  3. 証明:私、0x000…が実際にこのメッセージ全体を作成したという証明です(これは通常、暗号技術的なものです)。

デジタル署名には「メッセージ」と「署名」の両方が含まれることに注意することが重要です。

なぜでしょうか?例えば、あなたが私に署名用の契約書を渡し、私が署名ページだけを切り取って、契約書の残りの部分なしで署名だけを返したとしたら、その契約書は無効になります。

同様に、デジタル署名も関連するメッセージがなければ意味を持ちません!

なぜEIP-1271が存在するのか?

イーサリアムベースのブロックチェーンで使用するデジタル署名を作成するには、通常、他の誰も知らない秘密鍵が必要です。これがあなたの署名をあなた自身のものにする理由です(秘密鍵を知らなければ、他の誰も同じ署名を作成することはできません)。

あなたのイーサリアムアカウント(つまり、外部所有アカウント/EOA)には秘密鍵が関連付けられており、ウェブサイトや分散型アプリケーション (dapp) が署名を求めてきた場合(例:「イーサリアムでログイン」など)、通常はこの秘密鍵が使用されます。

アプリは、ethers.jsのようなサードパーティのライブラリを使用して、あなたの秘密鍵を知ることなく (opens in a new tab)、あなたが作成した署名を検証 (opens in a new tab)し、署名を作成したのが間違いなく_あなた_であることを確認できます。

実際、EOAのデジタル署名は公開鍵暗号技術を使用しているため、オフチェーンで生成および検証できます!これがガスレスのDAO投票の仕組みです。オンチェーンで投票を送信する代わりに、暗号技術ライブラリを使用してオフチェーンでデジタル署名を作成および検証できます。

EOAアカウントには秘密鍵がありますが、スマート・コントラクトアカウントにはいかなる種類の秘密鍵もありません(そのため、「イーサリアムでログイン」などはスマート・コントラクトアカウントではネイティブに機能しません)。

EIP-1271が解決しようとしている問題は、スマート・コントラクトに署名に組み込むことができる「秘密」がない場合、スマート・コントラクトの署名が有効であることをどのように判断できるかということです。

EIP-1271はどのように機能するのか?

スマート・コントラクトには、メッセージの署名に使用できる秘密鍵がありません。では、署名が本物かどうかをどのように判断できるのでしょうか?

1つのアイデアとして、スマート・コントラクトに署名が本物かどうかを単に_尋ねる_ことができます!

EIP-1271が行うのは、特定の署名が有効かどうかをスマート・コントラクトに「尋ねる」というこのアイデアを標準化することです。

EIP-1271を実装するコントラクトには、メッセージと署名を受け取るisValidSignatureという関数が必要です。その後、コントラクトは何らかの検証ロジックを実行し(仕様ではここで特定のものを強制していません)、署名が有効かどうかを示す値を返します。

もしisValidSignatureが有効な結果を返した場合、それはコントラクトが「はい、この署名とメッセージを承認します!」と言っているようなものです。

インターフェース

以下は、EIP-1271仕様の正確なインターフェースです(_hashパラメータについては後述しますが、今のところは検証されるメッセージと考えてください)。

EIP-1271の実装例:Safe

コントラクトはさまざまな方法でisValidSignatureを実装できます。仕様では正確な実装について多くを語っていません。

EIP-1271を実装している注目すべきコントラクトの1つがSafe(旧Gnosis Safe)です。

Safeのコードでは、署名を作成および検証できるように、isValidSignature2つの方法 (opens in a new tab)実装されています (opens in a new tab)

  1. オンチェーンメッセージ
    1. 作成:Safeの所有者は、メッセージを「署名」するための新しいSafeトランザクションを作成し、メッセージをデータとしてトランザクションに渡します。マルチシグのしきい値に達するのに十分な数の所有者がトランザクションに署名すると、トランザクションがブロードキャストされて実行されます。トランザクション内には、メッセージを「承認済み」メッセージのリストに追加する(signMessage(bytes calldata _data))というSafe関数があります。
    2. 検証:SafeコントラクトでisValidSignatureを呼び出し、検証するメッセージをメッセージパラメータとして渡し、署名パラメータには空の値 (opens in a new tab)(つまり、0x)を渡します。Safeは署名パラメータが空であることを確認し、暗号技術的に署名を検証する代わりに、メッセージが「承認済み」メッセージのリストにあるかどうかを確認するだけでよいと判断します。
  2. オフチェーンメッセージ:
    1. 作成:Safeの所有者はオフチェーンでメッセージを作成し、マルチシグの承認しきい値を超えるのに十分な署名が集まるまで、他のSafeの所有者に個別にメッセージに署名してもらいます。
    2. 検証:isValidSignatureを呼び出します。メッセージパラメータには、検証するメッセージを渡します。署名パラメータには、各Safe所有者の個別の署名をすべて連続して連結したものを渡します。Safeは、しきい値を満たすのに十分な署名があること、および各署名が有効であることを確認します。そうであれば、署名の検証が成功したことを示す値を返します。

_hashパラメータとは正確には何ですか?なぜメッセージ全体を渡さないのですか?

EIP-1271インターフェース (opens in a new tab)isValidSignature関数は、メッセージ自体ではなく、_hashパラメータを受け取ることに気付いたかもしれません。これが意味するのは、任意の長さのメッセージ全体をisValidSignatureに渡す代わりに、メッセージの32バイトのハッシュ(通常はkeccak256)を渡すということです。

コールデータの各バイト(つまり、スマート・コントラクト関数に渡される関数パラメータデータ)には16ガス(ゼロバイトの場合は4ガス)のコストがかかる (opens in a new tab)ため、メッセージが長い場合は多くのガスを節約できます。

以前のEIP-1271仕様

実際に使用されているEIP-1271仕様の中には、最初のパラメータの型がbytes(固定長のbytes32ではなく任意の長さ)で、パラメータ名がmessageであるisValidSignature関数を持つものがあります。これはEIP-1271標準の古いバージョン (opens in a new tab)です。

独自のコントラクトにEIP-1271をどのように実装すべきか?

ここでの仕様は非常に自由度が高いです。Safeの実装にはいくつかの良いアイデアがあります。

  • コントラクトの「所有者」からのEOA署名を有効と見なすことができます。
  • 承認されたメッセージのリストを保存し、それらのみを有効と見なすことができます。

最終的には、コントラクト開発者であるあなた次第です!

まとめ

EIP-1271 (opens in a new tab)は、スマート・コントラクトが署名を検証できるようにする汎用性の高い標準です。これにより、スマート・コントラクトがEOAのように機能する道が開かれます(例えば、「イーサリアムでログイン」をスマート・コントラクトで機能させる方法を提供します)。また、さまざまな方法で実装できます(Safeには、検討すべき重要で興味深い実装があります)。