【入門編】 EIP-712による型付きデータ署名の検証とフィッシング対策 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

こんにちは!Web3やIoTのセキュリティの世界へようこそ。セキュリティリサーチャーの視点から、日々進化する技術の裏側と、私たちが守るべき「デジタル世界の安全」についてお話ししています。

今回は、新人開発者の方や、これからセキュリティをしっかり学びたいという方に向けて、ブロックチェーン特有のちょっと怖い罠、そしてそれをスマートに防ぐ救世主「EIP-712」について、身近な例えを交えながら優しく紐解いていきたいと思います。

一歩ずつ、一緒に学んでいきましょう!

—

1. 玄関の鍵と「白紙の委任状」の恐ろしい罠

皆さんは、自分の家の玄関の鍵を管理するとき、どうしていますか?信頼できる家族にスペアキーを渡したり、宅配便の受け取りを頼んだりしますよね。

でも、もしこんな状況を想像してみてください。
ある日、見知らぬ人がやってきて、こう言いました。
> 「この紙にサインしてくれたら、お礼に素敵なプレゼントをあげますよ!」

その紙をよく見ると、なんと「あなたの家の所有権をすべてこの人に譲渡する」という恐ろしい内容が、ごちゃごちゃした暗号のような文字(16進数のバイト列)で書かれていました。文字が読めないからといって、うっかりサインしてしまう人はいませんよね?「怪しすぎるから絶対に断る!」となるはずです。

ところが、これと全く同じことが、Web3やスマートコントラクトの世界で日々起きています。

これまでの古い方式(例えば personal_sign など)では、ウォレット(MetaMaskなど)に接続したとき、画面に表示されるのは「意味不明な英数字の羅列(バイト列)」だけでした。ユーザーはそれに何が書いてあるか理解できないまま、「よく分からないけど、dApps(分散型アプリ)を使うために必要なんだな」と、言われるがままに承認ボタンを押してしまう……。

これは、泥棒に「あなたの家の全財産を差し出します」という白紙の委任状にサインさせられているようなものなんです。めちゃくちゃ怖いですよね。

—

2. 救世主「EIP-712」とは? — 構造化データ署名という防犯カメラ付きインターホン

この「意味が分からないから騙される」という問題を華麗に解決するために作られたのが、EIP-712という規格です。

EIP-712を使うと、ウォレットの画面に、ただの暗号の羅列ではなく、人間が読んで一目でわかる「構造化されたきれいなデータ」を表示できるようになります。

防犯の仕組みに例えるなら、これは「モニター付きインターホン」です。

訪問者が来たとき、ただ声だけで「開けてください」と言われるのではなく、モニターに相手の顔(誰から?)と、「お届け物:みかん 1箱、料金:0ETH」(何を?いくらで?)がハッキリと映し出されるのと同じです。これなら、怪しい人だったらすぐにインターホンを切ることができますよね。

EIP-712では、アプリ側が「私はこういう内容のデータに署名してほしいんです」という設計図(型)をあらかじめ定義し、ユーザーはその設計図に沿った分かりやすい情報(宛先や金額など)を確認してから署名します。これによって、悪意あるフィッシングサイトに「気づかないうちに全財産を奪われる」という事故を劇的に減らすことができるのです。

—

3. 実装してみよう!EIP-712のコードと仕組み

それでは、実際に開発現場でどのようにEIP-712が使われているのか、コードを見ていきましょう。「難しそう……」と思うかもしれませんが、順番に見ていけば大丈夫です!

ここでは、JavaScript(Ethers.jsなどを使用する環境)を想定して、ユーザーに「メッセージ」に署名してもらうための構造を定義するサンプルコードをご紹介します。

// 【実務で使えるサンプルコード】EIP-712を用いた構造化データの定義
// 新人エンジニアの方向けに、一つひとつの意味を日本語コメントで解説しています!

// 1. まず、アプリケーションの情報を定義します(ドメインセパレーターと呼ばれます)
// これにより、別のアプリで使われた署名が、自分のアプリで悪用されるのを防ぎます(リプレイ攻撃対策)。
const domain = {
    name: "安全なWeb3ショップ",      // アプリケーションの名前
    version: "1",                     // アプリのバージョン
    chainId: 1,                       // ネットワークID(Ethereumメインネットなら1)
    verifyingContract: "0x1234567890abcdef1234567890abcdef12345678" // 連携するスマートコントラクトのアドレス
};

// 2. ユーザーに署名してもらうデータの「型(構造)」を定義します。
// まさに「契約書のフォーマット」を作る作業です。
const types = {
    // 構造化データのメインとなる型
    PermitMessage: [
        { name: "owner", type: "address" },   // 署名する人のアドレス
        { name: "spender", type: "address" }, // 許可を与える相手のアドレス
        { name: "value", type: "uint256" },   // 金額や数量
        { name: "nonce", type: "uint256" }    // 一度使ったら使い回せないようにするための数字
    ]
};

// 3. 実際にユーザーに確認してもらう具体的なデータ(中身)を入れます。
const value = {
    owner: "0xAbC123...(あなたのウォレットアドレス)",
    spender: "0xDeF456...(ショップの決済用コントラクト)",
    value: 1000000000000000000n, // 1トークン(単位は最小単位)
    nonce: 0
};

// 4. ウォレット(MetaMaskなど)を使ってユーザーに署名を依頼する関数
async function requestEIP712Signature(signer, domain, types, value) {
    try {
        // ここでウォレットの画面が立ち上がり、ユーザーには綺麗なフォームとして表示されます!
        // ユーザーは「あ、DeF456宛てに1トークン送るんだな」と目で見て確認できます。
        const signature = await signer.signTypedData(domain, types, value);
        
        console.log("署名が正常に完了しました!:", signature);
        return signature;

    } catch (error) {
        console.error("ユーザーが署名をキャンセルしたか、エラーが発生しました:", error);
    }
}

このコードのポイントは、domain と types と value をきっちり分けて定義しているところです。特に domain 内にある chainId や verifyingContract は非常に重要で、「この署名は、ウチのサービス専用だからね!他の偽サイトで使い回しても無効だから!」という強力な鍵の役割を果たしています。

—

4. 現場のセキュリティリサーチャーからのアドバイス

実務や開発の現場でブロックチェーンやIoTデバイスに関わると、「動けばいいや」と古い署名方式をそのまま使ってしまったり、EIP-712の domain 設定を適当に済ませてしまうケースをたまに見かけます。

しかし、セキュリティの視点から言えば、それは「家の鍵を開けっ放しにしているのと同じ」です。攻撃者はユーザーの油断や、システムのちょっとした設定ミス(チェーンIDの検証漏れなど)を常に狙っています。

開発者である私たちができる最大の防御は、「ユーザーに分かりやすい情報を提示し、誤認させない仕組みを最初から組み込むこと」です。

  • ユーザー任せにしない: 「意味不明なバイト列に署名させない」UI/UXを絶対に守る。
  • ドメイン分離をサボらない: 別のコントラクトや別のネットワークで署名が流用されないよう、chainId や verifyingContract は厳格に設定する。

この2つを意識するだけで、あなたの作るプロダクトのセキュリティレベルはグッと跳ね上がります。

難しく感じるセキュリティの仕組みも、身近な防犯に置き換えて考えていけば、きっと本質が見えてくるはずです。ぜひ、今日の開発からEIP-712の丁寧な実装を取り入れてみてくださいね。それではまた、次のセキュリティ解説でお会いしましょう!

コメント

タイトルとURLをコピーしました