No commit suggestions generated

This commit is contained in:
Prad Nukala
2025-10-09 15:10:39 -04:00
commit a934caa7d3
323 changed files with 98121 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
// Package ecdsa provides ECDSA signature canonicalization
package ecdsa
import (
"crypto/ecdsa"
"crypto/elliptic"
"fmt"
"math/big"
)
// CanonicalizeSignature ensures ECDSA signature is in canonical form
// This prevents signature malleability attacks where (r, s) and (r, -s mod N) are both valid
func CanonicalizeSignature(r, s *big.Int, curve elliptic.Curve) (*big.Int, *big.Int, error) {
if r == nil || s == nil {
return nil, nil, fmt.Errorf("r and s cannot be nil")
}
if curve == nil {
return nil, nil, fmt.Errorf("curve cannot be nil")
}
N := curve.Params().N
if N == nil {
return nil, nil, fmt.Errorf("invalid curve parameters")
}
// Create copies to avoid modifying originals
rCopy := new(big.Int).Set(r)
sCopy := new(big.Int).Set(s)
// Check if r is in valid range [1, N-1]
if rCopy.Sign() <= 0 || rCopy.Cmp(N) >= 0 {
return nil, nil, fmt.Errorf("r is not in valid range [1, N-1]")
}
// Check if s is in valid range [1, N-1]
if sCopy.Sign() <= 0 || sCopy.Cmp(N) >= 0 {
return nil, nil, fmt.Errorf("s is not in valid range [1, N-1]")
}
// Ensure s is canonical (s <= N/2)
halfN := new(big.Int).Div(N, big.NewInt(2))
if sCopy.Cmp(halfN) > 0 {
// Use N - s to get canonical form
sCopy.Sub(N, sCopy)
}
return rCopy, sCopy, nil
}
// IsSignatureCanonical checks if an ECDSA signature is in canonical form
func IsSignatureCanonical(r, s *big.Int, curve elliptic.Curve) bool {
if r == nil || s == nil || curve == nil {
return false
}
N := curve.Params().N
if N == nil {
return false
}
// Check r is in valid range [1, N-1]
if r.Sign() <= 0 || r.Cmp(N) >= 0 {
return false
}
// Check s is in valid range [1, N/2]
halfN := new(big.Int).Div(N, big.NewInt(2))
if s.Sign() <= 0 || s.Cmp(halfN) > 0 {
return false
}
return true
}
// ValidateAndCanonicalizeSignature validates and canonicalizes an ECDSA signature
func ValidateAndCanonicalizeSignature(pub *ecdsa.PublicKey, hash []byte, r, s *big.Int) (*big.Int, *big.Int, error) {
if pub == nil {
return nil, nil, fmt.Errorf("public key cannot be nil")
}
if len(hash) == 0 {
return nil, nil, fmt.Errorf("hash cannot be empty")
}
// Canonicalize the signature
rCanon, sCanon, err := CanonicalizeSignature(r, s, pub.Curve)
if err != nil {
return nil, nil, fmt.Errorf("failed to canonicalize signature: %w", err)
}
// Verify the canonical signature
if !ecdsa.Verify(pub, hash, rCanon, sCanon) {
// If canonical signature doesn't verify, try the original
// This handles the case where the signature was already canonical but negated
if !ecdsa.Verify(pub, hash, r, s) {
return nil, nil, fmt.Errorf("signature verification failed")
}
// Original verified, return it canonicalized
return CanonicalizeSignature(r, s, pub.Curve)
}
return rCanon, sCanon, nil
}
// RejectNonCanonical rejects non-canonical signatures outright
// This is stricter than canonicalization and prevents accepting malleable signatures
func RejectNonCanonical(r, s *big.Int, curve elliptic.Curve) error {
if r == nil || s == nil {
return fmt.Errorf("r and s cannot be nil")
}
if curve == nil {
return fmt.Errorf("curve cannot be nil")
}
if !IsSignatureCanonical(r, s, curve) {
return fmt.Errorf("signature is not in canonical form")
}
return nil
}
// NormalizeSignature normalizes an ECDSA signature to ensure consistent representation
// This is useful for signature aggregation and comparison
func NormalizeSignature(r, s *big.Int, curve elliptic.Curve) (*big.Int, *big.Int, error) {
// First canonicalize
rNorm, sNorm, err := CanonicalizeSignature(r, s, curve)
if err != nil {
return nil, nil, err
}
// Additional normalization can be added here if needed
// For example, ensuring consistent byte representation
return rNorm, sNorm, nil
}
// CompareSignatures compares two ECDSA signatures for equality after canonicalization
func CompareSignatures(r1, s1, r2, s2 *big.Int, curve elliptic.Curve) (bool, error) {
// Canonicalize both signatures
r1Canon, s1Canon, err := CanonicalizeSignature(r1, s1, curve)
if err != nil {
return false, fmt.Errorf("failed to canonicalize first signature: %w", err)
}
r2Canon, s2Canon, err := CanonicalizeSignature(r2, s2, curve)
if err != nil {
return false, fmt.Errorf("failed to canonicalize second signature: %w", err)
}
// Compare canonical forms
return r1Canon.Cmp(r2Canon) == 0 && s1Canon.Cmp(s2Canon) == 0, nil
}
// SignatureBytes converts signature to bytes in canonical form
// Returns 64 bytes for P-256 (32 bytes for r, 32 bytes for s)
func SignatureBytes(r, s *big.Int, curve elliptic.Curve) ([]byte, error) {
// Canonicalize first
rCanon, sCanon, err := CanonicalizeSignature(r, s, curve)
if err != nil {
return nil, err
}
// Get the byte size for the curve
byteSize := (curve.Params().BitSize + 7) / 8
// Convert to bytes with proper padding
rBytes := rCanon.Bytes()
sBytes := sCanon.Bytes()
// Pad if necessary
signature := make([]byte, 2*byteSize)
copy(signature[byteSize-len(rBytes):byteSize], rBytes)
copy(signature[2*byteSize-len(sBytes):], sBytes)
return signature, nil
}
// SignatureFromBytes reconstructs signature from bytes and ensures it's canonical
func SignatureFromBytes(sig []byte, curve elliptic.Curve) (*big.Int, *big.Int, error) {
byteSize := (curve.Params().BitSize + 7) / 8
if len(sig) != 2*byteSize {
return nil, nil, fmt.Errorf("invalid signature length: expected %d, got %d", 2*byteSize, len(sig))
}
r := new(big.Int).SetBytes(sig[:byteSize])
s := new(big.Int).SetBytes(sig[byteSize:])
// Ensure canonical form
return CanonicalizeSignature(r, s, curve)
}
+276
View File
@@ -0,0 +1,276 @@
package ecdsa
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCanonicalizeSignature(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
// Test canonical signature (s <= N/2)
r := big.NewInt(12345)
s := new(big.Int).Sub(halfN, big.NewInt(1)) // s = N/2 - 1
rCanon, sCanon, err := CanonicalizeSignature(r, s, curve)
require.NoError(t, err)
assert.Equal(t, r, rCanon)
assert.Equal(t, s, sCanon)
// Test non-canonical signature (s > N/2)
sNonCanon := new(big.Int).Add(halfN, big.NewInt(1)) // s = N/2 + 1
rCanon, sCanon, err = CanonicalizeSignature(r, sNonCanon, curve)
require.NoError(t, err)
assert.Equal(t, r, rCanon)
// sCanon should be N - sNonCanon
expected := new(big.Int).Sub(N, sNonCanon)
assert.Equal(t, expected, sCanon)
}
func TestIsSignatureCanonical(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
// Test canonical signature
r := big.NewInt(12345)
s := halfN // s = N/2 (boundary case, still canonical)
assert.True(t, IsSignatureCanonical(r, s, curve))
// Test non-canonical signature
sNonCanon := new(big.Int).Add(halfN, big.NewInt(1))
assert.False(t, IsSignatureCanonical(r, sNonCanon, curve))
// Test invalid r (r = 0)
assert.False(t, IsSignatureCanonical(big.NewInt(0), s, curve))
// Test invalid r (r >= N)
assert.False(t, IsSignatureCanonical(N, s, curve))
// Test invalid s (s = 0)
assert.False(t, IsSignatureCanonical(r, big.NewInt(0), curve))
// Test nil inputs
assert.False(t, IsSignatureCanonical(nil, s, curve))
assert.False(t, IsSignatureCanonical(r, nil, curve))
assert.False(t, IsSignatureCanonical(r, s, nil))
}
func TestValidateAndCanonicalizeSignature(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
message := []byte("test message")
hash := sha256.Sum256(message)
// Sign with standard ECDSA
r, s, err := ecdsa.Sign(rand.Reader, priv, hash[:])
require.NoError(t, err)
// Validate and canonicalize
rCanon, sCanon, err := ValidateAndCanonicalizeSignature(&priv.PublicKey, hash[:], r, s)
require.NoError(t, err)
// Verify canonical signature
valid := ecdsa.Verify(&priv.PublicKey, hash[:], rCanon, sCanon)
assert.True(t, valid)
// Ensure signature is canonical
assert.True(t, IsSignatureCanonical(rCanon, sCanon, priv.Curve))
// Test with invalid signature
wrongR := new(big.Int).Add(r, big.NewInt(1))
_, _, err = ValidateAndCanonicalizeSignature(&priv.PublicKey, hash[:], wrongR, s)
assert.Error(t, err)
}
func TestRejectNonCanonical(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
r := big.NewInt(12345)
// Test canonical signature
sCanon := new(big.Int).Sub(halfN, big.NewInt(1))
err := RejectNonCanonical(r, sCanon, curve)
assert.NoError(t, err)
// Test non-canonical signature
sNonCanon := new(big.Int).Add(halfN, big.NewInt(1))
err = RejectNonCanonical(r, sNonCanon, curve)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not in canonical form")
// Test nil inputs
err = RejectNonCanonical(nil, sCanon, curve)
assert.Error(t, err)
err = RejectNonCanonical(r, nil, curve)
assert.Error(t, err)
err = RejectNonCanonical(r, sCanon, nil)
assert.Error(t, err)
}
func TestCompareSignatures(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
r := big.NewInt(12345)
s1 := new(big.Int).Sub(halfN, big.NewInt(1))
// Same signature should be equal
equal, err := CompareSignatures(r, s1, r, s1, curve)
require.NoError(t, err)
assert.True(t, equal)
// Canonical and non-canonical versions of same signature should be equal
s1NonCanon := new(big.Int).Sub(N, s1)
equal, err = CompareSignatures(r, s1, r, s1NonCanon, curve)
require.NoError(t, err)
assert.True(t, equal)
// Different signatures should not be equal
s2 := new(big.Int).Sub(halfN, big.NewInt(10))
equal, err = CompareSignatures(r, s1, r, s2, curve)
require.NoError(t, err)
assert.False(t, equal)
}
func TestSignatureBytes(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
r := big.NewInt(12345)
s := new(big.Int).Sub(halfN, big.NewInt(1))
// Convert to bytes
sigBytes, err := SignatureBytes(r, s, curve)
require.NoError(t, err)
assert.Len(t, sigBytes, 64) // 32 bytes for r, 32 bytes for s on P-256
// Test with non-canonical s - should be canonicalized
sNonCanon := new(big.Int).Sub(N, s)
sigBytesNonCanon, err := SignatureBytes(r, sNonCanon, curve)
require.NoError(t, err)
// Both should produce the same bytes (after canonicalization)
assert.Equal(t, sigBytes, sigBytesNonCanon)
}
func TestSignatureFromBytes(t *testing.T) {
curve := elliptic.P256()
// Create a signature
r := big.NewInt(12345)
s := big.NewInt(67890)
// Convert to bytes
sigBytes, err := SignatureBytes(r, s, curve)
require.NoError(t, err)
// Convert back from bytes
rRecovered, sRecovered, err := SignatureFromBytes(sigBytes, curve)
require.NoError(t, err)
// Should be canonical
assert.True(t, IsSignatureCanonical(rRecovered, sRecovered, curve))
// Values should match (after canonicalization)
rCanon, sCanon, err := CanonicalizeSignature(r, s, curve)
require.NoError(t, err)
assert.Equal(t, rCanon, rRecovered)
assert.Equal(t, sCanon, sRecovered)
// Test with invalid length
_, _, err = SignatureFromBytes([]byte("too short"), curve)
assert.Error(t, err)
}
func TestNormalizeSignature(t *testing.T) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
r := big.NewInt(12345)
s := new(big.Int).Add(halfN, big.NewInt(1)) // Non-canonical
// Normalize should canonicalize
rNorm, sNorm, err := NormalizeSignature(r, s, curve)
require.NoError(t, err)
assert.True(t, IsSignatureCanonical(rNorm, sNorm, curve))
assert.Equal(t, r, rNorm)
// sNorm should be N - s
expected := new(big.Int).Sub(N, s)
assert.Equal(t, expected, sNorm)
}
func TestCanonicalWithRealSignatures(t *testing.T) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
message := []byte("test canonical signatures")
hash := sha256.Sum256(message)
// Generate multiple signatures and ensure all can be canonicalized
for i := 0; i < 10; i++ {
r, s, err := ecdsa.Sign(rand.Reader, priv, hash[:])
require.NoError(t, err)
// Canonicalize
rCanon, sCanon, err := CanonicalizeSignature(r, s, priv.Curve)
require.NoError(t, err)
// Should be canonical
assert.True(t, IsSignatureCanonical(rCanon, sCanon, priv.Curve))
// Should still verify
valid := ecdsa.Verify(&priv.PublicKey, hash[:], rCanon, sCanon)
assert.True(t, valid)
}
}
func BenchmarkCanonicalizeSignature(b *testing.B) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
r := big.NewInt(12345)
s := new(big.Int).Add(halfN, big.NewInt(1))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = CanonicalizeSignature(r, s, curve)
}
}
func BenchmarkIsSignatureCanonical(b *testing.B) {
curve := elliptic.P256()
N := curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
r := big.NewInt(12345)
s := new(big.Int).Sub(halfN, big.NewInt(1))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = IsSignatureCanonical(r, s, curve)
}
}
+224
View File
@@ -0,0 +1,224 @@
// Package ecdsa provides RFC 6979 deterministic ECDSA implementation
package ecdsa
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
"crypto/sha256"
"fmt"
"hash"
"math/big"
)
// DeterministicSign implements RFC 6979 deterministic ECDSA signing
// This prevents nonce reuse and bias attacks by generating k deterministically
func DeterministicSign(priv *ecdsa.PrivateKey, hash []byte) (*big.Int, *big.Int, error) {
if priv == nil || priv.D == nil {
return nil, nil, fmt.Errorf("invalid private key")
}
if len(hash) == 0 {
return nil, nil, fmt.Errorf("hash cannot be empty")
}
// Generate deterministic k using RFC 6979
k := generateK(priv, hash, sha256.New)
// Sign with deterministic k
return signWithK(priv, hash, k)
}
// generateK implements RFC 6979 deterministic nonce generation
func generateK(priv *ecdsa.PrivateKey, hash []byte, hashFunc func() hash.Hash) *big.Int {
curve := priv.Curve
N := curve.Params().N
bitSize := N.BitLen()
byteSize := (bitSize + 7) / 8
// Step a: Process hash
h1 := hashToInt(hash, curve)
// Step b: Convert private key to bytes
x := priv.D.Bytes()
if len(x) < byteSize {
// Pad with zeros on the left
padding := make([]byte, byteSize-len(x))
x = append(padding, x...)
}
// Step c: Create HMAC-DRBG instance
hm := hmac.New(hashFunc, nil)
hlen := hm.Size()
// Step d: Set V = 0x01 0x01 0x01 ... 0x01
v := bytes(hlen, 0x01)
// Step e: Set K = 0x00 0x00 0x00 ... 0x00
k := bytes(hlen, 0x00)
// Step f: K = HMAC_K(V || 0x00 || x || h1)
k = hmacCompute(hashFunc, k, v, []byte{0x00}, x, h1.Bytes())
// Step g: V = HMAC_K(V)
v = hmacCompute(hashFunc, k, v)
// Step h: K = HMAC_K(V || 0x01 || x || h1)
k = hmacCompute(hashFunc, k, v, []byte{0x01}, x, h1.Bytes())
// Step i: V = HMAC_K(V)
v = hmacCompute(hashFunc, k, v)
// Step j: Generate k
for {
// Step j.1: Set T = empty sequence
var t []byte
// Step j.2: While tlen < qlen
for len(t)*8 < bitSize {
// V = HMAC_K(V)
v = hmacCompute(hashFunc, k, v)
// T = T || V
t = append(t, v...)
}
// Step j.3: k = bits2int(T)
kInt := hashToInt(t, curve)
// Check if k is valid (0 < k < N)
if kInt.Sign() > 0 && kInt.Cmp(N) < 0 {
return kInt
}
// Step j.4: K = HMAC_K(V || 0x00)
k = hmacCompute(hashFunc, k, v, []byte{0x00})
// V = HMAC_K(V)
v = hmacCompute(hashFunc, k, v)
}
}
// signWithK performs ECDSA signing with a given k value
func signWithK(priv *ecdsa.PrivateKey, hash []byte, k *big.Int) (*big.Int, *big.Int, error) {
curve := priv.Curve
N := curve.Params().N
// Calculate r = x-coordinate of k*G mod N
x, _ := curve.ScalarBaseMult(k.Bytes())
r := new(big.Int).Set(x)
r.Mod(r, N)
if r.Sign() == 0 {
return nil, nil, fmt.Errorf("invalid r value")
}
// Calculate s = k^(-1) * (h + r*d) mod N
e := hashToInt(hash, curve)
kInv := new(big.Int).ModInverse(k, N)
if kInv == nil {
return nil, nil, fmt.Errorf("k has no inverse")
}
s := new(big.Int).Mul(r, priv.D)
s.Add(s, e)
s.Mul(s, kInv)
s.Mod(s, N)
if s.Sign() == 0 {
return nil, nil, fmt.Errorf("invalid s value")
}
// Canonicalize signature (ensure s <= N/2)
rFinal, sFinal := canonicalize(r, s, N)
return rFinal, sFinal, nil
}
// canonicalize ensures the signature is in canonical form (s <= N/2)
// This prevents signature malleability
func canonicalize(r, s, N *big.Int) (*big.Int, *big.Int) {
halfN := new(big.Int).Div(N, big.NewInt(2))
// If s > N/2, use N - s instead
if s.Cmp(halfN) > 0 {
s = new(big.Int).Sub(N, s)
}
return r, s
}
// hashToInt converts a hash value to an integer for ECDSA operations
func hashToInt(hash []byte, curve elliptic.Curve) *big.Int {
N := curve.Params().N
orderBits := N.BitLen()
orderBytes := (orderBits + 7) / 8
if len(hash) > orderBytes {
hash = hash[:orderBytes]
}
ret := new(big.Int).SetBytes(hash)
excess := len(hash)*8 - orderBits
if excess > 0 {
ret.Rsh(ret, uint(excess))
}
return ret
}
// hmacCompute computes HMAC with concatenated data
func hmacCompute(hashFunc func() hash.Hash, key []byte, data ...[]byte) []byte {
mac := hmac.New(hashFunc, key)
for _, d := range data {
mac.Write(d)
}
return mac.Sum(nil)
}
// bytes creates a byte slice filled with value
func bytes(size int, value byte) []byte {
b := make([]byte, size)
for i := range b {
b[i] = value
}
return b
}
// VerifyDeterministic verifies a deterministic ECDSA signature
func VerifyDeterministic(pub *ecdsa.PublicKey, hash []byte, r, s *big.Int) bool {
if pub == nil || r == nil || s == nil {
return false
}
// Ensure signature is canonical
N := pub.Curve.Params().N
halfN := new(big.Int).Div(N, big.NewInt(2))
// Check r and s are in valid range
if r.Sign() <= 0 || r.Cmp(N) >= 0 {
return false
}
if s.Sign() <= 0 || s.Cmp(halfN) > 0 {
return false // s must be <= N/2 for canonical form
}
return ecdsa.Verify(pub, hash, r, s)
}
// IsCanonical checks if a signature is in canonical form
func IsCanonical(s, N *big.Int) bool {
if s == nil || N == nil {
return false
}
halfN := new(big.Int).Div(N, big.NewInt(2))
return s.Cmp(halfN) <= 0
}
// MakeCanonical converts a signature to canonical form
func MakeCanonical(r, s, N *big.Int) (*big.Int, *big.Int) {
if r == nil || s == nil || N == nil {
return r, s
}
return canonicalize(r, s, N)
}
+286
View File
@@ -0,0 +1,286 @@
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)
}
}
}