mldsa,slhdsa: crypto.Signer assertion

This commit is contained in:
Sun Yimin 2025-09-15 09:51:42 +08:00 committed by GitHub
parent fd2eedf24b
commit 2435170a2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 111 additions and 44 deletions

View File

@ -243,3 +243,19 @@ func vectorCountOnes(a []ringElement) int {
}
return oneCount
}
func constantTimeEqualRingElement(a, b ringElement) int {
var res int32
for i := range a {
res |= int32(a[i] ^ b[i])
}
return subtle.ConstantTimeByteEq(byte(res|(-res)>>31), 0)
}
func constantTimeEqualRingElementArray(a, b []ringElement) int {
eq := 1
for i := range a {
eq &= constantTimeEqualRingElement(a[i], b[i])
}
return eq
}

View File

@ -101,6 +101,9 @@ const (
sigEncodedLen87 = lambda256/4 + encodingSize20*l87 + omega75 + k87
)
var _ crypto.Signer = (*PrivateKey44)(nil)
var _ crypto.Signer = (*Key44)(nil)
// A PrivateKey44 is the private key for the ML-DSA-44 signature scheme.
type PrivateKey44 struct {
rho [32]byte // public random seed
@ -118,10 +121,10 @@ type PrivateKey44 struct {
t1Once sync.Once
}
// PublicKey returns the public key corresponding to the private key.
// Public returns the public key corresponding to the private key.
// Although we can derive the public key from the private key,
// but we do NOT need to derive it at most of the time.
func (sk *PrivateKey44) PublicKey() crypto.PublicKey {
func (sk *PrivateKey44) Public() crypto.PublicKey {
sk.ensureT1()
return &PublicKey44{
rho: sk.rho,
@ -187,9 +190,9 @@ type PublicKey44 struct {
nttOnce sync.Once
}
// PublicKey generates and returns the corresponding public key for the given
// Public generates and returns the corresponding public key for the given
// Key44 instance.
func (sk *Key44) PublicKey() *PublicKey44 {
func (sk *Key44) Public() crypto.PublicKey {
return &PublicKey44{
rho: sk.rho,
t1: sk.t1,
@ -210,9 +213,9 @@ func (pk *PublicKey44) Equal(x crypto.PublicKey) bool {
if !ok {
return false
}
b1 := pk.Bytes()
b2 := xx.Bytes()
return subtle.ConstantTimeCompare(b1, b2) == 1
eq := subtle.ConstantTimeCompare(pk.rho[:], xx.rho[:]) &
constantTimeEqualRingElementArray(pk.t1[:], xx.t1[:])
return eq == 1
}
// Bytes converts the PublicKey44 instance into a byte slice.
@ -271,9 +274,13 @@ func (sk *PrivateKey44) Equal(x any) bool {
if !ok {
return false
}
b1 := sk.Bytes()
b2 := xx.Bytes()
return subtle.ConstantTimeCompare(b1, b2) == 1
eq := subtle.ConstantTimeCompare(sk.rho[:], xx.rho[:]) &
subtle.ConstantTimeCompare(sk.k[:], xx.k[:]) &
subtle.ConstantTimeCompare(sk.tr[:], xx.tr[:]) &
constantTimeEqualRingElementArray(sk.s1[:], xx.s1[:]) &
constantTimeEqualRingElementArray(sk.s2[:], xx.s2[:]) &
constantTimeEqualRingElementArray(sk.t0[:], xx.t0[:])
return eq == 1
}
// GenerateKey44 generates a new Key44 (ML-DSA-44) using the provided random source.
@ -363,7 +370,7 @@ func dsaKeyGen44(sk *Key44, xi *[32]byte) {
}
}
H.Reset()
ek := sk.PublicKey().Bytes()
ek := sk.Public().(*PublicKey44).Bytes()
H.Write(ek)
H.Read(sk.tr[:])
}

View File

@ -46,7 +46,7 @@ func TestKeyGen44(t *testing.T) {
if err != nil {
t.Fatalf("NewPrivateKey44 failed: %v", err)
}
pub := priv.PublicKey()
pub := priv.Public().(*PublicKey44)
pubBytes := pub.Bytes()
if !bytes.Equal(pubBytes, pk) {
t.Errorf("Public key mismatch: got %x, want %x", pubBytes, pk)
@ -70,7 +70,7 @@ func TestKeyGen44(t *testing.T) {
if !priv.Equal(priv2) {
t.Errorf("Private key not equal: got %x, want %x", privBytes, priv2.Bytes())
}
pub3 := priv2.PublicKey()
pub3 := priv2.Public()
if !pub.Equal(pub3) {
t.Errorf("Public key from private key not equal")
}
@ -127,6 +127,7 @@ func TestSign44(t *testing.T) {
if err != nil {
t.Fatalf("NewPrivateKey44 failed: %v", err)
}
sig2, err := priv.signInternal(seed[:], mu)
if err != nil {
t.Fatalf("failed to sign: %v", err)

View File

@ -17,6 +17,9 @@ import (
"sync"
)
var _ crypto.Signer = (*PrivateKey65)(nil)
var _ crypto.Signer = (*Key65)(nil)
// A PrivateKey65 is the private key for the ML-DSA-65 signature scheme.
type PrivateKey65 struct {
rho [32]byte // public random seed
@ -34,10 +37,10 @@ type PrivateKey65 struct {
t1Once sync.Once
}
// PublicKey returns the public key corresponding to the private key.
// Public returns the public key corresponding to the private key.
// Although we can derive the public key from the private key,
// but we do NOT need to derive it at most of the time.
func (sk *PrivateKey65) PublicKey() crypto.PublicKey {
func (sk *PrivateKey65) Public() crypto.PublicKey {
sk.ensureT1()
return &PublicKey65{
rho: sk.rho,
@ -103,9 +106,9 @@ type PublicKey65 struct {
nttOnce sync.Once
}
// PublicKey generates and returns the corresponding public key for the given
// Public generates and returns the corresponding public key for the given
// Key65 instance.
func (sk *Key65) PublicKey() *PublicKey65 {
func (sk *Key65) Public() crypto.PublicKey {
return &PublicKey65{
rho: sk.rho,
t1: sk.t1,
@ -126,9 +129,9 @@ func (pk *PublicKey65) Equal(x crypto.PublicKey) bool {
if !ok {
return false
}
b1 := pk.Bytes()
b2 := xx.Bytes()
return subtle.ConstantTimeCompare(b1, b2) == 1
eq := subtle.ConstantTimeCompare(pk.rho[:], xx.rho[:]) &
constantTimeEqualRingElementArray(pk.t1[:], xx.t1[:])
return eq == 1
}
// Bytes converts the PublicKey65 instance into a byte slice.
@ -187,9 +190,13 @@ func (sk *PrivateKey65) Equal(x any) bool {
if !ok {
return false
}
b1 := sk.Bytes()
b2 := xx.Bytes()
return subtle.ConstantTimeCompare(b1, b2) == 1
eq := subtle.ConstantTimeCompare(sk.rho[:], xx.rho[:]) &
subtle.ConstantTimeCompare(sk.k[:], xx.k[:]) &
subtle.ConstantTimeCompare(sk.tr[:], xx.tr[:]) &
constantTimeEqualRingElementArray(sk.s1[:], xx.s1[:]) &
constantTimeEqualRingElementArray(sk.s2[:], xx.s2[:]) &
constantTimeEqualRingElementArray(sk.t0[:], xx.t0[:])
return eq == 1
}
// GenerateKey65 generates a new Key65 (ML-DSA-65) using the provided random source.
@ -279,7 +286,7 @@ func dsaKeyGen65(sk *Key65, xi *[32]byte) {
}
}
H.Reset()
ek := sk.PublicKey().Bytes()
ek := sk.Public().(*PublicKey65).Bytes()
H.Write(ek)
H.Read(sk.tr[:])
}

View File

@ -46,7 +46,7 @@ func TestKeyGen65(t *testing.T) {
if err != nil {
t.Fatalf("NewPrivateKey65 failed: %v", err)
}
pub := priv.PublicKey()
pub := priv.Public().(*PublicKey65)
pubBytes := pub.Bytes()
if !bytes.Equal(pubBytes, pk) {
t.Errorf("Public key mismatch: got %x, want %x", pubBytes, pk)
@ -70,7 +70,7 @@ func TestKeyGen65(t *testing.T) {
if !priv.Equal(priv2) {
t.Errorf("Private key not equal: got %x, want %x", privBytes, priv2.Bytes())
}
pub3 := priv2.PublicKey()
pub3 := priv2.Public()
if !pub.Equal(pub3) {
t.Errorf("Public key from private key not equal")
}

View File

@ -17,6 +17,9 @@ import (
"sync"
)
var _ crypto.Signer = (*PrivateKey87)(nil)
var _ crypto.Signer = (*Key87)(nil)
// A PrivateKey87 is the private key for the ML-DSA-87 signature scheme.
type PrivateKey87 struct {
rho [32]byte // public random seed
@ -34,10 +37,10 @@ type PrivateKey87 struct {
t1Once sync.Once
}
// PublicKey returns the public key corresponding to the private key.
// Public returns the public key corresponding to the private key.
// Although we can derive the public key from the private key,
// but we do NOT need to derive it at most of the time.
func (sk *PrivateKey87) PublicKey() crypto.PublicKey {
func (sk *PrivateKey87) Public() crypto.PublicKey {
sk.ensureT1()
return &PublicKey87{
rho: sk.rho,
@ -103,9 +106,9 @@ type PublicKey87 struct {
nttOnce sync.Once
}
// PublicKey generates and returns the corresponding public key for the given
// Public generates and returns the corresponding public key for the given
// Key87 instance.
func (sk *Key87) PublicKey() *PublicKey87 {
func (sk *Key87) Public() crypto.PublicKey {
return &PublicKey87{
rho: sk.rho,
t1: sk.t1,
@ -126,9 +129,9 @@ func (pk *PublicKey87) Equal(x crypto.PublicKey) bool {
if !ok {
return false
}
b1 := pk.Bytes()
b2 := xx.Bytes()
return subtle.ConstantTimeCompare(b1, b2) == 1
eq := subtle.ConstantTimeCompare(pk.rho[:], xx.rho[:]) &
constantTimeEqualRingElementArray(pk.t1[:], xx.t1[:])
return eq == 1
}
// Bytes converts the PublicKey87 instance into a byte slice.
@ -187,9 +190,13 @@ func (sk *PrivateKey87) Equal(x any) bool {
if !ok {
return false
}
b1 := sk.Bytes()
b2 := xx.Bytes()
return subtle.ConstantTimeCompare(b1, b2) == 1
eq := subtle.ConstantTimeCompare(sk.rho[:], xx.rho[:]) &
subtle.ConstantTimeCompare(sk.k[:], xx.k[:]) &
subtle.ConstantTimeCompare(sk.tr[:], xx.tr[:]) &
constantTimeEqualRingElementArray(sk.s1[:], xx.s1[:]) &
constantTimeEqualRingElementArray(sk.s2[:], xx.s2[:]) &
constantTimeEqualRingElementArray(sk.t0[:], xx.t0[:])
return eq == 1
}
// GenerateKey87 generates a new Key87 (ML-DSA-87) using the provided random source.
@ -279,7 +286,7 @@ func dsaKeyGen87(sk *Key87, xi *[32]byte) {
}
}
H.Reset()
ek := sk.PublicKey().Bytes()
ek := sk.Public().(*PublicKey87).Bytes()
H.Write(ek)
H.Read(sk.tr[:])
}

View File

@ -46,7 +46,7 @@ func TestKeyGen87(t *testing.T) {
if err != nil {
t.Fatalf("NewPrivateKey65 failed: %v", err)
}
pub := priv.PublicKey()
pub := priv.Public().(*PublicKey87)
pubBytes := pub.Bytes()
if !bytes.Equal(pubBytes, pk) {
t.Errorf("Public key mismatch: got %x, want %x", pubBytes, pk)
@ -70,7 +70,7 @@ func TestKeyGen87(t *testing.T) {
if !priv.Equal(priv2) {
t.Errorf("Private key not equal: got %x, want %x", privBytes, priv2.Bytes())
}
pub3 := priv2.PublicKey()
pub3 := priv2.Public()
if !pub.Equal(pub3) {
t.Errorf("Public key from private key not equal")
}

View File

@ -7,18 +7,42 @@
package slhdsa
import (
"crypto"
"errors"
"io"
)
var _ crypto.Signer = (*PrivateKey)(nil)
type Options struct {
Context []byte
AddRand []byte // optional randomness to be added to the signature. If nil, the signature is deterministic.
}
func (opts *Options) HashFunc() crypto.Hash {
return crypto.Hash(0)
}
// Sign produces a signature of the message using the private key.
// It is a wrapper around the SignMessage method, implementing the crypto.Signer interface.
func (sk *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
return sk.SignMessage(rand, message, opts)
}
// Sign generates a pure SLH-DSA signature for the given message.
// The signature is deterministic if the addRand parameter is nil.
// If addRand is not nil, it must be of the same length as n.
//
// See FIPS 205 Algorithm 22 slh_sign
func (sk *PrivateKey) Sign(message, context, addRand []byte) ([]byte, error) {
func (sk *PrivateKey) SignMessage(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
if len(message) == 0 {
return nil, errors.New("slhdsa: empty message")
}
var context, addRand []byte
if opts, ok := opts.(*Options); ok {
context = opts.Context
addRand = opts.AddRand
}
if len(addRand) > 0 && len(addRand) != int(sk.params.n) {
return nil, errors.New("slhdsa: addrnd should be nil (deterministic variant) or of length n")
}
@ -85,10 +109,14 @@ func (sk *PrivateKey) signInternal(msgPrefix, message, addRand []byte) ([]byte,
// Verify verifies a pure SLH-DSA signature for the given message.
//
// See FIPS 205 Algorithm 24 slh_verify
func (pk *PublicKey) Verify(signature, message, context []byte) bool {
func (pk *PublicKey) VerifyWithOptions(signature, message []byte, opts crypto.SignerOpts) bool {
if len(message) == 0 {
return false
}
var context []byte
if opts, ok := opts.(*Options); ok {
context = opts.Context
}
if len(context) > maxContextLen {
return false
}

View File

@ -75,7 +75,7 @@ func testData(t *testing.T, filename string, tc *slhtest) {
if err != nil {
t.Fatalf("%v NewPrivateKey(%x) = %v", filename, skBytes, err)
}
sig2, err := privKey.Sign(message, context, addRand)
sig2, err := privKey.Sign(nil, message, &Options{context, addRand})
if err != nil {
t.Fatalf("%v Sign(%x,%x) = %v", filename, message, context, err)
}
@ -104,7 +104,7 @@ func testData(t *testing.T, filename string, tc *slhtest) {
if err != nil {
t.Fatalf("%v NewPublicKey(%x) = %v", filename, pkBytes, err)
}
if !pub.Verify(sigOriginal, message, context) {
if !pub.VerifyWithOptions(sigOriginal, message, &Options{Context: context}) {
t.Errorf("%v Verify() = false, want true", filename)
}
}

View File

@ -7,6 +7,7 @@
package slhdsa
import (
"crypto"
"crypto/sha256"
"crypto/sha3"
"crypto/sha512"
@ -69,7 +70,7 @@ func (sk *PrivateKey) Bytes() []byte {
}
// Public returns the public key of the private key.
func (sk *PrivateKey) Public() *PublicKey {
func (sk *PrivateKey) Public() crypto.PublicKey {
return &sk.PublicKey
}