mirror of
https://github.com/sonr-io/crypto.git
synced 2026-01-11 20:08:57 +00:00
225 lines
5.2 KiB
Go
225 lines
5.2 KiB
Go
// 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)
|
|
}
|