Bitcoin
Signatures – Bitcoin message signing and other functions coded in C (not C++). Is there a good library that handles everything?
After searching the internet for libraries that can do this and/or code and AI code helpers that can do it, I have succeeded in writing C code (simple C, not C++) that generates valid Bitcoins. Sign your message! I coded in C 30 years ago, so my code may not be the best. If anyone knows a good library that does all the functions, I’d like to share it here. The next step is to verify and prepare your signature. Including transaction posting and more. Here’s what I did:
Next compile (assuming you have all the libs in your path):
gcc btcsign.c -o btcsign -lsecp256k1 -lssl -lcrypto -lm
Run with:
./btcsign "Private Key WIF" "Text message or SHA256 hash of a file"
for example:
./btcsign "5JkH4WGyg1XcUAzcfqLhKwpfs5A4v4Jdw6gWpgTLFhQW7wcnUMo" "Hello"
It produces the following output:
Decoded Private Key: 7a97de76a46131c657bb9cc1ea2a19db73a4db1977c613ae2d6b1359466f0738
Uncompressed Public Key: 042a5d607f02bc50d6472eb1f098a6255cbdcf8ac181b43e52c15fc0abe0aa44bb9890417f2336f47e963e9892ef5f8ee6379a7aa9196324e4b11e550e921c7bce
Address: 136sTPvs2UWRUS7sLScmPqWiqCKveqnewL
Message Hash: c6e436f77154a548799e2b749f9a0687b4dc03a1c4b0d3ebf962f5e862ae1b6e
sig: bf2015fab095a0acc6e53245fc72de4de431008638069b49f20f8b1ad2457bba7db492187e42d70d38e7ace0d2f184ec156aabf8de5979ed1e5e877209518864
bitcoin_sig: 1cbf2015fab095a0acc6e53245fc72de4de431008638069b49f20f8b1ad2457bba7db492187e42d70d38e7ace0d2f184ec156aabf8de5979ed1e5e877209518864
signature_base64: HL8gFfqwlaCsxuUyRfxy3k3kMQCGOAabSfIPixrSRXu6fbSSGH5C1w0456zg0vGE7BVqq/jeWXntHl6HcglRiGQ=
Verify the signature (last string) using the following public tools:
https://bitaps.com/signature
or
https://tools.qz.sg/
The full code is below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/ripemd.h>
#include <secp256k1.h>
#include <secp256k1_recovery.h>
const char BASE58_CHARS() = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
void base58c_encode(uint8_t *input, size_t input_len, char **output)
// Count leading zeros
size_t leading_zeros = 0;
while (leading_zeros < input_len && input(leading_zeros) == 0)
++leading_zeros;
// Determine the size of the output buffer
size_t output_size = (input_len - leading_zeros) * 138 / 100 + 1;
uint8_t buffer(output_size);
memset(buffer, 0, output_size);
// Encode the input data
for (size_t i = leading_zeros, j = output_size - 1; i < input_len; ++i, j = output_size - 1)
for (int carry = input(i); j >= 0
// Determine the start index of the encoded data
size_t start_index = 0;
while (start_index < output_size && buffer(start_index) == 0)
++start_index;
// Allocate memory for the output string
*output = malloc(leading_zeros + output_size - start_index + 1);
if (*output == NULL)
fprintf(stderr, "Memory allocation failed\n");
return;
// Add leading zeros to the output string
memset(*output, '1', leading_zeros);
// Copy the encoded data to the output string
for (size_t i = start_index, j = leading_zeros; i < output_size; ++i, ++j)
(*output)(j) = BASE58_CHARS(buffer(i));
(*output)(leading_zeros + output_size - start_index) = '\0';
// Base58 decoding
int base58_decode(const char *base58, unsigned char *output, int *out_len)
unsigned char buffer(50) = 0;
int i, j, carry;
for (i = 0; i < strlen(base58); i++)
const char *ptr = strchr(BASE58_CHARS, base58(i));
if (!ptr) return -1;
carry = ptr - BASE58_CHARS;
for (j = sizeof(buffer) - 1; j >= 0; j--)
carry += 58 * buffer(j);
buffer(j) = carry & 0xff;
carry >>= 8;
// Find the starting non-zero position
for (i = 0; i < sizeof(buffer) && buffer(i) == 0; i++);
int leading_zeros = i;
int size = sizeof(buffer) - leading_zeros;
memcpy(output, buffer + leading_zeros, size);
*out_len = size;
return 0;
// Decode WIF private key to hex
int decode_wif_to_hex(const char *wif_key, unsigned char *hex_key)
unsigned char buffer(37);
int out_len;
if (base58_decode(wif_key, buffer, &out_len) != 0)
return -1;
// Validate checksum
unsigned char checksum(SHA256_DIGEST_LENGTH);
SHA256(buffer, out_len - 4, checksum);
SHA256(checksum, SHA256_DIGEST_LENGTH, checksum);
if (memcmp(checksum, buffer + out_len - 4, 4) != 0)
return -1;
// Extract private key (strip first and last byte)
memcpy(hex_key, buffer + 1, 32);
return 32;
// Base64 encoding
char *base64_encode(const unsigned char *input, int length)
BIO *bmem, *b64;
BUF_MEM *bptr;
char *buff;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, input, length);
BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
buff = (char *)malloc(bptr->length + 1);
memcpy(buff, bptr->data, bptr->length);
buff(bptr->length) = '\0';
BIO_free_all(b64);
return buff;
// Helper function to convert a recovered ECDSA signature to Bitcoin format
int ecdsa_signature_to_bitcoin(const unsigned char *sig, unsigned int sig_len, int recid, unsigned char *output) recid > 3) return -1;
output(0) = 27 + recid + (recid & 2 ? 4 : 0);
memcpy(output + 1, sig, sig_len);
return sig_len + 1;
// Function to print hexadecimal data
void print_hex(const char *label, const unsigned char *data, int len)
printf("%s: ", label);
for (int i = 0; i < len; i++)
printf("%02x", data(i));
printf("\n");
// Helper function to print the uncompressed public key
void print_uncompressed_pubkey(secp256k1_pubkey *pubkey, secp256k1_context *ctx)
unsigned char pubkey_serialized(65);
size_t pubkey_len = 65;
secp256k1_ec_pubkey_serialize(ctx, pubkey_serialized, &pubkey_len, pubkey, SECP256K1_EC_UNCOMPRESSED);
print_hex("Uncompressed Public Key", pubkey_serialized, pubkey_len);
void double_sha256(const unsigned char *input, size_t length, unsigned char *output)
unsigned char hash(SHA256_DIGEST_LENGTH);
SHA256(input, length, hash);
SHA256(hash, SHA256_DIGEST_LENGTH, output);
int main(int argc, char *argv()) SECP256K1_CONTEXT_VERIFY);
// Load the private key
secp256k1_ecdsa_recoverable_signature recoverable_sig;
secp256k1_pubkey pubkey;
if (!secp256k1_ec_pubkey_create(secp256k1_ctx, &pubkey, private_key_hex))
printf("Error creating public key\n");
return 1;
// Print the uncompressed public key
print_uncompressed_pubkey(&pubkey, secp256k1_ctx);
// Generate un_Bitcoin address from public key
unsigned char un_serialized_pubkey(65); // Increase the buffer size to 65 bytes
size_t un_serialized_pubkey_len = 65;
secp256k1_ec_pubkey_serialize(secp256k1_ctx, un_serialized_pubkey, &un_serialized_pubkey_len, &pubkey, SECP256K1_EC_UNCOMPRESSED);
const unsigned char *d = un_serialized_pubkey;
uint8_t un_sha256_hash(32);
uint8_t un_pubkey_hash(20);
SHA256(un_serialized_pubkey, sizeof(un_serialized_pubkey), un_sha256_hash);
RIPEMD160(un_sha256_hash, sizeof(un_sha256_hash), un_pubkey_hash);
uint8_t un_address_payload(21);
un_address_payload(0) = 0x00; // Main net version byte
memcpy(un_address_payload + 1, un_pubkey_hash, 20);
uint8_t un_address_checksum(4);
uint8_t un_address_hash(32);
SHA256(un_address_payload, 21, un_address_hash);
SHA256(un_address_hash, 32, un_address_hash);
memcpy(un_address_checksum, un_address_hash, 4);
uint8_t un_address_pre_base58(25);
memcpy(un_address_pre_base58, un_address_payload, 21);
memcpy(un_address_pre_base58 + 21, un_address_checksum, 4);
size_t un_address_base58_len = 25;
char* un_base58_address = malloc(34 * sizeof(char));
if (un_base58_address == NULL)
exit(EXIT_FAILURE);
base58c_encode(un_address_pre_base58, un_address_base58_len, &un_base58_address);
// Print the Address
printf("Address: %s\n", un_base58_address);
free(un_base58_address);
// Create the prefixed message
unsigned char prefixed_message(256);
size_t prefix_len = strlen(bitcoin_prefix);
size_t msg_len = strlen(message);
prefixed_message(0) = (unsigned char)msg_len;
memcpy(prefixed_message + 1, message, msg_len);
unsigned char full_prefixed_msg(256);
size_t full_prefixed_len = prefix_len + msg_len + 1;
memcpy(full_prefixed_msg, bitcoin_prefix, prefix_len);
memcpy(full_prefixed_msg + prefix_len, prefixed_message, msg_len + 1);
// Hash the prefixed message using SHA256 twice
unsigned char hash2(SHA256_DIGEST_LENGTH);
double_sha256(full_prefixed_msg, full_prefixed_len, hash2);
// Print the message hash
print_hex("Message Hash", hash2, SHA256_DIGEST_LENGTH);
// Sign the message hash
if (!secp256k1_ecdsa_sign_recoverable(secp256k1_ctx, &recoverable_sig, hash2, private_key_hex, NULL, NULL))
printf("Error signing the message\n");
return 1;
// Serialize the recoverable signature
unsigned char sig(64);
int recid;
secp256k1_ecdsa_recoverable_signature_serialize_compact(secp256k1_ctx, sig, &recid, &recoverable_sig);
printf("sig: ");
for (int i = 0; i < 64; i++)
printf("%02x", sig(i));
printf("\n");
// Convert to Bitcoin format
unsigned char bitcoin_sig(65);
int bitcoin_sig_len = ecdsa_signature_to_bitcoin(sig, sizeof(sig), recid, bitcoin_sig);
printf("bitcoin_sig: ");
for (int i = 0; i < 65; i++)
printf("%02x", bitcoin_sig(i));
printf("\n");
// Encode the signature in base64
char *signature_base64 = base64_encode(bitcoin_sig, bitcoin_sig_len);
printf("signature_base64: %s\n", signature_base64);
// Clean up
free(signature_base64);
secp256k1_context_destroy(secp256k1_ctx);
return 0;