T2 · 暗号学の基礎
なぜ署名の文字列が「私だ」と証明できるのか?
- 練習する能力
- BuilderProtocol Literacy
- 実践
- ローカルで鍵ペアを作り、メッセージに署名して公開鍵で検証する。1 バイトだけ変え、検証が失敗することを確認する。
- AI Lab
- AI にマークル証明の検証関数を書かせ、自分で偽の証明を作って拒否されることを確認する。
現実の問題
DApp のログインにはパスワードがありません。
ウォレットを接続し、署名するとログインできます。バックエンドが受け取るのは、アドレスと 132 文字の十六進数だけです。それで所有者からの要求だと判断します。
従来なら登録、パスワードの要約、照合、セッションが必要ですが、ここではありません。初めての利用者でも、サーバーは秘密を保存しません。
しかも署名は公開されています。HTTP、アクセスログ、途中の通信、自分のコンソールへ現れる可能性があります。
見られても、なぜ他者はなりすませないのでしょうか。
誰でもコピーできる文字列が、なぜ本人を証明できるのでしょうか。
思考実験
知らない街で、初対面の人へ、このメモを自分が書いたと証明します。
共通の秘密はなく、自分の秘密を教えられず、メモは誰でも見てコピーできるという三条件です。
第一案:合言葉を末尾に書く。
見た相手は同じメモを作れます。検証者が偽造者にもなります。実装より構造の問題です。
第二案:合言葉から計算し、元へ戻せない文字列を書く。
逆算しなくても、そのまま別のメモへコピーできます。証明が内容に結び付いていないため、何度でも使えます。
第三案:合言葉と内容を合わせて計算する。
別のメモには合いませんが、検証するには合言葉が必要で、第一案に戻ります。
検証できることと、偽造できないことが両立しません。
普通の一方向の関数だけでは解決できません。別のものが必要です。
証明を作る数字と検査する数字の対で、検査用から生成用を導けないものです。
検査用を公開すると誰でも検証でき、生成用を渡さなければ誰も偽造できません。
132 文字は秘密ではなく、秘密で計算した一回の結果です。そのメッセージだけに有効なので、見ても別のものを作れません。
あなたが決める
要求がアドレス A の所有者から来たと、どう確認しますか。
結果を観察する
四案を同じ軸で比べます。
| 方法 | 検証に秘密が必要か | 内容と結び付くか | 再使用できるか | 漏えいの結果 |
|---|---|---|---|---|
| 秘密を送る | 必要 | いいえ | 自由にできる | 全資産を失う |
| 秘密の要約 | 不要 | いいえ | 自由にできる | なりすまし |
| 秘密と内容の要約 | 必要 | はい | できない | 双方が偽造できる |
| 公開検証可能な証明 | 不要 | はい | 内容による | 対象メッセージだけ |
最後の曖昧さは重要です。再使用の可否は暗号学より、メッセージに何を書くかで決まります。
署名自体は永続します。「ログイン」だけなら、いつでも別サイトや別チェーンで使えます。一回、一か所に限定するには、乱数、ドメイン、チェーン番号、期限を署名する内容へ含めます。
自分で設計する責務です。
モデルを作る
三つの基本要素と、一つの組合せです。
第一:一方向の要約。任意長を固定長へ変え、同じ入力は同じ結果、少しの変更は大きな変化となり、逆算は計算上困難です。
短い安定した名前、改ざん検知、大量のデータを一値にまとめる用途があります。
第二:鍵の対。秘密鍵は乱数、公開鍵は一方向の導出、アドレスはさらに要約です。
- 乱数 = 秘密鍵
- 楕円曲線の演算 → 公開鍵
- 要約と切出し → アドレス
第三:署名と検証。鍵と要約から、Ethereum では r、s、v の合計 65 バイトを作ります。検証はメッセージと署名から、署名者の公開鍵を復元してアドレスへ変えます。
公開鍵で復号するのではなく、署名から公開鍵を回復します。先に相手を知らなくても検証で分かるため、取引の送信アドレスも計算できます。
本人確認の全過程は次です。
- サーバーが乱数とドメインを含むメッセージを出す
- ウォレットが要約へ署名する
- 内容と署名を返す
- サーバーがアドレスを復元する
- 照合し、乱数を無効にする
組合せ:大量のデータを一値にまとめ、個々の所属も証明する。
30 万アドレスの一覧をコントラクトへすべて書くと、費用が大きすぎます。
各データを要約して葉にし、隣の二つを結合して要約し、層を重ねて一つにします。チェーンには一値だけを置きます。
利用者は自分のデータと、葉から根までの兄弟値を渡します。30 万なら約 19 個です。経路を計算して根が一致すれば認めます。
全データを渡さず、一つが集合に属すると証明します。
ブロックの取引、エアドロップの一覧、ブリッジのメッセージでも同じ構造です。
何と呼ぶか
任意長を固定長へ変える一方向の関数です。同じ入力なら同じ結果、ビットが変われば大きく変わり、逆算できません。
Ethereum は Keccak-256、Bitcoin は SHA-256 です。同じ関数ではなく、混同するとアドレスも一致しません。
作り上げるより、無作為に選ぶ数字です。乱数の質が安全を決めます。
本人証明と資産支配を兼ね、従来のパスワードのように再設定できず、紛失は永続します。
秘密鍵から一方向に導いた値で、公開できます。さらに要約して切り出すとアドレスです。
公開鍵やアドレスの公開は安全です。危険なのは秘密鍵と、それを導ける署名時の乱数などです。
Ethereum と Bitcoin で使い、曲線は secp256k1 です。Solana の Ed25519 は別方式で、長さと形式も異なります。
一回限りの乱数を使います。一度でも再使用すると、秘密鍵を解けます。実例で事故を扱います。
一つのメッセージの証明です。Ethereum では r と s が各 32 バイト、回復識別子 v が 1 バイトです。
内容が変わらず、署名時に鍵を持っていたことを証明します。意味の理解は証明できません。
集合を二つずつ要約して根へまとめます。データと葉から根への経路だけで、集合全体なしに所属を検証できます。
経路長は対数で、百万件でも約二十ハッシュです。この対数性が価値の理由です。
実践
チェーン、テストネット、実資産は使いません。生成鍵は実験専用で、終了後に削除します。
secp256k1 の秘密鍵と公開鍵を生成します。
openssl ecparam -name secp256k1 -genkey -noout -out priv.pem
openssl ec -in priv.pem -pubout -out pub.pem
cat priv.pem pub.pempriv.pem から pub.pem を再生成できますが、逆はできません。pub.pem を削除して再生成し、一致を確認します。
メッセージを用意し、署名します。
printf 'transfer 100 to alice' > msg.txt
openssl dgst -sha256 -sign priv.pem -out sig.bin msg.txt
xxd sig.bin | headsig.bin の長さを見ます。内容ではなく要約へ署名するため、メッセージ長と無関係です。
公開鍵で検証します。
openssl dgst -sha256 -verify pub.pem -signature sig.bin msg.txtVerified OK が出ます。pub.pem だけを使い、秘密鍵は参加しません。
一バイトを変えて再検証します。
printf 'transfer 100 to blice' > tampered.txt
openssl dgst -sha256 -verify pub.pem -signature sig.bin tampered.txt一文字でも要約が変わり、失敗します。
署名ファイルの一バイトを変えても失敗します。おおむね正しい状態はなく、通るか通らないかだけです。
実際のチェーンの署名を見ます。
ウォレットのメッセージ署名で、自分の短い文章を署名してコピーします。
0x と 130 文字で 65 バイトです。最初の 64 文字が r、次の 64 が s、最後の 2 が v です。
openssl と曲線は同じでも、Ethereum は Keccak-256、固定 65 バイト、要約前の接頭辞を使います。DER ではありません。数学は同じでも、符号化と前処理が違います。言語間の実装で注意します。
署名内容を読みます。
実際の DApp のログイン署名で、ドメイン、乱数、期限、チェーン番号を全文から確認します。
欠けた軸では再使用できます。
AI Lab
二段階で、第二が中心です。
第一段階:
指定した言語で Merkle 証明の検証関数を書いてください。
入力は leaf、proof 配列、root、出力は true または false です。
ハッシュ、結合順、葉と内部ノードの区別の仮定を説明してください。
第二段階:
今度は攻撃者になり、この実装で、
木にないデータに true を返す入力を作ってください。
不可能なら、防ぐコードの行を示してください。第二原像攻撃という典型的な欠陥があります。葉と内部ノードを同じ方法で要約すると、内部ノードを葉として短い経路と渡せます。異なる接頭辞や長さで防ぎます。
モデルは欠陥のある版を書き、攻撃は不可能と言うかもしれません。その場合、説明を渡して書き直させます。
訓練するのは実装より、何を問うべきかを知ることです。
例の契約アドレスや根を勝手に作っていたら、チェーンで確認してください。存在しないことが多いものです。
AI の回答後、自分で必ず検証すること
- 対象チェーンのハッシュを使い、SHA-256 と Keccak-256 を混同していない
- 二子の結合順が明記されている
- 葉と内部ノードを区別し、内部ノードを葉として渡せない
- ライブラリーと関数が公式文書に実在する
- 十六進数とバイト配列を正しく変換し、0x を数えすぎない
- 検証成功をデータの正当性と取り違えず、根の信頼性を区別する
実例
同じ鍵と乱数で異なる二メッセージを署名すると、署名から初歩的な代数で鍵を解けます。
2010 年、ゲーム機のコード署名が乱数を定数にし、鍵が計算されて体系が失効しました。2013 年、モバイル OS の乱数の欠陥でウォレットが同じ乱数を使い、資金を失いました。
破られたのはアルゴリズムではなく実装です。乱数は自分で書くべきでない部分です。
偽サイトの理解できない十六進数へ、利用者が署名しました。
実際は NFT をゼロ価格で売る注文でした。合法な署名で、同意はしたが、何に同意したかを知らなかったのです。
2022〜2023 年に大きな損失を生みました。構造化して各項目を表示し、ドメインとチェーンを結び付けます。T13 で扱います。
初期の形式にはチェーン番号がなく、テストネットの取引をメインネットでそのまま使えました。
取引は後の規格で防ぎましたが、オフラインのメッセージはアプリの責務です。番号や期限のないログインでは、一年前の署名も使えます。
全一覧は高価で、毎回の運営の署名なら常時オンラインが必要です。
根だけをチェーンへ置き、公開一覧から利用者が経路を持参すると、運営が停止しても受領できます。
アプリで最も一般的な Merkle の用途で、自分でも実装する機会があります。
変数を一つ変える
異なるデータが同じ要約なら、証明が二つを指し、コードの照合も置換できます。
一アプリより、要約をデータの代表にする体系が崩れます。チェーンが新しい関数へ慎重な理由です。
ブレインウォレットです。攻撃者は詩、歌詞、辞書を試せばよく、算法を破る必要がありません。
空にされる平均時間は秒単位です。安全は乱数のエントロピーにあります。
永続的な通行証となり、ログ、エラー報告、通信記録が漏えい源になります。
チェーン上の承認を全部取り消しても、オフラインの署名には効きません。チェーンへ載っていないためです。
曲線と算法が異なり、アドレスは公開鍵自体なので、直接取得できます。
署名者を明記する Solana と、署名から導く Ethereum の違いを、T6 のアカウントモデルで扱います。
持ち帰る問い
署名検証自体にブロックチェーンは不要です。SSH、HTTPS にもあります。チェーンで必要なのは、全員が同じ変更不能な規則で検証することで、会社のサーバーでなくプロトコルへ記します。
鍵の紛失は保有者がすべて負担し、救済はありません。マルチシグ、社会的回復、権限の階層化は、そのリスクを再分配します。
Agent へ秘密鍵を渡すと、全権限です。額、相手、期限のある署名承認で限定します。権限を先に考え、署名方法を後に決めます。逆なら一つの誘導で資金を失えます。
章末チェック
署名は特定のメッセージだけの結果で、鍵へ逆算できません。
鍵は任意のメッセージへ証明を作る能力そのもので、証明の工場です。
ありません。署名と内容から復元し、アドレスへ変えて照合します。
送信アドレスを取引に含める必要もなく、初めての相手を検証できます。
署名は永続し、可否は署名する内容で完全に決まります。
一回限りの乱数を使用後に無効にし、ドメイン、チェーン番号、期限を含めます。欠けた軸では再使用できます。
全データなしに、一つが集合に属すると証明します。
線形から対数へ減り、百万件でも約二十ハッシュです。チェーンには約束の値、外には全データという設計の基礎です。
根の信頼性は保証しません。誤った根なら、証明も誤った一覧を指します。
正解は一つではありません。次を確認します。
- サーバーの一回限りの乱数と、検証後の無効化。
- 他サイトへの再使用を防ぐドメイン。
- チェーン番号と期限。
- 十六進数だけでなく、ウォレットが表示できる構造化内容。
- 大文字小文字によらないアドレス比較と、チェックサムの扱い。
- ログイン署名と資産承認の区別。前者に送金の誤解を与えず、後者をログインへ隠さない。
第一、六項目まで書ければ、中心を理解しています。
要点
秘密鍵は決して送らず、誰でも検証できる証明だけを送る。