mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 23:41:37 +00:00
No commit suggestions generated
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user