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