前章で、こういうコマンドを打ちました。

cast send 0x5FbD... "setNumber(uint256)" 42 --private-key 0xac0974...

秘密鍵を渡したら書き込めた。なぜこれで「本人が指示した」ことになるのか。 ログインもしていないし、パスワードも入れていないのに。

この章はそこだけを扱います。まるばつは出てきません。しかし、ここが分かっていないと以降の章の判断がすべて宙に浮くので、いったん立ち止まります。

Web アプリとの違い

Web アプリで「誰が操作したか」を決める流れはこうです。

ID とパスワードを送る → サーバが照合する → セッションを発行する → 以降そのセッションで本人扱い

中央に「照合する人」がいます。 サーバがユーザー表を持っていて、正しいかどうかを判定する。

ブロックチェーンにはそれがありません。ユーザー登録もなければ、パスワードを保管しているサーバもない。にもかかわらず「このアドレスの持ち主が指示した」と判定できます。それを可能にしているのがデジタル署名です。

鍵ペア — 数から住所が作られる

まず秘密鍵とは何か。ただの 256 ビットの乱数です。

0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

これだけ。データベースに登録されているわけでも、誰かが発行したわけでもありません。サイコロを 256 回振れば作れます。

この数から、計算だけでアドレスが求まります。

$ cast wallet address --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

道筋はこうです。

秘密鍵 (256 bit の乱数)
   ↓ 楕円曲線上の掛け算
公開鍵
   ↓ keccak256 して下位 20 バイトを取る
アドレス 0xf39F2266

秘密鍵 32 バイトから公開鍵 64 バイト、ハッシュ 32 バイトを経てアドレス 20 バイトへ至る一方向の流れ。逆向きの矢印には離散対数問題として×印が付いている

アドレスが 20 バイトなのは、ハッシュの下位 20 バイトだけを切り出しているからです。上位 12 バイトは捨てます。

公開鍵 (64 バイト)
  x  4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
  y  385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1
        ↓ keccak256
0x969b0a11b8a56bacf1ac18f2 19e7e376e7c213b7e7e7e46cc70a5dd086daff2a
  └─ 捨てる 12 バイト ─┘└──── アドレス 20 バイト ────┘

x と y の両方を使います。 片方だけでは別物になります(実測: x だけなら 0xb7aa7613…、y だけなら 0xda6cc296…)。公開鍵は曲線上の 1 点の座標なので、2 つで 1 組です。

捨てているので、アドレスから公開鍵には戻れません。情報が足りない。それでも困らないのは、公開鍵が署名から復元できるからです。

大文字小文字が混ざっているのは何か

アドレスは 2 通りの書き方で出てきます。

0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a   全部小文字
0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A   混在

同じアドレスです。 残高を問い合わせても同じ値が返ります。混在のほうは EIP-55 のチェックサムで、大文字の位置に打ち間違い検出が仕込まれています。

作り方は単純です。小文字のアドレス文字列を keccak256 し、i 文字目が英字で、ハッシュの i 桁目が 8 以上なら大文字にする。それだけ。

19e7e376e7c213b7e7e7e46cc70a5dd086daff2a   ← 小文字のアドレス
e4fb8f94fb83c8b88a104d252a48f686509b720b…  ← それを keccak256
0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A ← 規則を当てはめた結果

なぜこんなものがあるのか。アドレスを 1 文字打ち間違えても送金は成功してしまうからです。存在しないアドレスにも送れる、という性質の裏返しで、取り戻せません。そこで英字 1 文字あたり 1 ビットの冗長性を、既存の表記を壊さずに追加しました。ウォレットは規則に合わない混在を見たら、打ち間違いとして拒否できます。

なぜ SHA-256 ではなく keccak256 なのか

ここまで当たり前のように keccak256 を使ってきましたが、なぜこれなのか。

まず、紛らわしい事実から。

入力: 空文字列
keccak256  0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
SHA3-256   0xa7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
SHA-256    0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

keccak256 と SHA3-256 は別物です。 違いは詰め物(padding)の 1 バイトだけ(0x010x06)ですが、出力は完全に変わります。

経緯はこうです。2012 年に SHA-3 コンペで Keccak が優勝し、Ethereum はそれを実装しました。ところが 2015 年 8 月に NIST が padding を変更して FIPS 202 = SHA-3 として標準化。Ethereum は同年 7 月にローンチ済みで、変えられませんでした。つまり Ethereum は「標準化される前の SHA-3」を使い続けています。

実害があります。Python の hashlib.sha3_256 は keccak256 ではありません。 名前が似ているので取り違えが後を絶ちません。

SHA-256 でも技術的には問題ありません(Bitcoin はそちらです)。選ばなかった理由は、Keccak がスポンジ構造で長さ拡張攻撃を原理的に受けないこと、そして当時 SHA-3 が「次の標準」だったこと。EVM の中では keccak256 のほうが安いというのも、選んだ結果として後から付いてきた性質です(実測: 128 バイトの入力で keccak256 が 1,006 ガス、sha256 が 1,466 ガス。前者はネイティブ命令、後者はプリコンパイル呼び出しだからです)。

重要なのは、この矢印が一方通行だということです。 秘密鍵からアドレスは一瞬で求まりますが、アドレスから秘密鍵を逆算することは(現実的な時間では)できません。

だからアドレスは公開してよく、秘密鍵は絶対に見せてはいけない。この非対称性が全部の土台です。

そして誰も「登録」していないので、アカウント作成という手続きが存在しません。鍵を作った瞬間から、そのアドレスは全チェーンに存在します。残高 0 で、誰にも知られていないだけです。

署名の 3 つの性質

秘密鍵とメッセージがあれば、署名という 65 バイトのデータが作れます。

$ cast wallet sign --private-key 0xac0974... "send 1 ETH to alice"
0x12f6cf78a64e0de0eae2760a...

この署名には 3 つの性質があります。順に確かめます。

性質 1: メッセージが変われば署名が変わる

$ cast wallet sign --private-key 0xac09... "send 1 ETH to alice"
0x12f6cf78a64e0de0eae2760a…

$ cast wallet sign --private-key 0xac09... "send 2 ETH to alice"
0x867349e9f3006b48acba8d82…

1 文字変えただけで、まったく別の署名になりました。 つまり署名は「このメッセージに対して」作られたものであって、他のメッセージには流用できません。

性質 2: 署名から署名者が復元できる。鍵は要らない

ここが署名のいちばん不思議なところです。

$ cast wallet verify --address 0x3DC1e16a…1125 "I control this address" 0x9e8cb9ae…
Validation succeeded. Address 0x3DC1e16ac0cC3e3fc0C96f2A1E6a3b3551fE1125 signed this message.

検証する側は秘密鍵を持っていません。メッセージと署名だけを見て、「これは 0x3DC1e16a…1125 の鍵で作られたものだ」と言い当てています。

Ethereum ではこれを ecrecover と呼びます。署名(r, s, v の 3 つの数)とメッセージから、署名者のアドレスを計算で取り出す操作です。照合するのではなく、復元します。 だからユーザー表が要りません。

性質 3: 他人の鍵では作れない

$ cast wallet verify --address 0x3Dc1fd52…24DC "I control this address" 0x9e8cb9ae…
Error: Validation failed. Address 0x3Dc1fd52…24DC did not sign this message.

別のアドレスとして検証すると失敗します。その署名を作れるのは、その秘密鍵を持っている者だけです。

この 3 つで認証が置き換わる

まとめると、こうなります。

Web アプリブロックチェーン
本人確認の材料パスワード署名
判定する主体サーバ誰でも(計算するだけ)
必要な事前登録ユーザー登録なし
秘密の送信パスワードをサーバに送る秘密鍵は絶対に送らない

秘密鍵そのものは一度もネットワークに出ません。 出るのは署名だけで、そこから鍵は逆算できない。だから「本人であること」を、秘密を渡さずに示せます。

署名する側と検証する側を左右に並べた図。両者が文書から同じハッシュを別々に計算し、左は秘密鍵で s を作り、右は署名から公開鍵を復元する

図の要点は、中央のハッシュを両者が別々に作っていることです。渡す必要がありません。そして秘密鍵が現れるのは左側の 1 箇所だけで、右側には一度も出てきません。

そしてノードは、受け取ったトランザクションの署名から送信者アドレスを復元し、それを msg.sender としてコントラクトに渡します。認証コードを 1 行も書かなくていいのは、これが実行環境の仕事だからです。

msg.sendertx.origin の違い

正確には、msg.senderその呼び出しの直接の呼び出し元です。人が鍵で直接コントラクトを呼んだときは、署名から復元されたアドレスと一致します。しかしコントラクトがコントラクトを呼んだときは、呼び出し元コントラクトのアドレスになります。署名した人ではありません。

「署名した人」を指すのは tx.origin のほうです。ただし tx.origin を認証に使ってはいけません。 利用者を騙して悪意あるコントラクトを呼ばせると、tx.origin は利用者のままなので「本人が呼んだ」と誤判定します。認証には常に msg.sender を使います。

トランザクションは「何に」署名しているのか

メッセージへの署名は分かりました。では送金のとき、何に署名しているのか。

署名済みトランザクションを分解してみます。

$ cast mktx --private-key 0xac09... 0x3Dc1fd52…24DC --value 0.001ether
0x02f86f827a6980018477359401825208943dc1fd528cfef3280f7cd056…   ← 230 文字

$ cast decode-tx 0x02f86f...
{
  "signer":       "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",   ← 署名から復元された
  "chainId":      "0x7a69",                                        ← 31337
  "nonce":        "0x0",
  "to":           "0x3dc1fd528cfef3280f7cd056b47efebea45b24dc",
  "value":        "0x38d7ea4c68000",                               ← 0.001 ETH
  "gas":          "0x5208",
  "maxFeePerGas": "0x77359401",
  "input":        "0x",
  "r": "0x27964cd9ed3d566850c52c63521b71ceb32704f827a999b31d027e3d5c6d2c69",
  "s": "0x3591d06b797629f598b36feb1d4b73f13afe47b15f5e0dba593bde1753088c6c",
  "yParity": "0x0"
}

rsyParity の 3 つが署名です。signerhash はこの JSON を作るときに計算された値で、チェーンには入っていません。そして残りのフィールド(chainId, nonce, to, value, gas, 各種手数料, accessList, input)がすべて署名の対象になっています。だから、

  • 宛先を書き換えられません。 to を変えれば署名が合わなくなる
  • 金額を書き換えられません。 同上
  • data を書き換えられません。 どの関数をどの引数で呼ぶかも固定されている
  • nonce があるので同じ取引を 2 回実行できません。 使用済みの番号は拒否される
  • chainId があるので他のチェーンに流用できません。 これが第 6 章で出てくるリプレイ防止です(この取引は 0x02 型なので chainId は EIP-1559 の正規フィールドです。それを持たなかった旧来の形式に chainId を後付けしたのが EIP-155 でした)

signer の行に注目してください。 このフィールドはトランザクションの中に書かれていません。署名から復元された値です。「差出人を名乗る欄」が存在しないので、差出人を詐称する余地がそもそもありません。

118 バイトの帯グラフ。封筒と手数料 20 バイト、宛先 21 バイト、金額ほか 13 バイト、署名 64 バイトで、署名が全体の 54% を占める

1 文字書き換えると、署名者が入れ替わる

署名の効き方を確かめる一番よい方法は、壊してみることです。宛先を 1 文字だけ変えて、署名はそのまま付けてみます。

原本と改竄後を上下に並べた図。文書が変わるとハッシュが変わり、同じ署名から復元されるアドレスが山田太郎から見知らぬアドレスへ入れ替わる

元:   …943dc1fd528cfef3280f7cd056b47efebea45b24dc…
改竄: …94edc1fd528cfef3280f7cd056b47efebea45b24dc…
                ↑ 3 を e に変えただけ
復元される署名者
元の取引0x3dc1e16ac0cc3e3fc0c96f2a1e6a3b3551fe1125
改竄後0x2aacbd016a437a724c610fdd3ca1a34dd8f8d020

「署名が不正です」というエラーは出ません。 復元の計算は必ず成功し、まったく別のアドレスが答えとして出てきます。

そのアドレスは何者か。残高 0、nonce 0。ハッシュ空間からたまたま出てきた 20 バイトで、対応する秘密鍵を知っている人間は存在しません。 実際に送ろうとすると、こう弾かれます。

insufficient funds for gas * price + value: balance 0, tx cost 50041969931066000

改竄は成功しますが、成功した結果として無意味になります。

同じ取引をそのまま再送しようとしても止まります。

nonce too low: next nonce 4, tx nonce 3

つまり守りは 3 段構えです。同じ取引は再実行できない(nonce)、中身を変えると署名者が別人になる(ハッシュ)、その別人は存在しない(秘密鍵がない)。

この 118 バイトはどこから取ってきたのか

ここまで「チェーンに載っているバイト列」を見てきましたが、それを手元に持ってくる方法も知っておく価値があります。ここは実際にやってみると、いくつか予想外のことが起きます。

ブロックエクスプローラでは見られなかった

まず Etherscan を試しました。トランザクションのページには From / To / Value / Gas / Input Data が並んでいますが、署名(r, s)はどこにも表示されていません

理由は分かります。Etherscan が From を表示している時点で、署名の検証はもう終わっているからです。復元済みの答えだけ見せて、材料は畳んである。人間にとって rs は 64 バイトの乱数にしか見えないので、載せる意味が薄い。

Etherscan には /getRawTx?tx=<hash> という生データ用のページがあります。しかし Sepolia では無効化されていました

<pre class="text-break-all …">This feature has not been enabled.</pre>

ページ自体は 200 で返ってくるので、URL を叩いただけでは気づけません。中身を見て初めて分かります。

エクスプローラは「加工済みの眺め」を提供する道具であって、生データの取得手段としては当てにできない、と考えておくのが安全です。

RPC から取る

確実なのは、ノードに直接聞くことです。

cast tx 0x6c4a8add… --raw --rpc-url https://ethereum-sepolia-rpc.publicnode.com
0x02f87383aa36a703830f424084771fbad2825208943dc1fd528cfef3280f7cd056b4…

casteth_getRawTransactionByHash を呼んでいるだけなので、curl でも同じです。

curl -s -X POST https://ethereum-sepolia-rpc.publicnode.com \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getRawTransactionByHash","params":["0x6c4a8add…"]}'

ただし、どのノードでも取れるとは限らない

公開エンドポイントを 3 つ試したところ、成功したのは 1 つだけでした。

エンドポイント結果
ethereum-sepolia-rpc.publicnode.com取得できた
sepolia.drpc.orgchain is not available on free plan, please upgrade to paid
rpc.sepolia.orgJSON ですらない応答が返ってきた(実質停止)

公開 RPC は「あるはずのものが無い」ことが普通にあります。 有料プラン限定になっていたり、止まっていたり、特定のメソッドだけ塞がれていたり。テストネットの公開エンドポイントは特に不安定です。

だから第 6 章で「RPC URL は差し替え自由」と書きましたが、それは差し替えられる必要があるという意味でもあります。1 つに依存しない構成にしておいてください。

ノードを信じる必要はない

ここが一番大事なところです。「そのノードが嘘をついていたらどうするのか」。

確かめられます。 トランザクションのハッシュは、その生バイト列を keccak256 したものだからです。

$ cast keccak 0x02f87383aa36a703830f424084771fbad2825208943dc1fd528cfef3280f7cd056b4…
0x6c4a8add3eb9d97e9c7cf6df6cfb16496612bb530832cba18a18e023bb4ec076

$ # 最初に指定したトランザクションハッシュ
0x6c4a8add3eb9d97e9c7cf6df6cfb16496612bb530832cba18a18e023bb4ec076

一致しました。 受け取ったバイト列を自分でハッシュすると、要求したハッシュそのものになる。つまりノードは別のバイト列を返しようがありません。1 ビットでも変えればハッシュが変わり、即座にばれます。

これが「trust, but verify」ではなく「verify, so you don't need to trust」ということです。相手が信頼できるかを考える代わりに、答えが正しいかを自分で計算して確かめる

さらに、そのトランザクションハッシュはブロックヘッダの transactionsRoot(ブロック内の全取引をまとめた Merkle 木の根)に含まれ、ブロックのハッシュは次のブロックの parentHash に含まれています。第 16 章で見る鎖です。根拠を辿っていけば、最後は自分の手元の計算に着地します。

検証できるものと、できないもの

とはいえ万能ではありません。整理しておきます。

問い自分で検証できるか
このバイト列は本当にこのハッシュの取引かできる(keccak256 するだけ)
誰が署名したかできる(署名から復元する)
このコントラクトのコードは何かできるeth_getCode の結果を自分で解析)
このノードが取引を隠していないかできない(無いものは無いと言われたら確かめようがない)
これが最新の状態かできない(古い状態を返されても分からない)

最後の 2 つが、軽量なクライアントの本質的な弱点です。「返ってきたものが正しいか」は検証できても、「返ってこなかったものが存在しないか」は検証できません。 自分でノードを動かすか、複数のノードに聞いて突き合わせるしかありません。

ハンコとどう違うか

物理世界の対応物はハンコです。対応関係はきれいに付きます。

物理世界暗号世界
ハンコ(実印そのもの)秘密鍵
押された印影署名
印鑑証明書公開鍵(ただし不要。後述)
印鑑登録番号アドレス

共通点も本質的です。持っている人だけが押せる。なくしたら終わり。盗まれたら本人と区別がつかない。

ただし 4 点で、ハンコより遥かに強い。

ハンコ電子署名
押すたびの印影毎回同じ文書ごとに全部違う
印影を見て真似できるかできるできない。何万件集めても新しい署名は作れない
押した後に文書を書き換えたら印影は無傷壊れる。別人が押したことになる
検証に必要なもの印鑑証明(登録簿と照合)不要。署名だけで自己完結

3 番目が決定的です。契約書に押印したあとで金額を書き換えても、印影は何ともありません。だから物理世界では割印や契印という別の工夫が要ります。電子署名は文書の中身そのものから印影を作るので、改竄検知が仕組みに内蔵されています。

4 番目は、電子署名一般の性質ではない

ここは注意が必要です。「登録簿が要らない」のは Ethereum 固有の設計であって、電子署名なら何でもそうなるわけではありません。

上段に実印・TLS・マイナンバーカードの流れ、下段に Ethereum の流れを並べた図。上段では公開鍵を別途もらい第三者の証明書で保証するが、下段では署名から公開鍵が計算で出てアドレスになる

仕組み本人性の担保登録簿・証明書
実印市区町村の印鑑登録要る(印鑑証明書)
TLS(https)認証局の署名要る(サーバ証明書)
マイナンバーカードの署名地方公共団体情報システム機構要る(電子証明書)
Ethereum要らない

TLS もマイナンバーカードも電子署名ですが、検証には相手の公開鍵が必要で、その公開鍵が本人のものだと誰かが保証しなければなりません。それが証明書であり、認証局であり、PKI です。構造としては印鑑証明と同じです。

Ethereum が例外なのは、問いの立て方を変えたからです。

従来      この公開鍵は山田太郎のものか?      → 誰かが保証する必要がある
Ethereum  この署名から出てくるアドレスは何か? → 計算するだけ

公開鍵は検証の入力ではなく、出力です。 事前に受け取っておく必要がないので、照合する相手がいません。

代償もあります。アドレスは「誰か」を教えてくれません。 「この署名は 0x3DC1e16a…1125 のものだ」までは分かっても、それが誰かはチェーンの外で結びつけるしかない。登録簿を捨てた分、現実世界の身元とは切り離されました。

鍵をどこに置くか — 4 つの形態と、その帰結

ここからが実務です。「署名できる = 全資産を動かせる」なので、鍵の置き場所がそのままセキュリティモデルになります

置き場所署名するのに必要なもの無人で署名できるか
コマンドラインに直書きコマンドを打つことできる
暗号化キーストアファイル + パスワードできる
MetaMask などのウォレット人間がボタンを押すことできない
ハードウェアウォレットデバイスの物理ボタンできない

1. --private-key に直書き

cast send ... --private-key 0xabc123...

シェル履歴(~/.zsh_history)に残ります。 プロセス一覧にも出ます。開発用の公開鍵ならともかく、実際の鍵でこれをやってはいけません。

2. 暗号化キーストア

cast wallet new ~/.foundry/keystores deployer
# → パスワードを 2 回聞かれ、アドレスだけが表示される

秘密鍵は AES で暗号化されたファイルになり、平文ではどこにも存在しません。復号にはパスワードが要ります。

forge script ... --account deployer --password-file <パスワードのファイル>

パスワードさえあれば無人で署名できます。 自動化には便利で、危険でもあります。

なお、パスワードを忘れると鍵は永久に失われます。 リセットも復旧もありません。その鍵の資金も、デプロイしたコントラクトの所有権も一緒に消えます。

3. MetaMask などのウォレット

秘密鍵は拡張機能の内部にあり、Web ページからもスクリプトからも取り出せません。そして署名のたびに人間が承認ボタンを押す必要があります。

「承認」画面がやっているのは、これから署名する内容を人間に見せることです。宛先、金額、関数名、ガス代。押した瞬間に署名が作られ、ブロードキャストされます。押さなければ署名は存在しません。

4. ハードウェアウォレット

鍵が専用デバイスから出ません。署名はデバイス内部で行われ、承認は物理ボタンです。PC がマルウェアに感染していても、鍵そのものは抜けません。

実例: この本を書きながら起きたこと

抽象論だと入りにくいので、実際にあったことを書きます。

本書のコントラクトを Sepolia にデプロイするとき、デプロイ専用の鍵を新しく作りました。1Password にパスワードを生成させ、それでキーストアを暗号化しています。

デプロイ用: 0x3DC1e16a…1125   キーストア + 1Password のパスワード

一方、遊ぶために使っている MetaMask のアカウントは別です。

MetaMask:  0x3Dc1fd52…24DC   鍵は MetaMask の中

デプロイ用のアドレスから MetaMask のアドレスへ 0.05 ETH を送る場面がありました。この送金は、承認ボタンを押さずに実行できました。 キーストアのパスワードが 1Password から読めるので、コマンド 1 本で署名できるからです。

逆に、MetaMask 側から送ることはできませんでした。 鍵が MetaMask の中にあり、人間が承認しない限り署名が作られないからです。

この非対称が、鍵の置き場所を選ぶということの実質です。自動化できる = 承認なしに動かせる。便利さと危うさが同じ性質から出ています。

だから怖いこと

署名の仕組みが分かると、怖さの正体も分かります。

鍵が漏れたら終わりです。 取り消しも、凍結も、サポート窓口もありません。攻撃者はあなたと区別のつかない有効な署名を作れるので、チェーンから見れば正当な取引です。

具体的に守るべきことは 3 つです。

  1. シードフレーズや秘密鍵を入力させようとするものは、100% 詐欺です。 faucet も、サポートも、エアドロップも、鍵を聞く正当な理由はありません
  2. 公開されている鍵を本物のチェーンで使わない。 anvil の開発用アドレスは常時監視されていて、着金した瞬間にボットが抜きます
  3. 用途ごとに鍵を分ける。 デプロイ用、遊ぶ用、資産保管用。1 つ漏れても被害がそこで止まります

署名 ≠ トランザクション

最後に、混同しやすい点を一つ。

ガス代のかからない署名があります。 メッセージへの署名(EIP-191)や構造化データへの署名(EIP-712)は、チェーンに何も送りません。ログインの代わり(Sign-In with Ethereum)や、後で誰かが提出する許可証として使われます。

問題は、それが承認そのものになり得ることです。ERC-20 の permit に署名すると、「このアドレスに私のトークンを引き出す権限を与える」という意味になります。ガス代はかからないのに、署名した時点で権限を渡しています。

「ガス代が出ないから安全」は成り立ちません。 ウォレットが署名を求めてきたら、それがトランザクションでなくても、内容を読んでから押してください。

まとめ

  • 秘密鍵はただの 256 ビットの乱数。そこからアドレスが一方向に計算される
  • 署名は「そのメッセージに対して、その鍵で作られた」ことを示す 65 バイト
  • 検証する側は鍵を持たずに署名者を復元できる(ecrecover)
  • だから照合するサーバが要らない。認証層が丸ごと消える
  • トランザクションは宛先・金額・data・nonce・chainId ごと署名されるので、改竄も再利用もできない
  • 鍵の置き場所がセキュリティモデル。無人で署名できるかどうかがそこで決まる
  • 署名できる = 全資産を動かせる。取り消しはない