前章で、こういうコマンドを打ちました。
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 バイトを取る
アドレス 0xf39F…2266
アドレスが 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 バイトだけ(0x01 と 0x06)ですが、出力は完全に変わります。
経緯はこうです。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 アプリ | ブロックチェーン | |
|---|---|---|
| 本人確認の材料 | パスワード | 署名 |
| 判定する主体 | サーバ | 誰でも(計算するだけ) |
| 必要な事前登録 | ユーザー登録 | なし |
| 秘密の送信 | パスワードをサーバに送る | 秘密鍵は絶対に送らない |
秘密鍵そのものは一度もネットワークに出ません。 出るのは署名だけで、そこから鍵は逆算できない。だから「本人であること」を、秘密を渡さずに示せます。
図の要点は、中央のハッシュを両者が別々に作っていることです。渡す必要がありません。そして秘密鍵が現れるのは左側の 1 箇所だけで、右側には一度も出てきません。
そしてノードは、受け取ったトランザクションの署名から送信者アドレスを復元し、それを msg.sender としてコントラクトに渡します。認証コードを 1 行も書かなくていいのは、これが実行環境の仕事だからです。
msg.senderとtx.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"
}
r・s・yParity の 3 つが署名です。signer と hash はこの JSON を作るときに計算された値で、チェーンには入っていません。そして残りのフィールド(chainId, nonce, to, value, gas, 各種手数料, accessList, input)がすべて署名の対象になっています。だから、
- 宛先を書き換えられません。
toを変えれば署名が合わなくなる - 金額を書き換えられません。 同上
dataを書き換えられません。 どの関数をどの引数で呼ぶかも固定されているnonceがあるので同じ取引を 2 回実行できません。 使用済みの番号は拒否されるchainIdがあるので他のチェーンに流用できません。 これが第 6 章で出てくるリプレイ防止です(この取引は0x02型なのでchainIdは EIP-1559 の正規フィールドです。それを持たなかった旧来の形式に chainId を後付けしたのが EIP-155 でした)
signer の行に注目してください。 このフィールドはトランザクションの中に書かれていません。署名から復元された値です。「差出人を名乗る欄」が存在しないので、差出人を詐称する余地がそもそもありません。
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 を表示している時点で、署名の検証はもう終わっているからです。復元済みの答えだけ見せて、材料は畳んである。人間にとって r と s は 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…
cast は eth_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.org | chain is not available on free plan, please upgrade to paid |
rpc.sepolia.org | JSON ですらない応答が返ってきた(実質停止) |
公開 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(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 つです。
- シードフレーズや秘密鍵を入力させようとするものは、100% 詐欺です。 faucet も、サポートも、エアドロップも、鍵を聞く正当な理由はありません
- 公開されている鍵を本物のチェーンで使わない。 anvil の開発用アドレスは常時監視されていて、着金した瞬間にボットが抜きます
- 用途ごとに鍵を分ける。 デプロイ用、遊ぶ用、資産保管用。1 つ漏れても被害がそこで止まります
署名 ≠ トランザクション
最後に、混同しやすい点を一つ。
ガス代のかからない署名があります。 メッセージへの署名(EIP-191)や構造化データへの署名(EIP-712)は、チェーンに何も送りません。ログインの代わり(Sign-In with Ethereum)や、後で誰かが提出する許可証として使われます。
問題は、それが承認そのものになり得ることです。ERC-20 の permit に署名すると、「このアドレスに私のトークンを引き出す権限を与える」という意味になります。ガス代はかからないのに、署名した時点で権限を渡しています。
「ガス代が出ないから安全」は成り立ちません。 ウォレットが署名を求めてきたら、それがトランザクションでなくても、内容を読んでから押してください。
まとめ
- 秘密鍵はただの 256 ビットの乱数。そこからアドレスが一方向に計算される
- 署名は「そのメッセージに対して、その鍵で作られた」ことを示す 65 バイト
- 検証する側は鍵を持たずに署名者を復元できる(ecrecover)
- だから照合するサーバが要らない。認証層が丸ごと消える
- トランザクションは宛先・金額・data・nonce・chainId ごと署名されるので、改竄も再利用もできない
- 鍵の置き場所がセキュリティモデル。無人で署名できるかどうかがそこで決まる
- 署名できる = 全資産を動かせる。取り消しはない