ハードウェアウォレットのファームウェア検証:ドメインではなく署名を見る
実測:Trezor 2.9.1(1,614,848バイト)を取得し、公式フィンガープリントを再計算。ファイルのSHA-256と公開値は一致しないが、両方正しい。2-of-3署名の検証まで。
2026年10月29日、Trezor T2T1のファームウェア2.9.1をdata.trezor.ioから取得しました。1,614,848バイト、SHA-256は6a84c200f13b564ffc2f16c2a6806272b0e7561d04d434f55b495d15c5a0c027。一方、ベンダーのreleases.jsonにある同一バージョンのフィンガープリントはaaa616c14904cf6eb940497d7fd5da8735ab0d0ecdebd3f6ced49fff0ce278a0です。1つのファイルに2つの異なる値があり、両方が正しい。ファームウェアにsha256sumを走らせ、公開値と比べて一致せず、改変されたパッケージを掴んだと思ったことがあるなら、理由はこれです。
2つのハッシュ、2つの役割
SHA-256が覆うのはディスク上のバイト列で、これは転送時の検査です。ダウンロードが途中で切れたり壊れたりしていないことを示します。フィンガープリントはまったく別物で、デバイスのブートローダがファームウェアヘッダに対して計算する値であり、ベンダー署名が署名する値でもあります。具体的には、2.9.1のファイルは3つの部分から成ります。4,608バイトのベンダーヘッダ(マジックはTRZV)、1,024バイトのファームウェアヘッダ(TRZF、オフセット4,608から)、そして1,609,216バイトのコードです。ヘッダにはバージョン2.9.1.0、fix_version 2.9.0.0、hw_model T2T1、単調カウンタ2、期限フィールド0、32バイトのコードハッシュ16枠(13枠が充填:128 KiBの完全なチャンク12個+最後の36,352バイト)、この世代では空のECDSA署名3枠、1バイトのsigmask、64バイトの署名が入っています。
5ステップで再現する
1)バイト列TRZFを探す。ここではオフセット4,608。2)続く12バイトからheader_len(1,024)とcode_length(1,609,216)を読む。3)コードを128 KiBのチャンクに切るが、境界はコードの先頭ではなくファイルの先頭に揃える。したがってチャンク0は131,072 − 5,632 = 125,440バイトになり、素直にコード先頭から切ると13個のハッシュはどれも一致しません。4)ヘッダを組み直す。13個のハッシュをオフセット32からの512バイト領域に入れ、544〜739(空のECDSA領域)と最後の65バイト(sigmaskと署名)をゼロにする。5)1,024バイトのヘッダをblake2sにかける。得られるのはaaa616c14904cf6eb940497d7fd5da8735ab0d0ecdebd3f6ced49fff0ce278a0で、releases.jsonの文字列と一致します。同じ手順を2.8.9、2.8.10、2.9.0にも適用すると各リリースの公開値に当たる。方法が偶然ではなく正しいと確信できたのはそのためです。
署名が錠前で、しかも2-of-3
ヘッダのsigmaskバイトは3で、0x20、0x40、0x60にある3本のベンダー公開鍵のうち0番と1番を選んでいます。TrezorはCoSi——各署名者の公開鍵をEdwards点として加算するしきい値方式——を使うため、64バイトの署名は合成鍵52ef8f8496c888a45aa526cd980105f1dd6ce7b435f0be8fde86152c244a7805の下で通常のEd25519署名として検証できます。曲線演算を実装し、まずRFC 8032のテストベクタで正しさを確認してからダイジェストに適用した結果はtrue。鍵0だけ、鍵1だけで同じ検証を行うとfalseになります。2-of-3であって「3本のうちどれか1本」ではありません。4,608バイトのベンダーヘッダも同様に署名され、ツールに組み込まれたブートローダ鍵3本のうち2本で検証に通り、ダイジェストはabb773af7aa4c475…です。連鎖はベンダーヘッダ→ファームウェアで、基点はサーバではなくデバイス上の鍵にあります。
1バイト変えて、失敗するのを見る
この検証自体を確かめるため、オフセット100,000の1ビットを反転し、ダウンロード元を掌握した攻撃者と同じように13個のチャンクハッシュを再計算し、ヘッダを組み直して検証器を再実行しました。ダイジェストはaaa616…から249da0562e5ce9c15f3537cec046894c46c488cd6017850c79ed66053a4e2218に変わり、署名検証はfalseを返します。これが設計の要点です。ハッシュの再計算は無料、署名の偽造は不可能。ダウングレードは別の仕組みが担当します。ヘッダの単調カウンタは2で、より低い値のイメージは拒否されます。これが「既知の脆弱性がある版へ戻す」攻撃を止めます。もう一点、フィールド表が実在することの証拠として、期限フィールドは0で、ベンダー自身のパース code には「expiry is not used now」というコメントがあります。
「公式チャネル」が実際に買うもの
人を落ち着かなくさせるのはここです。ベンダーの鍵はダウンロードに含まれていません。96バイトのベンダー公開鍵は2.8.9、2.8.10、2.9.0、2.9.1でバイト単位で同一(そのSHA-256はb56e59ef7eaf8217…)。ベンダーヘッダはリリースごとに配られるのではなく、製造時にデバイスへ固定されるからです。したがって悪意あるミラーが提供できるのは、本物の署名済みイメージ、古い署名済みイメージ、壊れたパッケージのいずれかで、どれも資金は盗めません。ブートローダが正当な署名を受け入れるか拒否するかのどちらかだからです。敵対ページが本当に盗めるのはシードです。「ファームウェア更新ツール」を装って24語を求める手口が現実の攻撃で、暗号学的な接点がまったくありません。ダウンロードページが効くのはフィッシング衛生のためで、信頼のためではありません。現実のリスクは3つ。シードを釣る更新ウィザード、ウォレットとdAppの背後にあるデスクトップ/npmサプライチェーン(2023年12月のLedger Connect Kit、2024年12月の@solana/web3.js)、そして他人のベンダーヘッダを持つ模造品や整備品です。もう一つ、2.9.1にはbitcoin-onlyビルドがあり、フィンガープリントは77b53ba1e381fcf8…。指紋の照合は誤った変体を書き込む事故も防ぎます。
Ledgerは信頼の基点を別に置く
Ledgerのモデルはこの方式の劣化版ではなく、別のトレードオフです。鍵はセキュアエレメントに置かれ、ファームウェアはセキュアエレメントが検証し、 companion app は認証に基づく正規品チェックを行い、アプリ層はオープンソースだがエレメント内のOSは非公開です。失うのは、私が上でやったことそのものです。第三者が再現できるダイジェストも、公開鍵で確認できるしきい値署名もなく、検証はデバイスとベンダーに委任されます。得るのは、汎用マイコンに鍵素材が置かれないこと。どちらのモデルも、実際にウォレットを空にする攻撃——自分でシードをデバイス以外の何かに入力すること——は防げません。
スワップはこれに触れない——署名するまでは
スワップ注文が受け取るのは5つのフィールドだけです。fromAsset、toAsset、fromAmount、toAddress、refundAddress。ウォレット接続も署名もシードも不要。だからファームウェアのリスクは交換の瞬間ではなく、ウォレットから外へ送る瞬間にあります。規模感として、今朝のカタログは516資産・74ネットワーク・415ティッカー・ステーブルコイン61・追加IDが必要な資産6でした。10:07 UTCの見積:100 USDT-TRC20は0.14687052 XMR(1 XMRあたり680.87ドル、スポット597.08ドルより14.03%高く、ほぼ全額が注文ごとの下限)、1 XMRは578.800581 USDT(出金側はスポットより3.06%低い)、0.05 BTCは6.61007527 XMRで132.20151 XMR/BTC、スポットは80,072ドル。これらは執筆時点の値で、ライブの数字はページにのみあります。 (XMR→USDT 出口ルート)
書き込む前の6つの確認
1)ベンダー自身のドメインからのみ取得し、単語を求める「ファームウェア更新ツール」は攻撃として扱う。2)フィンガープリントを計算しreleases.jsonと比較する。`trezorctl firmware verify trezor-t2t1-2.9.1.bin --fingerprint aaa616c1…` で署名と指紋を一度に確認できます。3)しきい値を確認する。sigmask 3・鍵3本は共署名者が2であることを意味します。4)ダウングレードでないことを確認する。単調カウンタは戻ってはいけません。5)公式アプリからUSB経由で書き込み、バージョン文字列はノートPCの画面ではなくデバイス画面で確認する。6)再起動後、デバイスから再度バージョンと指紋を読む。書き込んだ内容と違う値を報告するなら、そのハードウェアを疑わしいものとして扱う。
脅威モデルは一行に圧縮できます。ファームウェア攻撃にはベンダーの秘密鍵か、あなたのシードのどちらかが必要で、どちらもブラウザの外にあります。だからドメインも鍵アイコンもダウンロードページも、頼るべき制御にはなりません。それらは途中でフィッシングに遭うかどうかを決めるだけです。上の算術で成果物を一度検証し、シードをデバイスの中に保てば、ハードウェアウォレットの最も怖い部分は謎ではなく、チェックできる項目になります。