2

I am trying to recreate the exact same signature as Bitcoin Core. I have installed bitcoin-core/secp256k1, but I get sometimes different signature as Bitcoin Core (which are valid). I read that since v0.17 there is an additional entropy (32 bit little endian unsigned integer with a counter). Due to my limiting experience I am not able to fully understand and implement it.

Could someone please give me some hints how to implement this counter in my code to produce the exact same signatures as Bitcoin Core.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <secp256k1.h>

// Function to convert hex string to byte array
void hex_to_bytes(const char *hex_str, unsigned char *byte_array, int byte_array_length) {
    for (int i = 0; i < byte_array_length; i++) {
        sscanf(hex_str + 2 * i, "%2hhx", &byte_array[i]);
    }
}

int main(void) {
    // Initialize secp256k1 context
    secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);

    // Provided private key and message hash in hex
    const char *priv_key_hex = "a8188b5621448098ae66fec001acff97b7b5dcfbe371b433455135794daec37a";
    const char *msg_hash_hex = "81fdf78421e2395807c9e41fa0b5ef2b587e5096d5eec43605fd669be824a872";

    unsigned char priv_key[32];
    unsigned char msg_hash[32];
    hex_to_bytes(priv_key_hex, priv_key, sizeof(priv_key));
    hex_to_bytes(msg_hash_hex, msg_hash, sizeof(msg_hash));

    // Sign the transaction hash
    secp256k1_ecdsa_signature signature;
    secp256k1_ecdsa_sign(ctx, &signature, msg_hash, priv_key, NULL, NULL);

    // Serialize the signature in DER format
    unsigned char der_signature[72];
    size_t der_sig_len = sizeof(der_signature);
    secp256k1_ecdsa_signature_serialize_der(ctx, der_signature, &der_sig_len, &signature);

    // Print the DER-encoded signature in hexadecimal
    printf("DER-encoded Signature: ");
    for (size_t i = 0; i < der_sig_len; i++) {
        printf("%02x", der_signature[i]);
    }
    printf("\n");

    // Free the secp256k1 context
    secp256k1_context_destroy(ctx);

    return 0;
}

Data:

tx_hash: 81fdf78421e2395807c9e41fa0b5ef2b587e5096d5eec43605fd669be824a872

key1_hex: a8188b5621448098ae66fec001acff97b7b5dcfbe371b433455135794daec37a

key2_hex: 9c1fa27bdf0e6d06ab451375aa4c29ca4298af28b97ac8773200054e98a2e215

Bitcoin core signatures: 304402203688862c2e94573a43e66305358aa74e84ace947ce080667e31265787f3738c10220380fa99f9507ae2dee832d19a7ee8fb2307708ff30e7a1532eb30a17877fa7fe

304402204647371d0957d5456484337ff9860ab86026f05e0e97533f09126635c35a4a1302200ee59f53f4e25f2b5f2239547a8719ea58b90d98a4afcc42fe1e55dbc70debc8

Signatures with my code: 3045022100803388608a707cf1cab202a3bd758af2d42dafaf1c07ee45283b8fb478953b5002205a132280324832637d5082b8c6e42af74236647b893effe47f72472ad277a073

3045022100a3378d3c142daead02c00d14ca69de55bcb1415e8df34173f891e7af5a7d78660220334a57a745e10d92cb007f7ddc905f3a2aa2ce72dad7b065fff339e461fa2260

1

1 Answer 1

2

I have implemented the counter and can reproduce the exact same signature as bitcoin core. Use with caution)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <secp256k1.h>
#include <assert.h>

// Function to write a 32-bit unsigned int to a byte array in little-endian format
static inline void WriteLE32(unsigned char* ptr, uint32_t x) {
    uint8_t data[4] = {
        (uint8_t)(x & 0xFF),
        (uint8_t)((x >> 8) & 0xFF),
        (uint8_t)((x >> 16) & 0xFF),
        (uint8_t)((x >> 24) & 0xFF)
    };
    memcpy(ptr, data, 4);
}

void hex_to_bytes(const char *hex_str, unsigned char *byte_array, int byte_array_length) {
    for (int i = 0; i < byte_array_length; i++) {
        sscanf(hex_str + 2 * i, "%2hhx", &byte_array[i]);
    }
}

int is_low_r(const secp256k1_ecdsa_signature *sig, secp256k1_context *ctx) {
    unsigned char compact_sig[64];
    secp256k1_ecdsa_signature_serialize_compact(ctx, compact_sig, sig);
    return compact_sig[0] < 0x80;
}

void print_signature(const secp256k1_ecdsa_signature *sig, secp256k1_context *ctx, const char* label) {
    unsigned char der_signature[72];
    size_t der_sig_len = sizeof(der_signature);
    secp256k1_ecdsa_signature_serialize_der(ctx, der_signature, &der_sig_len, sig);

    printf("%s Signature: ", label);
    for (size_t i = 0; i < der_sig_len; i++) {
        printf("%02x", der_signature[i]);
    }
    printf("\n");
}

// Function to print extra entropy
void print_extra_entropy(const unsigned char* extra_entropy, const char* label) {
    printf("%s Extra Entropy: ", label);
    for (int i = 0; i < 32; i++) {
        printf("%02x", extra_entropy[i]);
    }
    printf("\n");
}

int main(void) {
    secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
    const char *priv_key_hex = "79ae83f3ad0768dc7285f5b3b04e1a4277b791f1c262fea3152dc3cacbcc48f0";
    const char *msg_hash_hex = "496e448e4ddc9e493f165d900e604fd6f04bd4a7db99638947994ceac15519a1";

    unsigned char priv_key[32];
    unsigned char msg_hash[32];
    hex_to_bytes(priv_key_hex, priv_key, sizeof(priv_key));
    hex_to_bytes(msg_hash_hex, msg_hash, sizeof(msg_hash));

    secp256k1_ecdsa_signature signature;
    unsigned char extra_entropy[32] = {0};
    uint32_t test_case = 0; // Example test case initialization
    uint32_t counter = 0;
    int ret;

    // Enable grinding
    int grind = 1;

    // Initial signature generation with conditional extra entropy
    WriteLE32(extra_entropy, test_case);
    
    // In the ini run entropy is not NULL. Will not work correctly if just 32 bytes of 0s are passed in. Dont know why.
    ret = secp256k1_ecdsa_sign(ctx, &signature, msg_hash, priv_key, secp256k1_nonce_function_rfc6979, (!grind && test_case) ? extra_entropy : NULL);
    assert(ret);
    if (!is_low_r(&signature, ctx)) {
        print_signature(&signature, ctx, "Initial High R");
    } else {
        print_signature(&signature, ctx, "Initial Low R");
    }

    // Grinding for low R signatures, if necessary
    while (grind && !is_low_r(&signature, ctx)) {
        WriteLE32(extra_entropy, ++counter);
        print_extra_entropy(extra_entropy, "Grinding");
        ret = secp256k1_ecdsa_sign(ctx, &signature, msg_hash, priv_key, secp256k1_nonce_function_rfc6979, extra_entropy);
        assert(ret);
        if (is_low_r(&signature, ctx)) {
            print_signature(&signature, ctx, "Low R");
        } else {
            print_signature(&signature, ctx, "High R");
        }
    }

    secp256k1_context_destroy(ctx);
    return 0;
}

// gcc sign.c -lsecp256k1 -o sign
// ./sign

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.