add SignUnsafe with RFC6979 counter support

This commit is contained in:
garima-uttam 2026-03-16 13:39:54 +05:30
parent de3d0c69dd
commit fac8fc3c6c
2 changed files with 94 additions and 0 deletions

View file

@ -59,6 +59,24 @@ func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
return secp256k1.Sign(digestHash, seckey)
}
// SignUnsafe calculates an ECDSA signature, using the given RFC6979 counter as the
// starting point for nonce generation.
//
// This function is susceptible to chosen plaintext attacks that can leak
// information about the private key that is used for signing. Callers must
// be aware that the given digest cannot be chosen by an adversary. Common
// solution is to hash any input before calculating the signature.
//
// The produced signature is in the [R || S || V] format where V is 0 or 1.
func SignUnsafe(digestHash []byte, prv *ecdsa.PrivateKey, counter uint) (sig []byte, err error) {
if len(digestHash) != DigestLength {
return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(digestHash))
}
seckey := math.PaddedBigBytes(prv.D, prv.Params().BitSize/8)
defer zeroBytes(seckey)
return secp256k1.SignUnsafe(digestHash, seckey, counter)
}
// VerifySignature checks that the given public key created signature over digest.
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
// The signature should have the 64 byte [R || S] format.

View file

@ -99,6 +99,82 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
return sig, nil
}
// SignUnsafe calculates an ECDSA signature, using the given RFC6979 counter as the
// starting point for nonce generation.
//
// This function is susceptible to chosen plaintext attacks that can leak
// information about the private key that is used for signing. Callers must
// be aware that the given hash cannot be chosen by an adversary. Common
// solution is to hash any input before calculating the signature.
//
// The produced signature is in the [R || S || V] format where V is 0 or 1.
func SignUnsafe(hash []byte, prv *ecdsa.PrivateKey, counter uint) ([]byte, error) {
if counter > uint(^uint32(0)) {
return nil, errors.New("invalid counter")
}
if counter == 0 {
return Sign(hash, prv)
}
if len(hash) != DigestLength {
return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(hash))
}
if prv.Curve != S256() {
return nil, errors.New("private key curve is not secp256k1")
}
// ecdsa.PrivateKey -> secp256k1.PrivateKey
var priv secp256k1.PrivateKey
if overflow := priv.Key.SetByteSlice(prv.D.Bytes()); overflow || priv.Key.IsZero() {
return nil, errors.New("invalid private key")
}
defer priv.Zero()
// RFC6979/BIP62 deterministic signing with the ability to choose the initial
// RFC6979 extra-iterations counter.
privKeyScalar := &priv.Key
var privKeyBytes [32]byte
privKeyScalar.PutBytes(&privKeyBytes)
defer zeroBytes(privKeyBytes[:])
for iteration := uint32(counter); ; iteration++ {
k := secp256k1.NonceRFC6979(privKeyBytes[:], hash, nil, nil, iteration)
var kG secp256k1.JacobianPoint
secp256k1.ScalarBaseMultNonConst(k, &kG)
kG.ToAffine()
var xBuf [32]byte
kG.X.PutBytes(&xBuf)
var r secp256k1.ModNScalar
overflow := r.SetBytes(&xBuf)
zeroBytes(xBuf[:])
if r.IsZero() {
k.Zero()
continue
}
pubKeyRecoveryCode := byte(overflow<<1) | byte(kG.Y.IsOddBit())
var e secp256k1.ModNScalar
e.SetByteSlice(hash)
kInv := new(secp256k1.ModNScalar).InverseValNonConst(k)
k.Zero()
s := new(secp256k1.ModNScalar).Mul2(privKeyScalar, &r).Add(&e).Mul(kInv)
if s.IsZero() {
continue
}
if s.IsOverHalfOrder() {
s.Negate()
pubKeyRecoveryCode ^= 0x01
}
sig := make([]byte, SignatureLength)
r.PutBytesUnchecked(sig[:32])
s.PutBytesUnchecked(sig[32:64])
sig[RecoveryIDOffset] = pubKeyRecoveryCode
return sig, nil
}
}
// VerifySignature checks that the given public key created signature over hash.
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
// The signature should have the 64 byte [R || S] format.