mirror of
https://github.com/golang/go.git
synced 2026-06-28 03:40:37 +00:00
crypto/mldsa: new package
Fixes #77626 Change-Id: I3f271f961872d9e33605ba7becba7ece6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/776706 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com> Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
parent
4212586726
commit
7bc111c6eb
13 changed files with 1161 additions and 81 deletions
41
api/next/77626.txt
Normal file
41
api/next/77626.txt
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
pkg crypto, const MLDSAMu = 20 #77626
|
||||
pkg crypto, const MLDSAMu Hash #77626
|
||||
pkg crypto/mldsa, const MLDSA44PublicKeySize = 1312 #77626
|
||||
pkg crypto/mldsa, const MLDSA44PublicKeySize ideal-int #77626
|
||||
pkg crypto/mldsa, const MLDSA44SignatureSize = 2420 #77626
|
||||
pkg crypto/mldsa, const MLDSA44SignatureSize ideal-int #77626
|
||||
pkg crypto/mldsa, const MLDSA65PublicKeySize = 1952 #77626
|
||||
pkg crypto/mldsa, const MLDSA65PublicKeySize ideal-int #77626
|
||||
pkg crypto/mldsa, const MLDSA65SignatureSize = 3309 #77626
|
||||
pkg crypto/mldsa, const MLDSA65SignatureSize ideal-int #77626
|
||||
pkg crypto/mldsa, const MLDSA87PublicKeySize = 2592 #77626
|
||||
pkg crypto/mldsa, const MLDSA87PublicKeySize ideal-int #77626
|
||||
pkg crypto/mldsa, const MLDSA87SignatureSize = 4627 #77626
|
||||
pkg crypto/mldsa, const MLDSA87SignatureSize ideal-int #77626
|
||||
pkg crypto/mldsa, const PrivateKeySize = 32 #77626
|
||||
pkg crypto/mldsa, const PrivateKeySize ideal-int #77626
|
||||
pkg crypto/mldsa, func GenerateKey(Parameters) (*PrivateKey, error) #77626
|
||||
pkg crypto/mldsa, func MLDSA44() Parameters #77626
|
||||
pkg crypto/mldsa, func MLDSA65() Parameters #77626
|
||||
pkg crypto/mldsa, func MLDSA87() Parameters #77626
|
||||
pkg crypto/mldsa, func NewPrivateKey(Parameters, []uint8) (*PrivateKey, error) #77626
|
||||
pkg crypto/mldsa, func NewPublicKey(Parameters, []uint8) (*PublicKey, error) #77626
|
||||
pkg crypto/mldsa, func Verify(*PublicKey, []uint8, []uint8, *Options) error #77626
|
||||
pkg crypto/mldsa, method (*Options) HashFunc() crypto.Hash #77626
|
||||
pkg crypto/mldsa, method (*PrivateKey) Bytes() []uint8 #77626
|
||||
pkg crypto/mldsa, method (*PrivateKey) Equal(crypto.PrivateKey) bool #77626
|
||||
pkg crypto/mldsa, method (*PrivateKey) Public() crypto.PublicKey #77626
|
||||
pkg crypto/mldsa, method (*PrivateKey) PublicKey() *PublicKey #77626
|
||||
pkg crypto/mldsa, method (*PrivateKey) Sign(io.Reader, []uint8, crypto.SignerOpts) ([]uint8, error) #77626
|
||||
pkg crypto/mldsa, method (*PrivateKey) SignDeterministic([]uint8, crypto.SignerOpts) ([]uint8, error) #77626
|
||||
pkg crypto/mldsa, method (*PublicKey) Bytes() []uint8 #77626
|
||||
pkg crypto/mldsa, method (*PublicKey) Equal(crypto.PublicKey) bool #77626
|
||||
pkg crypto/mldsa, method (*PublicKey) Parameters() Parameters #77626
|
||||
pkg crypto/mldsa, method (Parameters) PublicKeySize() int #77626
|
||||
pkg crypto/mldsa, method (Parameters) SignatureSize() int #77626
|
||||
pkg crypto/mldsa, method (Parameters) String() string #77626
|
||||
pkg crypto/mldsa, type Options struct #77626
|
||||
pkg crypto/mldsa, type Options struct, Context string #77626
|
||||
pkg crypto/mldsa, type Parameters struct #77626
|
||||
pkg crypto/mldsa, type PrivateKey struct #77626
|
||||
pkg crypto/mldsa, type PublicKey struct #77626
|
||||
6
doc/next/6-stdlib/70-mldsa.md
Normal file
6
doc/next/6-stdlib/70-mldsa.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
### crypto/mldsa
|
||||
|
||||
<!-- https://go.dev/issue/77626 --->
|
||||
|
||||
The new [crypto/mldsa] package implements the post-quantum ML-DSA signature
|
||||
scheme specified in FIPS 204.
|
||||
2
doc/next/6-stdlib/99-minor/crypto/77626.md
Normal file
2
doc/next/6-stdlib/99-minor/crypto/77626.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
The new [MLDSAMu] [Hash] value is meant to be used as a signaling mechanism for
|
||||
External μ ML-DSA signing.
|
||||
1
doc/next/6-stdlib/99-minor/crypto/mldsa/77626.md
Normal file
1
doc/next/6-stdlib/99-minor/crypto/mldsa/77626.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
<!-- crypto/mldsa is documented in doc/next/6-stdlib/70-mldsa.md. -->
|
||||
|
|
@ -60,6 +60,8 @@ func (h Hash) String() string {
|
|||
return "BLAKE2b-384"
|
||||
case BLAKE2b_512:
|
||||
return "BLAKE2b-512"
|
||||
case MLDSAMu:
|
||||
return "ML-DSA μ message representative"
|
||||
default:
|
||||
return "unknown hash value " + strconv.Itoa(int(h))
|
||||
}
|
||||
|
|
@ -85,6 +87,14 @@ const (
|
|||
BLAKE2b_256 // import golang.org/x/crypto/blake2b
|
||||
BLAKE2b_384 // import golang.org/x/crypto/blake2b
|
||||
BLAKE2b_512 // import golang.org/x/crypto/blake2b
|
||||
|
||||
// MLDSAMu is a sentinel value for a [pre-hashed μ message representative].
|
||||
// It has no implementation, but is used as a [SignerOpts.HashFunc] return
|
||||
// value for [crypto/mldsa.PrivateKey.Sign].
|
||||
//
|
||||
// [pre-hashed μ message representative]: https://www.rfc-editor.org/rfc/rfc9881.html#externalmu
|
||||
MLDSAMu
|
||||
|
||||
maxHash
|
||||
)
|
||||
|
||||
|
|
@ -108,6 +118,7 @@ var digestSizes = []uint8{
|
|||
BLAKE2b_256: 32,
|
||||
BLAKE2b_384: 48,
|
||||
BLAKE2b_512: 64,
|
||||
MLDSAMu: 64,
|
||||
}
|
||||
|
||||
// Size returns the length, in bytes, of a digest resulting from the given hash
|
||||
|
|
@ -146,6 +157,9 @@ func RegisterHash(h Hash, f func() hash.Hash) {
|
|||
if h == 0 || h >= maxHash {
|
||||
panic("crypto: RegisterHash of unknown hash function")
|
||||
}
|
||||
if h == MLDSAMu {
|
||||
panic("crypto: cannot RegisterHash for MLDSAMu")
|
||||
}
|
||||
hashes[h] = f
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ func TestDisallowedAssemblyInstructions(t *testing.T) {
|
|||
func TestRegisterHashLimits(t *testing.T) {
|
||||
// maxHash is not exported, so we just use its value. If maxHash ever changes
|
||||
// this will need to be updated.
|
||||
for _, h := range []crypto.Hash{0, 20} {
|
||||
for _, h := range []crypto.Hash{0, 21, crypto.MLDSAMu} {
|
||||
t.Run(fmt.Sprintf("h=%d", h), func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
|
|
@ -158,3 +158,27 @@ func TestRegisterHashLimits(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMLDSAMu(t *testing.T) {
|
||||
h := crypto.MLDSAMu
|
||||
if got := h.Size(); got != 64 {
|
||||
t.Errorf("MLDSAMu.Size() = %d, want 64", got)
|
||||
}
|
||||
if got := h.String(); got != "ML-DSA μ message representative" {
|
||||
t.Errorf("MLDSAMu.String() = %q", got)
|
||||
}
|
||||
if h.Available() {
|
||||
t.Errorf("MLDSAMu.Available() = true, want false")
|
||||
}
|
||||
if got := h.HashFunc(); got != h {
|
||||
t.Errorf("MLDSAMu.HashFunc() = %v, want itself", got)
|
||||
}
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("MLDSAMu.New() did not panic")
|
||||
}
|
||||
}()
|
||||
_ = h.New()
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
47
src/crypto/mldsa/example_test.go
Normal file
47
src/crypto/mldsa/example_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !fips140v1.0
|
||||
|
||||
package mldsa_test
|
||||
|
||||
import (
|
||||
"crypto/mldsa"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// The signer generates a new ML-DSA-44 key pair.
|
||||
sk, err := mldsa.GenerateKey(mldsa.MLDSA44())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// The signer publishes the public key encoding.
|
||||
publicKey := sk.PublicKey().Bytes()
|
||||
fmt.Printf("public key: %d bytes\n", len(publicKey))
|
||||
|
||||
// The signer signs a message and publishes the signature.
|
||||
msg := []byte("hello, world")
|
||||
sig, err := sk.Sign(nil, msg, &mldsa.Options{Context: "example"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("signature: %d bytes\n", len(sig))
|
||||
|
||||
// The verifier reconstructs the public key and checks the signature.
|
||||
// The context string must match the one used by the signer.
|
||||
pk, err := mldsa.NewPublicKey(mldsa.MLDSA44(), publicKey)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := mldsa.Verify(pk, msg, sig, &mldsa.Options{Context: "example"}); err != nil {
|
||||
log.Fatal("invalid signature: ", err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// public key: 1312 bytes
|
||||
// signature: 2420 bytes
|
||||
}
|
||||
97
src/crypto/mldsa/mldsa.go
Normal file
97
src/crypto/mldsa/mldsa.go
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package mldsa implements the post-quantum ML-DSA signature scheme specified
|
||||
// in [FIPS 204].
|
||||
//
|
||||
// This package is unavailable if using the [FIPS 140-3 Go Cryptographic Module]
|
||||
// v1.0.0, in which case [GenerateKey], [NewPrivateKey], [NewPublicKey], and
|
||||
// [Verify] will return an error. It is available if using v1.26.0 or later.
|
||||
//
|
||||
// [FIPS 204]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf
|
||||
// [FIPS 140-3 Go Cryptographic Module]: https://go.dev/doc/security/fips140
|
||||
package mldsa
|
||||
|
||||
import "crypto"
|
||||
|
||||
const (
|
||||
PrivateKeySize = 32
|
||||
|
||||
MLDSA44PublicKeySize = 1312
|
||||
MLDSA65PublicKeySize = 1952
|
||||
MLDSA87PublicKeySize = 2592
|
||||
|
||||
MLDSA44SignatureSize = 2420
|
||||
MLDSA65SignatureSize = 3309
|
||||
MLDSA87SignatureSize = 4627
|
||||
)
|
||||
|
||||
// Parameters represents one of the fixed parameter sets defined in FIPS 204.
|
||||
//
|
||||
// Most applications should use [MLDSA44].
|
||||
//
|
||||
// Multiple invocations of [MLDSA44], [MLDSA65], or [MLDSA87] will return the
|
||||
// same respective value, which can be used for equality checks and switch
|
||||
// statements. The returned value is safe for concurrent use.
|
||||
type Parameters struct {
|
||||
name string
|
||||
publicKeySize int
|
||||
signatureSize int
|
||||
}
|
||||
|
||||
// MLDSA44 returns the ML-DSA-44 parameter set defined in FIPS 204.
|
||||
func MLDSA44() Parameters {
|
||||
return Parameters{
|
||||
name: "ML-DSA-44",
|
||||
publicKeySize: MLDSA44PublicKeySize,
|
||||
signatureSize: MLDSA44SignatureSize,
|
||||
}
|
||||
}
|
||||
|
||||
// MLDSA65 returns the ML-DSA-65 parameter set defined in FIPS 204.
|
||||
func MLDSA65() Parameters {
|
||||
return Parameters{
|
||||
name: "ML-DSA-65",
|
||||
publicKeySize: MLDSA65PublicKeySize,
|
||||
signatureSize: MLDSA65SignatureSize,
|
||||
}
|
||||
}
|
||||
|
||||
// MLDSA87 returns the ML-DSA-87 parameter set defined in FIPS 204.
|
||||
func MLDSA87() Parameters {
|
||||
return Parameters{
|
||||
name: "ML-DSA-87",
|
||||
publicKeySize: MLDSA87PublicKeySize,
|
||||
signatureSize: MLDSA87SignatureSize,
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeySize returns the size of public keys for this parameter set, in bytes.
|
||||
func (params Parameters) PublicKeySize() int {
|
||||
return params.publicKeySize
|
||||
}
|
||||
|
||||
// SignatureSize returns the size of signatures for this parameter set, in bytes.
|
||||
func (params Parameters) SignatureSize() int {
|
||||
return params.signatureSize
|
||||
}
|
||||
|
||||
// String returns the name of the parameter set, e.g. "ML-DSA-44".
|
||||
func (params Parameters) String() string {
|
||||
return params.name
|
||||
}
|
||||
|
||||
// Options contains additional options for signing and verifying ML-DSA signatures.
|
||||
type Options struct {
|
||||
// Context can be used to distinguish signatures created for different
|
||||
// purposes. It must be at most 255 bytes long, and it is empty by default.
|
||||
//
|
||||
// The same context must be used when signing and verifying a signature.
|
||||
Context string
|
||||
}
|
||||
|
||||
// HashFunc returns zero, to implement the [crypto.SignerOpts] interface.
|
||||
func (opts *Options) HashFunc() crypto.Hash {
|
||||
return 0
|
||||
}
|
||||
116
src/crypto/mldsa/mldsa_fips140v1.0.go
Normal file
116
src/crypto/mldsa/mldsa_fips140v1.0.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build fips140v1.0
|
||||
|
||||
package mldsa
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// This file provides stub implementations of the ML-DSA API for building
|
||||
// against the FIPS 140-3 Go Cryptographic Module v1.0.0, which does not include
|
||||
// ML-DSA. Top-level functions return an error, and methods are unreachable
|
||||
// since there is no way to construct a valid PublicKey or PrivateKey.
|
||||
|
||||
var errUnavailable = errors.New("mldsa: unavailable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
|
||||
// PrivateKey is an in-memory ML-DSA private key. It implements [crypto.Signer]
|
||||
// and the informal extended [crypto.PrivateKey] interface.
|
||||
//
|
||||
// A PrivateKey is safe for concurrent use.
|
||||
type PrivateKey struct{}
|
||||
|
||||
// GenerateKey generates a new random ML-DSA private key.
|
||||
func GenerateKey(params Parameters) (*PrivateKey, error) {
|
||||
return nil, errUnavailable
|
||||
}
|
||||
|
||||
// NewPrivateKey decodes an ML-DSA private key from the given seed.
|
||||
//
|
||||
// The seed must be exactly [PrivateKeySize] bytes long.
|
||||
func NewPrivateKey(params Parameters, seed []byte) (*PrivateKey, error) {
|
||||
return nil, errUnavailable
|
||||
}
|
||||
|
||||
// Public returns the corresponding [PublicKey] for this private key.
|
||||
//
|
||||
// It implements the [crypto.Signer] interface.
|
||||
func (sk *PrivateKey) Public() crypto.PublicKey {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// Equal reports whether sk and x are the same key (i.e. they are derived from
|
||||
// the same seed).
|
||||
//
|
||||
// If x is not a *PrivateKey, Equal returns false.
|
||||
func (sk *PrivateKey) Equal(x crypto.PrivateKey) bool {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// PublicKey returns the corresponding [PublicKey] for this private key.
|
||||
func (sk *PrivateKey) PublicKey() *PublicKey {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// Bytes returns the private key seed.
|
||||
func (sk *PrivateKey) Bytes() []byte {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// Sign returns a signature of the given message using this private key.
|
||||
//
|
||||
// If opts is nil or opts.HashFunc returns zero, the message is signed directly.
|
||||
// If opts.HashFunc returns [crypto.MLDSAMu], the provided message must be a
|
||||
// [pre-hashed μ message representative]. opts can be of type *[Options].
|
||||
// The io.Reader argument is ignored.
|
||||
//
|
||||
// [pre-hashed μ message representative]: https://www.rfc-editor.org/rfc/rfc9881.html#externalmu
|
||||
func (sk *PrivateKey) Sign(_ io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// SignDeterministic works like [PrivateKey.Sign], but the signature is
|
||||
// deterministic.
|
||||
func (sk *PrivateKey) SignDeterministic(message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// PublicKey is an ML-DSA public key. It implements the informal extended
|
||||
// [crypto.PublicKey] interface.
|
||||
//
|
||||
// A PublicKey is safe for concurrent use.
|
||||
type PublicKey struct{}
|
||||
|
||||
// NewPublicKey creates a new ML-DSA public key from the given encoding.
|
||||
func NewPublicKey(params Parameters, encoding []byte) (*PublicKey, error) {
|
||||
return nil, errUnavailable
|
||||
}
|
||||
|
||||
// Bytes returns the public key encoding.
|
||||
func (pk *PublicKey) Bytes() []byte {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// Equal reports whether pk and x are the same key (i.e. they have the same
|
||||
// encoding).
|
||||
//
|
||||
// If x is not a *PublicKey, Equal returns false.
|
||||
func (pk *PublicKey) Equal(x crypto.PublicKey) bool {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// Parameters returns the parameters associated with this public key.
|
||||
func (pk *PublicKey) Parameters() Parameters {
|
||||
panic("mldsa: methods are unreachable in FIPS 140-3 Go Cryptographic Module v1.0.0")
|
||||
}
|
||||
|
||||
// Verify reports whether signature is a valid signature of message by pk.
|
||||
// If opts is nil, it's equivalent to the zero value of Options.
|
||||
func Verify(pk *PublicKey, message []byte, signature []byte, opts *Options) error {
|
||||
return errUnavailable
|
||||
}
|
||||
93
src/crypto/mldsa/mldsa_fips140v1.0_test.go
Normal file
93
src/crypto/mldsa/mldsa_fips140v1.0_test.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build fips140v1.0
|
||||
|
||||
package mldsa_test
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
. "crypto/mldsa"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ crypto.Signer = (*PrivateKey)(nil)
|
||||
|
||||
func TestUnavailable(t *testing.T) {
|
||||
for _, params := range []Parameters{MLDSA44(), MLDSA65(), MLDSA87()} {
|
||||
t.Run(params.String(), func(t *testing.T) {
|
||||
if _, err := GenerateKey(params); err == nil {
|
||||
t.Errorf("GenerateKey: want error, got nil")
|
||||
}
|
||||
if _, err := NewPrivateKey(params, make([]byte, PrivateKeySize)); err == nil {
|
||||
t.Errorf("NewPrivateKey: want error, got nil")
|
||||
}
|
||||
if _, err := NewPublicKey(params, make([]byte, params.PublicKeySize())); err == nil {
|
||||
t.Errorf("NewPublicKey: want error, got nil")
|
||||
}
|
||||
if err := Verify(&PublicKey{}, nil, nil, nil); err == nil {
|
||||
t.Errorf("Verify: want error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethodsPanic(t *testing.T) {
|
||||
// All PrivateKey and PublicKey methods are unreachable in the v1.0 stub
|
||||
// (since there is no way to construct a non-zero key) and panic if invoked
|
||||
// on the zero value.
|
||||
sk := &PrivateKey{}
|
||||
pk := &PublicKey{}
|
||||
cases := []struct {
|
||||
name string
|
||||
fn func()
|
||||
}{
|
||||
{"PrivateKey.Public", func() { sk.Public() }},
|
||||
{"PrivateKey.Equal", func() { sk.Equal(sk) }},
|
||||
{"PrivateKey.PublicKey", func() { sk.PublicKey() }},
|
||||
{"PrivateKey.Bytes", func() { sk.Bytes() }},
|
||||
{"PrivateKey.Sign", func() { sk.Sign(nil, nil, nil) }},
|
||||
{"PrivateKey.SignDeterministic", func() { sk.SignDeterministic(nil, nil) }},
|
||||
{"PublicKey.Bytes", func() { pk.Bytes() }},
|
||||
{"PublicKey.Equal", func() { pk.Equal(pk) }},
|
||||
{"PublicKey.Parameters", func() { pk.Parameters() }},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("%s: did not panic", tc.name)
|
||||
}
|
||||
}()
|
||||
tc.fn()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParametersAvailable(t *testing.T) {
|
||||
// The Parameters value type and its methods must remain usable even under
|
||||
// the v1.0 stub, so callers can introspect parameter sets without invoking
|
||||
// the unavailable key APIs.
|
||||
cases := []struct {
|
||||
params Parameters
|
||||
name string
|
||||
pkSize int
|
||||
sigSize int
|
||||
}{
|
||||
{MLDSA44(), "ML-DSA-44", MLDSA44PublicKeySize, MLDSA44SignatureSize},
|
||||
{MLDSA65(), "ML-DSA-65", MLDSA65PublicKeySize, MLDSA65SignatureSize},
|
||||
{MLDSA87(), "ML-DSA-87", MLDSA87PublicKeySize, MLDSA87SignatureSize},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got := tc.params.String(); got != tc.name {
|
||||
t.Errorf("String() = %q, want %q", got, tc.name)
|
||||
}
|
||||
if got := tc.params.PublicKeySize(); got != tc.pkSize {
|
||||
t.Errorf("%s PublicKeySize() = %d, want %d", tc.name, got, tc.pkSize)
|
||||
}
|
||||
if got := tc.params.SignatureSize(); got != tc.sigSize {
|
||||
t.Errorf("%s SignatureSize() = %d, want %d", tc.name, got, tc.sigSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
212
src/crypto/mldsa/mldsa_fips140v1.26.go
Normal file
212
src/crypto/mldsa/mldsa_fips140v1.26.go
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !fips140v1.0
|
||||
|
||||
package mldsa
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/internal/fips140/mldsa"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// PrivateKey is an in-memory ML-DSA private key. It implements [crypto.Signer]
|
||||
// and the informal extended [crypto.PrivateKey] interface.
|
||||
//
|
||||
// A PrivateKey is safe for concurrent use.
|
||||
type PrivateKey struct {
|
||||
k mldsa.PrivateKey
|
||||
}
|
||||
|
||||
var errInvalidParameters = errors.New("mldsa: invalid parameters")
|
||||
|
||||
// GenerateKey generates a new random ML-DSA private key.
|
||||
func GenerateKey(params Parameters) (*PrivateKey, error) {
|
||||
switch params {
|
||||
case MLDSA44():
|
||||
return &PrivateKey{k: *mldsa.GenerateKey44()}, nil
|
||||
case MLDSA65():
|
||||
return &PrivateKey{k: *mldsa.GenerateKey65()}, nil
|
||||
case MLDSA87():
|
||||
return &PrivateKey{k: *mldsa.GenerateKey87()}, nil
|
||||
default:
|
||||
return nil, errInvalidParameters
|
||||
}
|
||||
}
|
||||
|
||||
// NewPrivateKey decodes an ML-DSA private key from the given seed.
|
||||
//
|
||||
// The seed must be exactly [PrivateKeySize] bytes long.
|
||||
func NewPrivateKey(params Parameters, seed []byte) (*PrivateKey, error) {
|
||||
var err error
|
||||
var k *mldsa.PrivateKey
|
||||
switch params {
|
||||
case MLDSA44():
|
||||
k, err = mldsa.NewPrivateKey44(seed)
|
||||
case MLDSA65():
|
||||
k, err = mldsa.NewPrivateKey65(seed)
|
||||
case MLDSA87():
|
||||
k, err = mldsa.NewPrivateKey87(seed)
|
||||
default:
|
||||
return nil, errInvalidParameters
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PrivateKey{k: *k}, nil
|
||||
}
|
||||
|
||||
// Public returns the corresponding [PublicKey] for this private key.
|
||||
//
|
||||
// It implements the [crypto.Signer] interface.
|
||||
func (sk *PrivateKey) Public() crypto.PublicKey {
|
||||
return sk.PublicKey()
|
||||
}
|
||||
|
||||
// Equal reports whether sk and x are the same key (i.e. they are derived from
|
||||
// the same seed).
|
||||
//
|
||||
// If x is not a *PrivateKey, Equal returns false.
|
||||
func (sk *PrivateKey) Equal(x crypto.PrivateKey) bool {
|
||||
other, ok := x.(*PrivateKey)
|
||||
if !ok || other == nil {
|
||||
return false
|
||||
}
|
||||
return sk.k.Equal(&other.k)
|
||||
}
|
||||
|
||||
// PublicKey returns the corresponding [PublicKey] for this private key.
|
||||
func (sk *PrivateKey) PublicKey() *PublicKey {
|
||||
// Making a copy severs the pointer relationship between the private and
|
||||
// public keys, so that keeping the public key around doesn't keep the
|
||||
// private key alive. This costs a copy and an allocation.
|
||||
return &PublicKey{p: *sk.k.PublicKey()}
|
||||
}
|
||||
|
||||
// Bytes returns the private key seed.
|
||||
func (sk *PrivateKey) Bytes() []byte {
|
||||
return sk.k.Bytes()
|
||||
}
|
||||
|
||||
var errInvalidSignerOpts = errors.New("mldsa: invalid SignerOpts")
|
||||
|
||||
// Sign returns a signature of the given message using this private key.
|
||||
//
|
||||
// If opts is nil or opts.HashFunc returns zero, the message is signed directly.
|
||||
// If opts.HashFunc returns [crypto.MLDSAMu], the provided message must be a
|
||||
// [pre-hashed μ message representative]. opts can be of type *[Options] if a
|
||||
// context string is desired along with a directly-signed message. The io.Reader
|
||||
// argument is ignored.
|
||||
//
|
||||
// [pre-hashed μ message representative]: https://www.rfc-editor.org/rfc/rfc9881.html#externalmu
|
||||
func (sk *PrivateKey) Sign(_ io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
switch opts.HashFunc() {
|
||||
case 0:
|
||||
var context string
|
||||
if opts, ok := opts.(*Options); ok {
|
||||
context = opts.Context
|
||||
}
|
||||
return mldsa.Sign(&sk.k, message, context)
|
||||
case crypto.MLDSAMu:
|
||||
return mldsa.SignExternalMu(&sk.k, message)
|
||||
default:
|
||||
return nil, errInvalidSignerOpts
|
||||
}
|
||||
}
|
||||
|
||||
// SignDeterministic works like [PrivateKey.Sign], but the signature is
|
||||
// deterministic.
|
||||
func (sk *PrivateKey) SignDeterministic(message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
switch opts.HashFunc() {
|
||||
case 0:
|
||||
var context string
|
||||
if opts, ok := opts.(*Options); ok {
|
||||
context = opts.Context
|
||||
}
|
||||
return mldsa.SignDeterministic(&sk.k, message, context)
|
||||
case crypto.MLDSAMu:
|
||||
return mldsa.SignExternalMuDeterministic(&sk.k, message)
|
||||
default:
|
||||
return nil, errInvalidSignerOpts
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKey is an ML-DSA public key. It implements the informal extended
|
||||
// [crypto.PublicKey] interface.
|
||||
//
|
||||
// A PublicKey is safe for concurrent use.
|
||||
type PublicKey struct {
|
||||
p mldsa.PublicKey
|
||||
}
|
||||
|
||||
// NewPublicKey creates a new ML-DSA public key from the given encoding.
|
||||
func NewPublicKey(params Parameters, encoding []byte) (*PublicKey, error) {
|
||||
var err error
|
||||
var pk *mldsa.PublicKey
|
||||
switch params {
|
||||
case MLDSA44():
|
||||
pk, err = mldsa.NewPublicKey44(encoding)
|
||||
case MLDSA65():
|
||||
pk, err = mldsa.NewPublicKey65(encoding)
|
||||
case MLDSA87():
|
||||
pk, err = mldsa.NewPublicKey87(encoding)
|
||||
default:
|
||||
return nil, errInvalidParameters
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PublicKey{p: *pk}, nil
|
||||
}
|
||||
|
||||
// Bytes returns the public key encoding.
|
||||
func (pk *PublicKey) Bytes() []byte {
|
||||
return pk.p.Bytes()
|
||||
}
|
||||
|
||||
// Equal reports whether pk and x are the same key (i.e. they have the same
|
||||
// encoding).
|
||||
//
|
||||
// If x is not a *PublicKey, Equal returns false.
|
||||
func (pk *PublicKey) Equal(x crypto.PublicKey) bool {
|
||||
other, ok := x.(*PublicKey)
|
||||
if !ok || other == nil {
|
||||
return false
|
||||
}
|
||||
return pk.p.Equal(&other.p)
|
||||
}
|
||||
|
||||
// Parameters returns the parameters associated with this public key.
|
||||
func (pk *PublicKey) Parameters() Parameters {
|
||||
switch pk.p.Parameters() {
|
||||
case "ML-DSA-44":
|
||||
return MLDSA44()
|
||||
case "ML-DSA-65":
|
||||
return MLDSA65()
|
||||
case "ML-DSA-87":
|
||||
return MLDSA87()
|
||||
default:
|
||||
panic("mldsa: invalid parameters in public key")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify reports whether signature is a valid signature of message by pk.
|
||||
// If opts is nil, it's equivalent to the zero value of Options.
|
||||
func Verify(pk *PublicKey, message []byte, signature []byte, opts *Options) error {
|
||||
if pk == nil {
|
||||
return errors.New("mldsa: nil public key")
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
return mldsa.Verify(&pk.p, message, signature, opts.Context)
|
||||
}
|
||||
|
|
@ -4,22 +4,27 @@
|
|||
|
||||
//go:build !fips140v1.0
|
||||
|
||||
package fipstest
|
||||
package mldsa_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/fips140"
|
||||
"crypto/internal/cryptotest"
|
||||
"crypto/internal/fips140"
|
||||
. "crypto/internal/fips140/mldsa"
|
||||
"crypto/internal/fips140/sha3"
|
||||
. "crypto/mldsa"
|
||||
"crypto/sha3"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ crypto.Signer = (*PrivateKey)(nil)
|
||||
|
||||
var sixtyMillionFlag = flag.Bool("60million", false, "run 60M-iterations accumulated test")
|
||||
|
||||
// TestMLDSAAccumulated accumulates 10k (or 100, or 60M) random vectors and checks
|
||||
// TestAccumulated accumulates 10k (or 100, or 60M) random vectors and checks
|
||||
// the hash of the result, to avoid checking in megabytes of test vectors.
|
||||
//
|
||||
// 60M in particular is enough to give a 99.9% chance of hitting every value in
|
||||
|
|
@ -28,111 +33,119 @@ var sixtyMillionFlag = flag.Bool("60million", false, "run 60M-iterations accumul
|
|||
// 1-((q-1)/q)^60000000 ~= 0.9992
|
||||
//
|
||||
// If setting -60million, remember to also set -timeout 0.
|
||||
func TestMLDSAAccumulated(t *testing.T) {
|
||||
func TestAccumulated(t *testing.T) {
|
||||
t.Run("ML-DSA-44/100", func(t *testing.T) {
|
||||
testMLDSAAccumulated(t, NewPrivateKey44, NewPublicKey44, 100,
|
||||
testAccumulated(t, MLDSA44(), 100,
|
||||
"d51148e1f9f4fa1a723a6cf42e25f2a99eb5c1b378b3d2dbbd561b1203beeae4")
|
||||
})
|
||||
t.Run("ML-DSA-65/100", func(t *testing.T) {
|
||||
testMLDSAAccumulated(t, NewPrivateKey65, NewPublicKey65, 100,
|
||||
testAccumulated(t, MLDSA65(), 100,
|
||||
"8358a1843220194417cadbc2651295cd8fc65125b5a5c1a239a16dc8b57ca199")
|
||||
})
|
||||
t.Run("ML-DSA-87/100", func(t *testing.T) {
|
||||
testMLDSAAccumulated(t, NewPrivateKey87, NewPublicKey87, 100,
|
||||
testAccumulated(t, MLDSA87(), 100,
|
||||
"8c3ad714777622b8f21ce31bb35f71394f23bc0fcf3c78ace5d608990f3b061b")
|
||||
})
|
||||
if !testing.Short() {
|
||||
t.Run("ML-DSA-44/10k", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testMLDSAAccumulated(t, NewPrivateKey44, NewPublicKey44, 10000,
|
||||
testAccumulated(t, MLDSA44(), 10000,
|
||||
"e7fd21f6a59bcba60d65adc44404bb29a7c00e5d8d3ec06a732c00a306a7d143")
|
||||
})
|
||||
t.Run("ML-DSA-65/10k", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testMLDSAAccumulated(t, NewPrivateKey65, NewPublicKey65, 10000,
|
||||
testAccumulated(t, MLDSA65(), 10000,
|
||||
"5ff5e196f0b830c3b10a9eb5358e7c98a3a20136cb677f3ae3b90175c3ace329")
|
||||
})
|
||||
t.Run("ML-DSA-87/10k", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testMLDSAAccumulated(t, NewPrivateKey87, NewPublicKey87, 10000,
|
||||
testAccumulated(t, MLDSA87(), 10000,
|
||||
"80a8cf39317f7d0be0e24972c51ac152bd2a3e09bc0c32ce29dd82c4e7385e60")
|
||||
})
|
||||
}
|
||||
if *sixtyMillionFlag {
|
||||
t.Run("ML-DSA-44/60M", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testMLDSAAccumulated(t, NewPrivateKey44, NewPublicKey44, 60000000,
|
||||
testAccumulated(t, MLDSA44(), 60000000,
|
||||
"080b48049257f5cd30dee17d6aa393d6c42fe52a29099df84a460ebaf4b02330")
|
||||
})
|
||||
t.Run("ML-DSA-65/60M", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testMLDSAAccumulated(t, NewPrivateKey65, NewPublicKey65, 60000000,
|
||||
testAccumulated(t, MLDSA65(), 60000000,
|
||||
"0af0165db2b180f7a83dbecad1ccb758b9c2d834b7f801fc49dd572a9d4b1e83")
|
||||
})
|
||||
t.Run("ML-DSA-87/60M", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testMLDSAAccumulated(t, NewPrivateKey87, NewPublicKey87, 60000000,
|
||||
testAccumulated(t, MLDSA87(), 60000000,
|
||||
"011166e9d5032c9bdc5c9bbb5dbb6c86df1c3d9bf3570b65ebae942dd9830057")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMLDSAAccumulated(t *testing.T, newPrivateKey func([]byte) (*PrivateKey, error), newPublicKey func([]byte) (*PublicKey, error), n int, expected string) {
|
||||
s := sha3.NewShake128()
|
||||
o := sha3.NewShake128()
|
||||
func testAccumulated(t *testing.T, params Parameters, n int, expected string) {
|
||||
s := sha3.NewSHAKE128()
|
||||
o := sha3.NewSHAKE128()
|
||||
seed := make([]byte, PrivateKeySize)
|
||||
msg := make([]byte, 0)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
s.Read(seed)
|
||||
dk, err := newPrivateKey(seed)
|
||||
dk, err := NewPrivateKey(params, seed)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPrivateKey: %v", err)
|
||||
}
|
||||
pk := dk.PublicKey().Bytes()
|
||||
o.Write(pk)
|
||||
sig, err := SignDeterministic(dk, msg, "")
|
||||
sig, err := dk.SignDeterministic(msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic: %v", err)
|
||||
}
|
||||
o.Write(sig)
|
||||
pub, err := newPublicKey(pk)
|
||||
pub, err := NewPublicKey(params, pk)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPublicKey: %v", err)
|
||||
}
|
||||
if *pub != *dk.PublicKey() {
|
||||
t.Fatalf("public key mismatch")
|
||||
}
|
||||
if err := Verify(dk.PublicKey(), msg, sig, ""); err != nil {
|
||||
if err := Verify(dk.PublicKey(), msg, sig, nil); err != nil {
|
||||
t.Fatalf("Verify: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
got := hex.EncodeToString(o.Sum(nil))
|
||||
sum := make([]byte, 32)
|
||||
o.Read(sum)
|
||||
got := hex.EncodeToString(sum)
|
||||
if got != expected {
|
||||
t.Errorf("got %s, expected %s", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMLDSAGenerateKey(t *testing.T) {
|
||||
t.Run("ML-DSA-44", func(t *testing.T) {
|
||||
testMLDSAGenerateKey(t, GenerateKey44, NewPrivateKey44)
|
||||
})
|
||||
t.Run("ML-DSA-65", func(t *testing.T) {
|
||||
testMLDSAGenerateKey(t, GenerateKey65, NewPrivateKey65)
|
||||
})
|
||||
t.Run("ML-DSA-87", func(t *testing.T) {
|
||||
testMLDSAGenerateKey(t, GenerateKey87, NewPrivateKey87)
|
||||
})
|
||||
func testAllParameters(t *testing.T, f func(*testing.T, Parameters)) {
|
||||
for _, params := range []Parameters{MLDSA44(), MLDSA65(), MLDSA87()} {
|
||||
t.Run(params.String(), func(t *testing.T) {
|
||||
f(t, params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMLDSAGenerateKey(t *testing.T, generateKey func() *PrivateKey, newPrivateKey func([]byte) (*PrivateKey, error)) {
|
||||
k1 := generateKey()
|
||||
k2 := generateKey()
|
||||
func TestGenerateKey(t *testing.T) {
|
||||
testAllParameters(t, testGenerateKey)
|
||||
}
|
||||
|
||||
func testGenerateKey(t *testing.T, params Parameters) {
|
||||
k1, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
k2, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
if k1.Equal(k2) {
|
||||
t.Errorf("two generated keys are equal")
|
||||
}
|
||||
k1x, err := newPrivateKey(k1.Bytes())
|
||||
k1x, err := NewPrivateKey(params, k1.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("NewPrivateKey: %v", err)
|
||||
}
|
||||
|
|
@ -141,39 +154,50 @@ func testMLDSAGenerateKey(t *testing.T, generateKey func() *PrivateKey, newPriva
|
|||
}
|
||||
}
|
||||
|
||||
func TestMLDSAAllocations(t *testing.T) {
|
||||
// We allocate the PrivateKey (k and kk) and PublicKey (pk) structs and the
|
||||
// public key (pkBytes) and signature (sig) byte slices on the heap. They
|
||||
// are all large and for the byte slices variable-length. Still, check we
|
||||
// are not slipping more allocations in.
|
||||
var expected float64 = 5
|
||||
if fips140.Enabled {
|
||||
func TestAllocations(t *testing.T) {
|
||||
// We allocate
|
||||
//
|
||||
// - the PrivateKey (k and kk) and PublicKey (pk) structs
|
||||
// - their temporary inner structs (3x)
|
||||
// - the public key (pkBytes) and signature (sig) byte slices
|
||||
// - the k.PublicKey() return value
|
||||
// - the Options argument to Sign
|
||||
//
|
||||
// on the heap. The structs are too large for the stack, the byte slices are
|
||||
// variable-sized, and Options is cast into an interface.
|
||||
//
|
||||
// Still, check we are not slipping more allocations in.
|
||||
var expected float64 = 10
|
||||
if fips140.Enabled() {
|
||||
// The PCT does a sign/verify cycle, which allocates a signature slice.
|
||||
expected += 1
|
||||
}
|
||||
cryptotest.SkipTestAllocations(t)
|
||||
if allocs := testing.AllocsPerRun(100, func() {
|
||||
k := GenerateKey44()
|
||||
seed := k.Bytes()
|
||||
kk, err := NewPrivateKey44(seed)
|
||||
k, err := GenerateKey(MLDSA44())
|
||||
if err != nil {
|
||||
t.Fatalf("NewPrivateKey44: %v", err)
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
seed := k.Bytes()
|
||||
kk, err := NewPrivateKey(MLDSA44(), seed)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPrivateKey: %v", err)
|
||||
}
|
||||
if !k.Equal(kk) {
|
||||
t.Fatalf("keys not equal")
|
||||
}
|
||||
pkBytes := k.PublicKey().Bytes()
|
||||
pk, err := NewPublicKey44(pkBytes)
|
||||
pk, err := NewPublicKey(MLDSA44(), pkBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("NewPublicKey44: %v", err)
|
||||
t.Fatalf("NewPublicKey: %v", err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
context := "test"
|
||||
sig, err := Sign(k, message, context)
|
||||
sig, err := k.Sign(nil, message, &Options{Context: context})
|
||||
if err != nil {
|
||||
t.Fatalf("Sign: %v", err)
|
||||
}
|
||||
if err := Verify(pk, message, sig, context); err != nil {
|
||||
if err := Verify(pk, message, sig, &Options{Context: context}); err != nil {
|
||||
t.Fatalf("Verify: %v", err)
|
||||
}
|
||||
}); allocs > expected {
|
||||
|
|
@ -181,26 +205,409 @@ func TestMLDSAAllocations(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkMLDSASign(b *testing.B) {
|
||||
func TestParametersIdentity(t *testing.T) {
|
||||
// Per the MLDSA*() docs, repeated calls return the same value, suitable for
|
||||
// equality checks and switch statements.
|
||||
if MLDSA44() != MLDSA44() || MLDSA65() != MLDSA65() || MLDSA87() != MLDSA87() {
|
||||
t.Errorf("MLDSA*() returned different values across calls")
|
||||
}
|
||||
if MLDSA44() == MLDSA65() || MLDSA65() == MLDSA87() || MLDSA44() == MLDSA87() {
|
||||
t.Errorf("distinct parameter sets compare equal")
|
||||
}
|
||||
}
|
||||
|
||||
// computeMu reproduces μ = SHAKE256(SHAKE256(pk, 64) || 0x00 || ctxlen || ctx ||
|
||||
// msg, 64) per FIPS 204, used to drive the External μ signing path.
|
||||
func computeMu(pk, ctx, msg []byte) []byte {
|
||||
tr := sha3.NewSHAKE256()
|
||||
tr.Write(pk)
|
||||
trOut := make([]byte, 64)
|
||||
tr.Read(trOut)
|
||||
|
||||
h := sha3.NewSHAKE256()
|
||||
h.Write(trOut)
|
||||
h.Write([]byte{0x00, byte(len(ctx))})
|
||||
h.Write(ctx)
|
||||
h.Write(msg)
|
||||
out := make([]byte, 64)
|
||||
h.Read(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// fakeSignerOpts is a [crypto.SignerOpts] whose [HashFunc] returns h, used to
|
||||
// exercise the opts-dispatch paths in [PrivateKey.Sign] without going through
|
||||
// [Options].
|
||||
type fakeSignerOpts struct{ h crypto.Hash }
|
||||
|
||||
func (f fakeSignerOpts) HashFunc() crypto.Hash { return f.h }
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
testAllParameters(t, func(t *testing.T, params Parameters) {
|
||||
sk, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
pk := sk.PublicKey()
|
||||
msg := []byte("test message")
|
||||
|
||||
// nil opts and &Options{} must be equivalent (and both interoperable
|
||||
// with a nil/zero Verify opts).
|
||||
sig1, err := sk.Sign(nil, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(nil opts): %v", err)
|
||||
}
|
||||
if got := len(sig1); got != params.SignatureSize() {
|
||||
t.Errorf("len(sig) = %d, want %d", got, params.SignatureSize())
|
||||
}
|
||||
if err := Verify(pk, msg, sig1, nil); err != nil {
|
||||
t.Errorf("Verify of nil-opts signature with nil opts: %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig1, &Options{}); err != nil {
|
||||
t.Errorf("Verify of nil-opts signature with empty Options: %v", err)
|
||||
}
|
||||
|
||||
sig2, err := sk.Sign(nil, msg, &Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(&Options{}): %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig2, nil); err != nil {
|
||||
t.Errorf("Verify of empty-Options signature with nil opts: %v", err)
|
||||
}
|
||||
|
||||
// A non-*Options crypto.SignerOpts whose HashFunc returns 0 must
|
||||
// also sign directly, with empty context.
|
||||
sig3, err := sk.Sign(nil, msg, fakeSignerOpts{h: 0})
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(fakeSignerOpts{0}): %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig3, nil); err != nil {
|
||||
t.Errorf("Verify of fake-opts signature: %v", err)
|
||||
}
|
||||
|
||||
// crypto.Hash(0) similarly: HashFunc returns 0.
|
||||
sig4, err := sk.Sign(nil, msg, crypto.Hash(0))
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(crypto.Hash(0)): %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig4, nil); err != nil {
|
||||
t.Errorf("Verify of Hash(0)-opts signature: %v", err)
|
||||
}
|
||||
|
||||
// A wrong HashFunc must produce errInvalidSignerOpts.
|
||||
if _, err := sk.Sign(nil, msg, crypto.SHA256); err == nil {
|
||||
t.Errorf("Sign with crypto.SHA256 opts: want error, got nil")
|
||||
}
|
||||
if _, err := sk.SignDeterministic(msg, crypto.SHA256); err == nil {
|
||||
t.Errorf("SignDeterministic with crypto.SHA256 opts: want error, got nil")
|
||||
}
|
||||
|
||||
// SignDeterministic with nil and &Options{} must agree byte-for-byte.
|
||||
detA, err := sk.SignDeterministic(msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic(nil): %v", err)
|
||||
}
|
||||
detB, err := sk.SignDeterministic(msg, &Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic(&Options{}): %v", err)
|
||||
}
|
||||
if !bytes.Equal(detA, detB) {
|
||||
t.Errorf("SignDeterministic with nil and &Options{} differ")
|
||||
}
|
||||
|
||||
// A different Context produces a different deterministic signature
|
||||
// and verification with a mismatched context must fail.
|
||||
detCtx, err := sk.SignDeterministic(msg, &Options{Context: "ctx"})
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic(ctx): %v", err)
|
||||
}
|
||||
if string(detCtx) == string(detA) {
|
||||
t.Errorf("SignDeterministic with empty and non-empty context match")
|
||||
}
|
||||
if err := Verify(pk, msg, detCtx, nil); err == nil {
|
||||
t.Errorf("Verify of context signature with empty context: want error, got nil")
|
||||
}
|
||||
if err := Verify(pk, msg, detCtx, &Options{Context: "ctx"}); err != nil {
|
||||
t.Errorf("Verify with matching context: %v", err)
|
||||
}
|
||||
|
||||
// Context >255 bytes is rejected by the underlying implementation.
|
||||
longCtx := strings.Repeat("x", 256)
|
||||
if _, err := sk.Sign(nil, msg, &Options{Context: longCtx}); err == nil {
|
||||
t.Errorf("Sign with 256-byte context: want error, got nil")
|
||||
}
|
||||
if _, err := sk.SignDeterministic(msg, &Options{Context: longCtx}); err == nil {
|
||||
t.Errorf("SignDeterministic with 256-byte context: want error, got nil")
|
||||
}
|
||||
if err := Verify(pk, msg, detA, &Options{Context: longCtx}); err == nil {
|
||||
t.Errorf("Verify with 256-byte context: want error, got nil")
|
||||
}
|
||||
|
||||
// Tampered signature must not verify.
|
||||
sigTampered := bytes.Clone(sig1)
|
||||
sigTampered[len(sigTampered)/2] ^= 0x01
|
||||
if err := Verify(pk, msg, sigTampered, nil); err == nil {
|
||||
t.Errorf("Verify of tampered signature: want error, got nil")
|
||||
}
|
||||
|
||||
// Modified message must not verify against the original signature.
|
||||
msgTampered := bytes.Clone(msg)
|
||||
msgTampered[0] ^= 0x01
|
||||
if err := Verify(pk, msgTampered, sig1, nil); err == nil {
|
||||
t.Errorf("Verify of modified message: want error, got nil")
|
||||
}
|
||||
|
||||
// Signature from a different key must not verify.
|
||||
skOther, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
sigOther, err := skOther.SignDeterministic(msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic: %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sigOther, nil); err == nil {
|
||||
t.Errorf("Verify of signature from a different key: want error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExternalMu(t *testing.T) {
|
||||
testAllParameters(t, func(t *testing.T, params Parameters) {
|
||||
sk, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
pk := sk.PublicKey()
|
||||
pkBytes := pk.Bytes()
|
||||
msg := []byte("hello mu")
|
||||
|
||||
for _, ctx := range []string{"", "ctx"} {
|
||||
μ := computeMu(pkBytes, []byte(ctx), msg)
|
||||
sig, err := sk.Sign(nil, μ, crypto.MLDSAMu)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(MLDSAMu, ctx=%q): %v", ctx, err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig, &Options{Context: ctx}); err != nil {
|
||||
t.Errorf("Verify of MLDSAMu signature, ctx=%q: %v", ctx, err)
|
||||
}
|
||||
|
||||
detSig, err := sk.SignDeterministic(μ, crypto.MLDSAMu)
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic(MLDSAMu, ctx=%q): %v", ctx, err)
|
||||
}
|
||||
if err := Verify(pk, msg, detSig, &Options{Context: ctx}); err != nil {
|
||||
t.Errorf("Verify of deterministic MLDSAMu signature, ctx=%q: %v", ctx, err)
|
||||
}
|
||||
detSig2, err := sk.SignDeterministic(μ, crypto.MLDSAMu)
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic(MLDSAMu) second call: %v", err)
|
||||
}
|
||||
if string(detSig) != string(detSig2) {
|
||||
t.Errorf("SignDeterministic(MLDSAMu) is not deterministic")
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-context: μ computed under one ctx must not verify under another.
|
||||
μA := computeMu(pkBytes, []byte("a"), msg)
|
||||
sigA, err := sk.Sign(nil, μA, crypto.MLDSAMu)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(MLDSAMu, ctx=a): %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sigA, &Options{Context: "b"}); err == nil {
|
||||
t.Errorf("Verify of MLDSAMu(ctx=a) signature with ctx=b: want error, got nil")
|
||||
}
|
||||
|
||||
// Tampered MLDSAMu signature must not verify.
|
||||
sigTampered := bytes.Clone(sigA)
|
||||
sigTampered[len(sigTampered)/2] ^= 0x01
|
||||
if err := Verify(pk, msg, sigTampered, &Options{Context: "a"}); err == nil {
|
||||
t.Errorf("Verify of tampered MLDSAMu signature: want error, got nil")
|
||||
}
|
||||
|
||||
// Wrong-length μ must be rejected.
|
||||
if _, err := sk.Sign(nil, make([]byte, 32), crypto.MLDSAMu); err == nil {
|
||||
t.Errorf("Sign(MLDSAMu) with 32-byte input: want error, got nil")
|
||||
}
|
||||
if _, err := sk.SignDeterministic(make([]byte, 32), crypto.MLDSAMu); err == nil {
|
||||
t.Errorf("SignDeterministic(MLDSAMu) with 32-byte input: want error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPublicKey(t *testing.T) {
|
||||
cases := []struct {
|
||||
params Parameters
|
||||
name string
|
||||
pkSize int
|
||||
sigSize int
|
||||
}{
|
||||
{MLDSA44(), "ML-DSA-44", MLDSA44PublicKeySize, MLDSA44SignatureSize},
|
||||
{MLDSA65(), "ML-DSA-65", MLDSA65PublicKeySize, MLDSA65SignatureSize},
|
||||
{MLDSA87(), "ML-DSA-87", MLDSA87PublicKeySize, MLDSA87SignatureSize},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := tc.params.String(); got != tc.name {
|
||||
t.Errorf("Parameters.String() = %q, want %q", got, tc.name)
|
||||
}
|
||||
if got := tc.params.PublicKeySize(); got != tc.pkSize {
|
||||
t.Errorf("Parameters.PublicKeySize() = %d, want %d", got, tc.pkSize)
|
||||
}
|
||||
if got := tc.params.SignatureSize(); got != tc.sigSize {
|
||||
t.Errorf("Parameters.SignatureSize() = %d, want %d", got, tc.sigSize)
|
||||
}
|
||||
|
||||
sk, err := GenerateKey(tc.params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
pk := sk.PublicKey()
|
||||
if got := pk.Parameters(); got != tc.params {
|
||||
t.Errorf("PublicKey.Parameters() = %v, want %v", got, tc.params)
|
||||
}
|
||||
if got := len(pk.Bytes()); got != tc.params.PublicKeySize() {
|
||||
t.Errorf("len(PublicKey.Bytes()) = %d, want %d", got, tc.params.PublicKeySize())
|
||||
}
|
||||
if got := len(sk.Bytes()); got != PrivateKeySize {
|
||||
t.Errorf("len(PrivateKey.Bytes()) = %d, want %d", got, PrivateKeySize)
|
||||
}
|
||||
|
||||
// Public() returns the same key as PublicKey().
|
||||
anyPub := sk.Public()
|
||||
pub2, ok := anyPub.(*PublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("PrivateKey.Public() = %T, want *PublicKey", anyPub)
|
||||
}
|
||||
if !pk.Equal(pub2) {
|
||||
t.Errorf("PrivateKey.Public() does not equal PublicKey()")
|
||||
}
|
||||
|
||||
// Round-trip via NewPrivateKey/NewPublicKey.
|
||||
sk2, err := NewPrivateKey(tc.params, sk.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("NewPrivateKey round-trip: %v", err)
|
||||
}
|
||||
if !sk.Equal(sk2) {
|
||||
t.Errorf("PrivateKey round-trip not equal")
|
||||
}
|
||||
pk2, err := NewPublicKey(tc.params, pk.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("NewPublicKey round-trip: %v", err)
|
||||
}
|
||||
if !pk.Equal(pk2) {
|
||||
t.Errorf("PublicKey round-trip not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualWrongType(t *testing.T) {
|
||||
sk, err := GenerateKey(MLDSA44())
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
if sk.Equal("not a key") {
|
||||
t.Errorf("PrivateKey.Equal(string) = true, want false")
|
||||
}
|
||||
if sk.Equal((*PublicKey)(nil)) {
|
||||
t.Errorf("PrivateKey.Equal(*PublicKey) = true, want false")
|
||||
}
|
||||
if sk.PublicKey().Equal("not a key") {
|
||||
t.Errorf("PublicKey.Equal(string) = true, want false")
|
||||
}
|
||||
if sk.PublicKey().Equal((*PrivateKey)(nil)) {
|
||||
t.Errorf("PublicKey.Equal(*PrivateKey) = true, want false")
|
||||
}
|
||||
|
||||
// Distinct keys are not Equal.
|
||||
sk2, err := GenerateKey(MLDSA44())
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
if sk.Equal(sk2) {
|
||||
t.Errorf("two random PrivateKeys are Equal")
|
||||
}
|
||||
if sk.PublicKey().Equal(sk2.PublicKey()) {
|
||||
t.Errorf("two random PublicKeys are Equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidParameters(t *testing.T) {
|
||||
var zero Parameters
|
||||
if _, err := GenerateKey(zero); err == nil {
|
||||
t.Errorf("GenerateKey(zero Parameters): want error, got nil")
|
||||
}
|
||||
if _, err := NewPrivateKey(zero, make([]byte, PrivateKeySize)); err == nil {
|
||||
t.Errorf("NewPrivateKey(zero Parameters): want error, got nil")
|
||||
}
|
||||
if _, err := NewPublicKey(zero, make([]byte, MLDSA44PublicKeySize)); err == nil {
|
||||
t.Errorf("NewPublicKey(zero Parameters): want error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSize(t *testing.T) {
|
||||
testAllParameters(t, func(t *testing.T, params Parameters) {
|
||||
if _, err := NewPrivateKey(params, make([]byte, PrivateKeySize-1)); err == nil {
|
||||
t.Errorf("NewPrivateKey with short seed: want error, got nil")
|
||||
}
|
||||
if _, err := NewPrivateKey(params, make([]byte, PrivateKeySize+1)); err == nil {
|
||||
t.Errorf("NewPrivateKey with long seed: want error, got nil")
|
||||
}
|
||||
if _, err := NewPublicKey(params, make([]byte, params.PublicKeySize()-1)); err == nil {
|
||||
t.Errorf("NewPublicKey with short encoding: want error, got nil")
|
||||
}
|
||||
if _, err := NewPublicKey(params, make([]byte, params.PublicKeySize()+1)); err == nil {
|
||||
t.Errorf("NewPublicKey with long encoding: want error, got nil")
|
||||
}
|
||||
|
||||
sk, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
msg := []byte("test message")
|
||||
sig, err := sk.SignDeterministic(msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SignDeterministic: %v", err)
|
||||
}
|
||||
if err := Verify(sk.PublicKey(), msg, sig[:len(sig)-1], nil); err == nil {
|
||||
t.Errorf("Verify with short signature: want error, got nil")
|
||||
}
|
||||
if err := Verify(sk.PublicKey(), msg, append(sig, 0), nil); err == nil {
|
||||
t.Errorf("Verify with long signature: want error, got nil")
|
||||
}
|
||||
})
|
||||
|
||||
// Cross-parameter mismatch: an MLDSA65 public key encoding is rejected by
|
||||
// MLDSA44 (and vice versa), because the lengths differ.
|
||||
sk65, err := GenerateKey(MLDSA65())
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey(MLDSA65): %v", err)
|
||||
}
|
||||
if _, err := NewPublicKey(MLDSA44(), sk65.PublicKey().Bytes()); err == nil {
|
||||
t.Errorf("NewPublicKey(MLDSA44, MLDSA65 encoding): want error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSign(b *testing.B) {
|
||||
// Signing works by rejection sampling, which introduces massive variance in
|
||||
// individual signing times. To get stable but correct results, we benchmark
|
||||
// a series of representative operations, engineered to have the same
|
||||
// distribution of rejection counts and reasons as the average case. See also
|
||||
// https://words.filippo.io/rsa-keygen-bench/ for a similar approach.
|
||||
b.Run("ML-DSA-44", func(b *testing.B) {
|
||||
benchmarkMLDSASign(b, NewPrivateKey44, benchmarkMessagesMLDSA44)
|
||||
benchmarkSign(b, MLDSA44(), benchmarkMessagesMLDSA44)
|
||||
})
|
||||
b.Run("ML-DSA-65", func(b *testing.B) {
|
||||
benchmarkMLDSASign(b, NewPrivateKey65, benchmarkMessagesMLDSA65)
|
||||
benchmarkSign(b, MLDSA65(), benchmarkMessagesMLDSA65)
|
||||
})
|
||||
b.Run("ML-DSA-87", func(b *testing.B) {
|
||||
benchmarkMLDSASign(b, NewPrivateKey87, benchmarkMessagesMLDSA87)
|
||||
benchmarkSign(b, MLDSA87(), benchmarkMessagesMLDSA87)
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkMLDSASign(b *testing.B, newPrivateKey func([]byte) (*PrivateKey, error), messages []string) {
|
||||
func benchmarkSign(b *testing.B, params Parameters, messages []string) {
|
||||
seed := make([]byte, 32)
|
||||
priv, err := newPrivateKey(seed)
|
||||
priv, err := NewPrivateKey(params, seed)
|
||||
if err != nil {
|
||||
b.Fatalf("NewPrivateKey: %v", err)
|
||||
}
|
||||
|
|
@ -213,58 +620,77 @@ func benchmarkMLDSASign(b *testing.B, newPrivateKey func([]byte) (*PrivateKey, e
|
|||
if i++; i >= len(messages) {
|
||||
i = 0
|
||||
}
|
||||
SignDeterministic(priv, []byte(msg), "")
|
||||
priv.SignDeterministic([]byte(msg), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMLDSAVerify runs both public key parsing and signature verification,
|
||||
// since pre-computation can be easily moved between the two, but in practice
|
||||
// most uses of verification are for fresh public keys (unlike signing).
|
||||
func BenchmarkMLDSAVerify(b *testing.B) {
|
||||
func BenchmarkVerify(b *testing.B) {
|
||||
b.Run("ML-DSA-44", func(b *testing.B) {
|
||||
benchmarkMLDSAVerify(b, GenerateKey44, NewPublicKey44)
|
||||
benchmarkVerify(b, MLDSA44())
|
||||
})
|
||||
b.Run("ML-DSA-65", func(b *testing.B) {
|
||||
benchmarkMLDSAVerify(b, GenerateKey65, NewPublicKey65)
|
||||
benchmarkVerify(b, MLDSA65())
|
||||
})
|
||||
b.Run("ML-DSA-87", func(b *testing.B) {
|
||||
benchmarkMLDSAVerify(b, GenerateKey87, NewPublicKey87)
|
||||
benchmarkVerify(b, MLDSA87())
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkMLDSAVerify(b *testing.B, generateKey func() *PrivateKey, newPublicKey func([]byte) (*PublicKey, error)) {
|
||||
priv := generateKey()
|
||||
func benchmarkVerify(b *testing.B, params Parameters) {
|
||||
priv, err := GenerateKey(params)
|
||||
if err != nil {
|
||||
b.Fatalf("GenerateKey: %v", err)
|
||||
}
|
||||
msg := make([]byte, 128)
|
||||
sig, err := SignDeterministic(priv, msg, "context")
|
||||
sig, err := priv.SignDeterministic(msg, &Options{Context: "context"})
|
||||
if err != nil {
|
||||
b.Fatalf("SignDeterministic: %v", err)
|
||||
}
|
||||
pub := priv.PublicKey().Bytes()
|
||||
for b.Loop() {
|
||||
pk, err := newPublicKey(pub)
|
||||
|
||||
// "Whole" runs both public key parsing and signature verification,
|
||||
// since pre-computation can be easily moved between the two, but in practice
|
||||
// most uses of verification are for fresh public keys (unlike signing).
|
||||
b.Run("Whole", func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
pk, err := NewPublicKey(params, pub)
|
||||
if err != nil {
|
||||
b.Fatalf("NewPublicKey: %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig, &Options{Context: "context"}); err != nil {
|
||||
b.Fatalf("Verify: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// "Precomputed" runs only Verify with a pre-parsed public key.
|
||||
b.Run("Precomputed", func(b *testing.B) {
|
||||
pk, err := NewPublicKey(params, pub)
|
||||
if err != nil {
|
||||
b.Fatalf("NewPublicKey: %v", err)
|
||||
}
|
||||
if err := Verify(pk, msg, sig, "context"); err != nil {
|
||||
b.Fatalf("Verify: %v", err)
|
||||
for b.Loop() {
|
||||
if err := Verify(pk, msg, sig, &Options{Context: "context"}); err != nil {
|
||||
b.Fatalf("Verify: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMLDSAKeygen(b *testing.B) {
|
||||
func BenchmarkKeygen(b *testing.B) {
|
||||
b.Run("ML-DSA-44", func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
NewPrivateKey44(make([]byte, 32))
|
||||
NewPrivateKey(MLDSA44(), make([]byte, 32))
|
||||
}
|
||||
})
|
||||
b.Run("ML-DSA-65", func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
NewPrivateKey65(make([]byte, 32))
|
||||
NewPrivateKey(MLDSA65(), make([]byte, 32))
|
||||
}
|
||||
})
|
||||
b.Run("ML-DSA-87", func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
NewPrivateKey87(make([]byte, 32))
|
||||
NewPrivateKey(MLDSA87(), make([]byte, 32))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -560,7 +560,8 @@ var depsRules = `
|
|||
crypto/hkdf,
|
||||
crypto/pbkdf2,
|
||||
crypto/ecdh,
|
||||
crypto/mlkem
|
||||
crypto/mlkem,
|
||||
crypto/mldsa
|
||||
< CRYPTO;
|
||||
|
||||
CRYPTO
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue