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