diff --git a/mldsa/field.go b/mldsa/field.go index 3a9e70f..759269f 100644 --- a/mldsa/field.go +++ b/mldsa/field.go @@ -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 +} diff --git a/mldsa/mldsa44.go b/mldsa/mldsa44.go index f1eafec..34840c3 100644 --- a/mldsa/mldsa44.go +++ b/mldsa/mldsa44.go @@ -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[:]) } diff --git a/mldsa/mldsa44_test.go b/mldsa/mldsa44_test.go index a7dbbc7..5057f98 100644 --- a/mldsa/mldsa44_test.go +++ b/mldsa/mldsa44_test.go @@ -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) diff --git a/mldsa/mldsa65.go b/mldsa/mldsa65.go index 011283c..6222699 100644 --- a/mldsa/mldsa65.go +++ b/mldsa/mldsa65.go @@ -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[:]) } diff --git a/mldsa/mldsa65_test.go b/mldsa/mldsa65_test.go index ba72760..3355aca 100644 --- a/mldsa/mldsa65_test.go +++ b/mldsa/mldsa65_test.go @@ -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") } diff --git a/mldsa/mldsa87.go b/mldsa/mldsa87.go index c1e5747..d5edae9 100644 --- a/mldsa/mldsa87.go +++ b/mldsa/mldsa87.go @@ -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[:]) } diff --git a/mldsa/mldsa87_test.go b/mldsa/mldsa87_test.go index df2d862..4dce505 100644 --- a/mldsa/mldsa87_test.go +++ b/mldsa/mldsa87_test.go @@ -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") } diff --git a/slhdsa/dsa.go b/slhdsa/dsa.go index b011a16..d8766f8 100644 --- a/slhdsa/dsa.go +++ b/slhdsa/dsa.go @@ -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 } diff --git a/slhdsa/dsa_test.go b/slhdsa/dsa_test.go index 83a658b..5852b59 100644 --- a/slhdsa/dsa_test.go +++ b/slhdsa/dsa_test.go @@ -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) } } diff --git a/slhdsa/key.go b/slhdsa/key.go index 07d2855..b6dc20c 100644 --- a/slhdsa/key.go +++ b/slhdsa/key.go @@ -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 }