メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

新機能

crypto パッケージに、External μ ML-DSA 署名の入力が事前ハッシュ済みであることを示すセンチネル値 MLDSAMu が追加された。

crypto

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

crypto パッケージに新しい HashMLDSAMu が追加された。これ自体は実装を持たないセンチネル値で、SignerOpts.HashFunc()MLDSAMu を返すことによって、crypto.Signer に渡すメッセージが RFC 9881 の定める「事前ハッシュ済み External μ メッセージ代表値」であることをシグナルするために使う。主な利用先は crypto/mldsaPrivateKey.Sign/SignDeterministic で、ハッシュ計算を内部で完結させたいハードウェア実装から External μ 方式の ML-DSA 署名を要求できるようになる。

導入経緯

この値は crypto/mldsa パッケージ新設のproposal(golang/go#77626)の一部として提案された。提案では、External μ に対応する crypto.Hash センチネル値を用意することで、crypto.Signer インターフェース経由でハードウェア実装(HSMなど)にも一貫した方法で事前ハッシュ済みの入力を渡せるようにする狙いが説明されている。

議論タイムライン

  • 2026-02-25: Weekly Proposal Review Meeting で active へ移行。
  • 2026-04-15: Proposal Review Meeting でトップコメントのAPIをそのまま進めることに合意し、likely accept へ移行。
  • 2026-04-22: それ以降コンセンサスに変更がなかったため accepted。

議論のハイライト

  • 「なぜ Options のフィールドではなく crypto.Hash のセンチネル値なのか」という設計判断について、issue内では①crypto.Signer 実装が PSSOptions のようにフィールドを無視してしまいがちなこと、②External μ の検証が安全かどうかまだ明確でないためサポートすべきでないこと、③Options.Context と両立しないこと、の3点が理由として挙げられ、実装を持たない crypto.Hash 値という点で crypto.MD5SHA1 に前例があるとされた。
  • レビューでは MLDSAMu に対して Size()/Available()/HashFunc()/String()/New() の各メソッドの挙動が問われ、提案者は「Size() は64を返す、Available() は false、HashFunc() は自分自身、String()ML-DSA μ message representativeNew() はpanicする」と回答した。また「crypto.RegisterHash で外部パッケージが実装を差し込めないようにすべきか」という指摘を受け、RegisterHashMLDSAMu に対して明示的にpanicするよう変更された。
  • 「pre-hashed μ message representative を hash.Hash として実装できないか」という質問には、公開鍵とコンテキストで初期化する必要があるため通常の func() hash.Hash のような構築子の形にはできない(func(*mldsa.PublicKey, *mldsa.Options) hash.Hash のような形になる)という回答があった。

使用例

Before

package main

import (
	"crypto/mldsa"
	"fmt"
)

func main() {
	sk, err := mldsa.GenerateKey(mldsa.MLDSA44())
	if err != nil {
		panic(err)
	}
	message := []byte("hello, post-quantum world")
	// crypto.Signer 経由でメッセージを渡す標準的な方法は、生のメッセージを
	// そのまま署名するものだけだった。ハードウェア実装が内部で事前ハッシュ
	// (External μ)を計算し終えている場合でも、それを crypto.SignerOpts で
	// 汎用的にシグナルする手段がなかった。
	sig, err := sk.Sign(nil, message, &mldsa.Options{Context: "doc-v1"})
	if err != nil {
		panic(err)
	}
	fmt.Printf("signature length: %d\n", len(sig))
}

After

package main

import (
	"crypto"
	"crypto/mldsa"
	"fmt"
)

func main() {
	sk, err := mldsa.GenerateKey(mldsa.MLDSA44())
	if err != nil {
		panic(err)
	}

	// mu はハードウェア実装などが RFC 9881 の External μ 計算に従って
	// あらかじめ計算した「事前ハッシュ済みメッセージ代表値」で、長さは
	// crypto.MLDSAMu.Size()(64バイト)と一致する必要がある。
	mu := make([]byte, crypto.MLDSAMu.Size())

	// crypto.Hash は HashFunc() で自分自身を返すため crypto.SignerOpts を
	// 満たしており、crypto.MLDSAMu をそのまま opts として渡せる。これにより
	// message が生のメッセージではなく事前ハッシュ済みの μ であることを
	// crypto.Signer 経由で汎用的にシグナルできる。
	sig, err := sk.Sign(nil, mu, crypto.MLDSAMu)
	if err != nil {
		panic(err)
	}
	fmt.Printf("signature length: %d\n", len(sig))
}

移行時の注意

MLDSAMu は実装を持たないセンチネル値なので、crypto.RegisterHash(crypto.MLDSAMu, ...) を呼ぶとpanicする。MLDSAMu.New() を呼び出した場合もpanicし、MLDSAMu.Available() は常に false を返す。crypto.MLDSAMuSignerOpts.HashFunc() の戻り値として使う場合、Sign/SignDeterministic に渡すメッセージは生のメッセージではなく、事前計算済みのExternal μ(64バイト)である必要がある点に注意する。

実装解説

crypto パッケージでは MLDSAMu は既存の Hash の iota 列の末尾に追加された定数で、crypto.go#L91-L96で定義され、digestSizes テーブルで 64 を返すよう登録されている(crypto.go#L121)。String() メソッドは "ML-DSA μ message representative" を返す専用のcaseを持ち(crypto.go#L63)、RegisterHashMLDSAMu が渡されると明示的にpanicするようガードされている(crypto.go#L160)。

利用側の crypto/mldsa では、PrivateKey.Sign/SignDeterministicopts.HashFunc() の値で switch しており、戻り値が 0 なら通常のメッセージとして mldsa.Sign/mldsa.SignDeterministic に、crypto.MLDSAMu なら事前ハッシュ済みの μ として mldsa.SignExternalMu/mldsa.SignExternalMuDeterministic にディスパッチしている(mldsa_fips140v1.26.go#L105-L121)。

関連リンク