Files
crypto/ecdsa/deterministic_test.go

287 lines
7.9 KiB
Go

package ecdsa
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDeterministicSign(t *testing.T) {
// Generate test key
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
// Test message
message := []byte("test message for deterministic signing")
hash := sha256.Sum256(message)
// Sign with deterministic algorithm
r1, s1, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
assert.NotNil(t, r1)
assert.NotNil(t, s1)
// Sign again - should produce identical signature
r2, s2, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
assert.Equal(t, r1, r2, "deterministic signatures should be identical")
assert.Equal(t, s1, s2, "deterministic signatures should be identical")
// Verify signature
valid := ecdsa.Verify(&priv.PublicKey, hash[:], r1, s1)
assert.True(t, valid, "signature should be valid")
// Different message should produce different signature
message2 := []byte("different message")
hash2 := sha256.Sum256(message2)
r3, s3, err := DeterministicSign(priv, hash2[:])
require.NoError(t, err)
assert.NotEqual(t, r1, r3, "different messages should produce different signatures")
// Also verify the s component is different
assert.NotEqual(t, s1, s3, "different messages should produce different s values")
}
func TestCanonicalSignature(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
message := []byte("test canonical signature")
hash := sha256.Sum256(message)
// Sign multiple times and check all signatures are canonical
for i := 0; i < 10; i++ {
r, s, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
// Check signature is canonical (s <= N/2)
N := priv.Curve.Params().N
assert.True(t, IsCanonical(s, N), "signature should be canonical")
// Verify signature
valid := ecdsa.Verify(&priv.PublicKey, hash[:], r, s)
assert.True(t, valid, "canonical signature should be valid")
}
}
func TestMakeCanonical(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
// Test with non-canonical s (s > N/2)
r := big.NewInt(12345)
s := new(big.Int).Add(halfN, big.NewInt(1)) // s = N/2 + 1
assert.False(t, IsCanonical(s, N), "s > N/2 should not be canonical")
// Make canonical
rCanon, sCanon := MakeCanonical(r, s, N)
assert.Equal(t, r, rCanon, "r should not change")
assert.True(t, IsCanonical(sCanon, N), "canonicalized s should be <= N/2")
// sCanon should equal N - s
expected := new(big.Int).Sub(N, s)
assert.Equal(t, expected, sCanon, "canonical s should be N - s")
}
func TestVerifyDeterministic(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
message := []byte("test verification")
hash := sha256.Sum256(message)
// Create deterministic signature
r, s, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
// Verify with our function
valid := VerifyDeterministic(&priv.PublicKey, hash[:], r, s)
assert.True(t, valid, "signature should verify")
// Test with non-canonical signature (should fail)
N := priv.Curve.Params().N
sNonCanon := new(big.Int).Sub(N, s) // Create non-canonical s
valid = VerifyDeterministic(&priv.PublicKey, hash[:], r, sNonCanon)
assert.False(t, valid, "non-canonical signature should not verify")
// Test with wrong hash
wrongHash := sha256.Sum256([]byte("wrong message"))
valid = VerifyDeterministic(&priv.PublicKey, wrongHash[:], r, s)
assert.False(t, valid, "signature with wrong hash should not verify")
}
func TestInvalidInputs(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
hash := sha256.Sum256([]byte("test"))
// Test with nil private key
r, s, err := DeterministicSign(nil, hash[:])
assert.Error(t, err)
assert.Nil(t, r)
assert.Nil(t, s)
// Test with empty hash
r, s, err = DeterministicSign(priv, []byte{})
assert.Error(t, err)
assert.Nil(t, r)
assert.Nil(t, s)
// Test verify with nil inputs
assert.False(t, VerifyDeterministic(nil, hash[:], big.NewInt(1), big.NewInt(1)))
assert.False(t, VerifyDeterministic(&priv.PublicKey, hash[:], nil, big.NewInt(1)))
assert.False(t, VerifyDeterministic(&priv.PublicKey, hash[:], big.NewInt(1), nil))
}
func TestDifferentCurves(t *testing.T) {
curves := []elliptic.Curve{
elliptic.P224(),
elliptic.P256(),
elliptic.P384(),
elliptic.P521(),
}
message := []byte("test message for different curves")
hash := sha256.Sum256(message)
for _, curve := range curves {
t.Run(curve.Params().Name, func(t *testing.T) {
priv, err := ecdsa.GenerateKey(curve, rand.Reader)
require.NoError(t, err)
// Sign deterministically
r, s, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
// Verify signature is canonical
N := curve.Params().N
assert.True(t, IsCanonical(s, N))
// Verify signature
valid := ecdsa.Verify(&priv.PublicKey, hash[:], r, s)
assert.True(t, valid)
// Verify deterministic property
r2, s2, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
assert.Equal(t, r, r2)
assert.Equal(t, s, s2)
})
}
}
// TestRFC6979Vectors tests against known test vectors
// These are simplified vectors - in production, use the full RFC 6979 test vectors
func TestRFC6979Vectors(t *testing.T) {
// Test vector for P-256 with SHA-256
// This is a simplified example - real implementation should use official test vectors
privKeyHex := "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"
messageHex := "73616d706c65" // "sample"
privKeyBytes, err := hex.DecodeString(privKeyHex)
require.NoError(t, err)
message, err := hex.DecodeString(messageHex)
require.NoError(t, err)
// Create private key
priv := new(ecdsa.PrivateKey)
priv.Curve = elliptic.P256()
priv.D = new(big.Int).SetBytes(privKeyBytes)
priv.PublicKey.Curve = priv.Curve
priv.PublicKey.X, priv.PublicKey.Y = priv.Curve.ScalarBaseMult(privKeyBytes)
// Hash message
hash := sha256.Sum256(message)
// Sign deterministically
r, s, err := DeterministicSign(priv, hash[:])
require.NoError(t, err)
assert.NotNil(t, r)
assert.NotNil(t, s)
// Verify signature
valid := ecdsa.Verify(&priv.PublicKey, hash[:], r, s)
assert.True(t, valid, "RFC 6979 test vector signature should verify")
}
func BenchmarkDeterministicSign(b *testing.B) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(b, err)
message := []byte("benchmark message")
hash := sha256.Sum256(message)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = DeterministicSign(priv, hash[:])
}
}
func BenchmarkVerifyDeterministic(b *testing.B) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(b, err)
message := []byte("benchmark message")
hash := sha256.Sum256(message)
r, s, err := DeterministicSign(priv, hash[:])
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = VerifyDeterministic(&priv.PublicKey, hash[:], r, s)
}
}
func TestConcurrentSigning(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
message := []byte("concurrent test")
hash := sha256.Sum256(message)
// Sign concurrently
const goroutines = 10
results := make(chan struct {
r, s *big.Int
err error
}, goroutines)
for i := 0; i < goroutines; i++ {
go func() {
r, s, err := DeterministicSign(priv, hash[:])
results <- struct {
r, s *big.Int
err error
}{r, s, err}
}()
}
// Collect results
var firstR, firstS *big.Int
for i := 0; i < goroutines; i++ {
result := <-results
require.NoError(t, result.err)
if i == 0 {
firstR, firstS = result.r, result.s
} else {
// All signatures should be identical (deterministic)
assert.Equal(t, firstR, result.r)
assert.Equal(t, firstS, result.s)
}
}
}