crypto,crypto/secp256k1: Support custom nonce counter for specialized signing

This commit is contained in:
sashabeton 2026-02-20 00:25:28 +01:00
parent 54f72c796f
commit 9fff572442
6 changed files with 59 additions and 8 deletions

View file

@ -87,7 +87,8 @@ SECP256K1_API int secp256k1_ecdsa_sign_recoverable(
const unsigned char *msghash32,
const unsigned char *seckey,
secp256k1_nonce_function noncefp,
const void *ndata
const void *ndata,
const unsigned int initialcount
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Recover an ECDSA public key from a signature.

View file

@ -120,7 +120,7 @@ static int secp256k1_ecdsa_sig_recover(const secp256k1_scalar *sigr, const secp2
return !secp256k1_gej_is_infinity(&qj);
}
int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *signature, const unsigned char *msghash32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) {
int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *signature, const unsigned char *msghash32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata, const unsigned int initialcount) {
secp256k1_scalar r, s;
int ret, recid;
VERIFY_CHECK(ctx != NULL);
@ -129,7 +129,7 @@ int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecd
ARG_CHECK(signature != NULL);
ARG_CHECK(seckey != NULL);
ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, msghash32, seckey, noncefp, noncedata);
ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, msghash32, seckey, noncefp, noncedata, initialcount);
secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid);
return ret;
}

View file

@ -507,12 +507,12 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m
const secp256k1_nonce_function secp256k1_nonce_function_rfc6979 = nonce_function_rfc6979;
const secp256k1_nonce_function secp256k1_nonce_function_default = nonce_function_rfc6979;
static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) {
static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata, const unsigned int initialcount) {
secp256k1_scalar sec, non, msg;
int ret = 0;
int is_sec_valid;
unsigned char nonce32[32];
unsigned int count = 0;
unsigned int count = initialcount;
/* Default initialization here is important so we won't pass uninit values to the cmov in the end */
*r = secp256k1_scalar_zero;
*s = secp256k1_scalar_zero;
@ -572,7 +572,7 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature
ARG_CHECK(signature != NULL);
ARG_CHECK(seckey != NULL);
ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, msghash32, seckey, noncefp, noncedata);
ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, msghash32, seckey, noncefp, noncedata, 0);
secp256k1_ecdsa_signature_save(signature, &r, &s);
return ret;
}

View file

@ -60,6 +60,14 @@ var (
// directly by an attacker. It is usually preferable to use a cryptographic
// hash function on any input before handing it to this function.
func Sign(msg []byte, seckey []byte) ([]byte, error) {
return SignUnsafe(msg, seckey, 0)
}
// SignUnsafe signs a hash with a custom starting counter for nonce derivation.
// WARNING: This function is unsafe and should only be used for specialized cases
// like finding low-R signatures or canonical signatures. Improper use can lead
// to private key exposure. Use Sign for standard signing operations.
func SignUnsafe(msg []byte, seckey []byte, counter uint) ([]byte, error) {
if len(msg) != 32 {
return nil, ErrInvalidMsgLen
}
@ -76,7 +84,7 @@ func Sign(msg []byte, seckey []byte) ([]byte, error) {
noncefunc = C.secp256k1_nonce_function_rfc6979
sigstruct C.secp256k1_ecdsa_recoverable_signature
)
if C.secp256k1_ecdsa_sign_recoverable(context, &sigstruct, msgdata, seckeydata, noncefunc, nil) == 0 {
if C.secp256k1_ecdsa_sign_recoverable(context, &sigstruct, msgdata, seckeydata, noncefunc, nil, (C.uint)(counter)) == 0 {
return nil, ErrSignFailed
}

View file

@ -130,6 +130,40 @@ func TestSignDeterministic(t *testing.T) {
}
}
func TestSignUnsafe(t *testing.T) {
pubkey1, seckey := generateKeyPair()
msg := make([]byte, 32)
sig1, err := Sign(msg, seckey)
if err != nil {
t.Fatal(err)
}
sig2, err := SignUnsafe(msg, seckey, 0)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sig1, sig2) {
t.Fatal("signatures not equal")
}
sig3, err := SignUnsafe(msg, seckey, 1000)
if err != nil {
t.Fatal(err)
}
if bytes.Equal(sig1, sig3) {
t.Fatal("signatures with different count param equal")
}
pubkey2, err := RecoverPubkey(msg, sig3)
if err != nil {
t.Errorf("recover error: %s", err)
}
if !bytes.Equal(pubkey1, pubkey2) {
t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2)
}
}
func TestRandomMessagesWithSameKey(t *testing.T) {
pubkey, seckey := generateKeyPair()
keys := func() ([]byte, []byte) {

View file

@ -51,12 +51,20 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
//
// The produced signature is in the [R || S || V] format where V is 0 or 1.
func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
return SignUnsafe(digestHash, prv, 0)
}
// SignUnsafe signs a hash with a custom starting counter for nonce derivation.
// WARNING: This function is unsafe and should only be used for specialized cases
// like finding low-R signatures or canonical signatures. Improper use can lead
// to private key exposure. Use Sign for standard signing operations.
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.Sign(digestHash, seckey)
return secp256k1.SignUnsafe(digestHash, seckey, counter)
}
// VerifySignature checks that the given public key created signature over digest.