mirror of
https://github.com/sonr-io/crypto.git
synced 2026-01-12 04:09:13 +00:00
287 lines
7.9 KiB
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|