🇬🇧 English version of this article → The first Rust-native TPM 2.0 v1.85 PQC crate
要約(TL;DR)
- これは何か: TPM 2.0 v1.85 のポスト量子コマンド(ML-KEM / ML-DSA)の、世界初の Rust ネイティブ実装。crates.io に
hyde-tpm 0.2.0として公開、libtss2 も C も不要。 - これは何でないか: 「TPM で初の PQC」ではない ── それは wolfTPM が C で先着。実ハードでもない:v1.85 PQC を積んだ出荷シリコンはまだ存在しないので、ファームウェア TPM 相手(実 Infineon SLB9670 はこのコマンドを拒否する ── 最終節)。
- 証拠: コマンドのマーシャリングは C 実装とバイト単位で照合、TPM 生成の ML-DSA 署名は RustCrypto の
ml-dsaで独立検証(FIPS 204 VALID)。どちらも TPM 無しで CI 再現可。 - ステータス: 実験的
0.2.xの PoC ── 一部パラメータ暫定・レスポンスデコード手書き。at your own risk。
TCG の TPM 2.0 仕様 v1.85 で、ついにポスト量子(ML-KEM / ML-DSA)が TPM の標準コマンドに入った。……ただ、最初に正直に言っておく。「TPM で初めて PQC を動かした」のは私ではない。wolfTPM(C 言語)が先着している。PR #445、今年3月。私がやったのはそこではない ── Rust ネイティブで、libtss2 にも C にも一切依存せず、crates.io に置ける形で v1.85 PQC を動かすこと。そのレーンだけが、まだ誰も通っていなかった。そこに世界初を植えた。しかも C の実装をオラクルにして、自分の吐くバイト列が1バイトも違わないことを確かめながら。
Rust だけ空白だった理由 ── tss-esapi と non_exhaustive の壁
Rust の定番 tss-esapi は、C の libtss2 への薄いラッパーだ。その libtss2 がまだ v1.85 PQC を持っていない。だから tss-esapi 経由では、そもそも喋れない。
一方、純 Rust・no_std の tpm2-protocol(Jarkko Sakkinen)は美しい設計だが、コマンドやアルゴリズム ID を表す enum が #[non_exhaustive] で閉じている。下流から新しいコマンドを足せない ── 触るには fork が要る。つまり「Rust だけで v1.85 PQC を喋る」道は、C ラッパー経由でも純 Rust 経由でも、どちらも塞がっていた。空白の理由はここにあった。
C をオラクルにする ── バイト単位の答え合わせ
仕様を読んで、純 Rust で v1.85 のコマンドをマーシャル(バイト列に組み立て)する。だが「たぶん仕様どおり」では信用にならない。精度こそが信用だ。そこで、すでに動いている C 実装をオラクル(答え合わせの基準)にした。wolfTPM が実際にワイヤへ流すバイト列をキャプチャし、自分の Rust が吐くバイト列と突き合わせる。
// encapsulate / decapsulate / sign / verify …各コマンドで
assert_eq!(rust_marshalled, c_reference_wire); // A == B、1バイトも違わない
コマンドのマーシャリングを、独立した C 実装が流す参照ワイヤとバイト単位で照合する。この答え合わせは TPM が無くても走る(テストに焼き込んである)。CI でいつでも、ハードウェア無しで再現できる。
接ぎ木して、実際に喋らせる ── ファームウェア TPM との往復
tpm2-protocol を fork し、v1.85 の層を接ぎ木した(コマンドコード 0x1A3〜0x1AA、アルゴリズム ID は ML-KEM=0x00A0 / ML-DSA=0x00A1)。そして mssim ソケット(127.0.0.1:2321)でファームウェア TPM と往復させる。
- ML-KEM:ホスト側でカプセル化した共有秘密と、TPM が復号して返した共有秘密が一致した(encap == decap)。
- ML-DSA:TPM が署名鍵を生成し、メッセージに署名。検証鍵 1312 バイト・署名 2420 バイト(ML-DSA-44)が返る。さらに TPM 自身の Verify コマンドが「有効」チケットを返した。
これで「マーシャルできる」だけでなく、TPM が実際に受理して署名まで返すところまで取れた。公開 API はこれだけ薄い:
use hyde_tpm::pqc::{MlDsa, MlKem, PqcTpm};
let mut tpm = PqcTpm::connect("127.0.0.1:2321")?;
let secret = tpm.ml_kem_roundtrip(MlKem::K512)?; // KEM: encap == decap
let (pk, sig) = tpm.ml_dsa_sign(MlDsa::D44, message)?; // ハードウェア由来の署名
独立実装で、もう一度答え合わせ ── FIPS 204 VALID
TPM が作った署名を、TPM とは無関係の別実装(RustCrypto の ml-dsa)で検証した。結果は VALID。FIPS 204 として正しい署名だと、第三者の実装が裏書きした。
ml_dsa::verify_with_context(msg, &[], sig) // ctx=空(純 ML-DSA) => VALID
ただし、ここは正直に線を引く。これは evidence(証拠)であって attestation ではない。ホスト上で TPM 署名を検証することは、TPM を信頼の根とする attestation 検証の代わりにはならない。だから独立検証器はテストと例の中だけに閉じ込めてあり、公開 API には出していない。証拠は crate と一緒に旅する ── が、証拠は証拠として扱う。
crates.io に刻む ── hyde-tpm 0.2.0
hyde-tpm 0.2.0 として公開した。feature gate で二本立てにしてある ── default の tss(従来の tss-esapi 経路)は一切変えず、pqc feature が純 Rust の v1.85 経路。pqc 側は libtss2 も C も要らない、std だけでビルドできる。
# PQC 経路、libtss2 なしでビルド&テスト
cargo test -p hyde-tpm --no-default-features --features pqc
これで「Rust ネイティブの v1.85 PQC」は、誰でも cargo で引ける公開物になった。公開された Rust 実装としては、世界初。優先権のマーカーを打った。
そして実機は、まだ断る ── これが最前線の証明
同じ v1.85 コマンドを、実チップの Infineon SLB9670(/dev/tpmrm0)に投げた。返ってきたのは TPM_RC_COMMAND_CODE ── 「そのコマンドは知らない」。

TPM_RC_COMMAND_CODE で拒否し、古典コマンドは受理した ── フロンティアの実物。The real Infineon SLB9670 on a Raspberry Pi 4 — the very chip that rejected v1.85 PQC with
TPM_RC_COMMAND_CODE while accepting classic commands.だが同じチップに古典的な GetRandom を投げれば、ちゃんと成功する。チップは生きている。ただ v1.85 PQC を実装したシリコンが、まだ世に無いだけだ。この「拒否」こそが、私たちの立ち位置の証明になる ── 実装が存在する最前線(ファームウェア TPM)のすぐ先、シリコンがまだ到達していない縁に立っている。これは弱点ではない。フロンティアだ。
なぜ急ぐのか。CNSA 2.0 はファームウェア署名を最初の期限に置いている(2030年 exclusive-use、その前に 2027年の調達ゲート)。TPM の PQC 対応は、そこに直結する。シリコンが来る前に、Rust の道を通しておく価値はここにある。
正直な限界(薄めない)
- ファームウェア TPM 限定。v1.85 PQC を積んだハードウェア TPM はまだ存在しないので、存在する唯一の実装=ファームウェア TPM で実証した。実機検証は出荷を待つ。トランスポートは TCTI 型なので移植は無改変で通る見込みだが、これは未検証。
- 一部パラメータは暫定。Sequence-Start 系の数フィールドは観測ワイヤに固定してある。機能的には成功している(TPM が受理し、Verify がチケットを返す)。残っているのは、そのバイト配置を TCG Part 3 仕様と突き合わせる「文書化」であって、動くかどうかではない。
- 実験的(0.2.x)、PoC。レスポンスのデコードは手書きで、v1.85 層は fork 派生として維持している。at your own risk。
刻んだのは、別の座標
C が先に着いた。それは事実だし、隠さない。私が刻んだのは別の座標だ ── Rust だけで、C 無しで、crates.io に置ける形で、そして C をオラクルにバイト単位で答え合わせしながら。ワンセグの回で「ネイティブと1バイト違わず同じ TS が出た」と書いた、あの気持ちよさと同じ確度で、TPM にポスト量子を喋らせた。次は、シリコンが来る番だ。
- crates.io: hyde-tpm
- ソース: gitlab.com/Ryujiyasu/hyde
- 🇬🇧 English version → The first Rust-native TPM 2.0 v1.85 PQC crate

コメントを残す