LeetArxiv

LeetArxiv

Share this post

LeetArxiv
LeetArxiv
Hacking Dormant Bitcoin Wallets in C

Hacking Dormant Bitcoin Wallets in C

Legal, Zero-fluff, Step-by-Step Coding Guide for Programmers Interested in Accessing Dormant Bitcoin Puzzle Wallets

Murage Kibicho's avatar
Murage Kibicho
Jul 25, 2025
∙ Paid
2

Share this post

LeetArxiv
LeetArxiv
Hacking Dormant Bitcoin Wallets in C
2
Share
LeetArxiv is Leetcode for implementing Arxiv and other research papers.

Stop reading papers. Start coding them. Subscribe to receive weekly paper implementations.

This is Chapter 2 in our upcoming book: A Programmer’s Guide to Hacking Satoshi’s Billion Dollar Wallet.
Available Chapters
  • Chapter 1 : A Friendly Introduction to Elliptic Curves.

  • Chapter 2 (we are here) : Hacking Dormant Bitcoin Wallets in C and Elliptic Curves.

  • Chapter 3 : Can we Find the Edwards Form of the Bitcoin Curve.

  • Chapter 4 : What Every Programmer Needs To Know About Conic Sections (To Attack Bitcoin)

  • Chapter 5 : The Discrete Logarithm Problem and the Index Calculus Solution

  • Chapter 6 : Finding the Montgomery Form of the Bitcoin Curve.

  • Chapter 7 : The US Government Hid a Mathematical Backdoor in another Elliptic Curve

This chapter demonstrates how to gain access to a bitcoin wallet.

Here are the chapter’s main takeaways:

  1. Not Your Keys Not Your Wallet:

    • Knowing a private key lets you takeover a bitcoin wallet.

  2. Private keys are used to generate bitcoin addresses:

    • One can test knowledge of a private key by generating a wallet address.

    • A private key generates a public key. The public key is hashed by SHA256 + RIPEMD. These are the summarized steps:

      • Private key →Public Key → SHA-256 → RIPEMD-160 → Base58Check = Bitcoin Address

  3. Official Bitcoin libraries are written in C.

    • This is a C programming guide since the official bitcoin libraries are written in the C language.

2.0 Coding a Wallet Breaker

This section is a step-by-step guide to coding a wallet breaker. It is split into these parts:

  1. Setting up a C coding environment on Linux.

  2. Writing the logic for a wallet breaker.

  3. The underlying elliptic curve theory needed to generate a public key from a private key.

If you’re in a hurry then grab a copy of this section’s code here.

2.1 Environment Setup

This is a C coding tutorial for Linux users. This section demonstrates how to install the official Secp256k1 library and where to get public keys and addresses.

2.1.0 Installing Secp256k1

libsecp256k1 GitHub repo

We use the secp256k1 library written by the official bitcoin-core team1.

  • Step 1: Clone the library from GitHub:

    git clone https://github.com/bitcoin-core/secp256k1.git
  • Step 2: Open the directory:

    cd secp256k1
  • Step 3: Use Autotools to build the library:

    ./autogen.sh       # Generate a ./configure script
    ./configure        # Generate a build system
    make               # Run the actual build process
    make check         # Run the test suite
    sudo make install  # Install the library into the system (optional)
  • Step 4: Do NOT close your terminal window. You need to add the library path to your bashrc. First export the path:

    export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
  • Step 5: Then add the path permanently. Close all terminal windows after this step.

    echo 'export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH' >> ~/.bashrc
    source ~/.bashrc

2.1.1 Installing OpenSSL

OpenSSL Github Repo

We need OpenSSL for SHA256 and RIPEMD. This library tends to come preinstalled on Linux. Here’s the GitHub repo with installation instructions.

2.1.2 Getting Test Bitcoin Keys and Addresses

List of Bitcoin challenges taken from Privatekeys.pw

Our primary testing data source is the Bitcoin puzzles. This is a set of private keys we are encouraged to bruteforce, or rather, “a crude measuring instrument of the cracking strength of the community.” (Saatoshi_Rising, 2017)2

A list of known private keys and wallet addresses is available on BitcoinTalk

Wallet address and private keys taken from BitcoinTalk

2.1.3 Verify Environment

MiningPOC directory with PrivateKey.c file

Create a folder called MiningPOC and a file named PrivateKey.c.

Add this code to your file:

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <secp256k1.h>
#include <openssl/sha.h>
#include <openssl/ripemd.h>

//Run: clear && gcc PrivateKey.c -lsecp256k1 -lcrypto -o m.o && ./m.o

int main()
{
	char *walletAddress = "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH";
	printf("%s\n", walletAddress);
}

Compile the code using this command:

clear && gcc PrivateKey.c -lsecp256k1 -lcrypto -o m.o && ./m.o

Your terminal window should output the annotated string:

Terminal window with annotated output

2.2 Wallet Breaker Logic

This section demonstrates a bruteforce attack on the Bitcoin Puzzle wallets. We illustrate how to convert a private key into a bitcoin wallet address in C.

Owning a bitcoin is equivalent to knowing a wallet’s private key.

Here’s a summary of the logic needed to convert a private key into a bitcoin wallet address:

Going from public key to bitcoin address

2.2.0 Understanding the Bitcoin Puzzle Challenge

The Bitcoin Puzzles demand one find a private key that generates a known bitcoin address.

The challenge is structured such that we know where to look for the keys. The upper bound is the maximum possible value of a puzzle solution.

Upper search bounds for a variety of puzzles taken from BitcoinTalk

Each private key in the bitcoin puzzle is a 32 bit integer ranging between 0 and the upper bound inclusive. Thus, most of the private keys in the challenge tend to start with lots of zeros.

Private keys specific to the bitcoin puzzles have lots of significant zeros. Taken from BitcoinTalk

We shall work with Puzzle #3 for the remainder of this section:

Puzzle #3 wallet address annotated. Taken from BitcoinTalk

We need to initialize the secp library, set our variables and destroy the secp context. At this point, your main function should resemble this:

int main()
{
	//Initialize secpContext
	secp256k1_context *secpContext = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
	
	//Set upper bound and target wallet address
	uint32_t upperBound = 7;
	char *walletAddress = "19ZewH8Kk1PDbSNdJ97FP4EiCjTRaZMZQA";
	printf("%s\n", walletAddress);
	
	//Destroy secpContext
	secp256k1_context_destroy(secpContext);
}	

2.2.1 Writing a Private Key Generator

This is a coding section. Feel free to compare your code with mine here.

First, we write a function to print strings in hexadecimal:

void PrintHexadecimalString(size_t stringLength, uint8_t *string)
{
	for(size_t i = 0; i < stringLength; i++)
	{
		printf("%02X", string[i]);
	}
	printf("\n");
}

Next, we write a for loop to generate private keys between 0 and our upper bound.

We also use secp256k1_ec_seckey_verify function to validate our private (secret) key. It returns 0 if the private key is invalid:

*0 is an invalid key
//Test different private keys	
for(uint8_t privateKey = 1; privateKey <= upperBound; privateKey++)
{
	uint8_t privateKeyString[32] = {0};
	privateKeyString[31] = privateKey;
	assert(secp256k1_ec_seckey_verify(secpContext, privateKeyString) != 0);
	PrintHexadecimalString(32, privateKeyString);
}

Your main function and terminal output should resemble this:

Main function and terminal output for Section 2.2.1

2.2.2 Writing a Private Key to Wallet Address Function

This section splits the inner workings of a private key to wallet address function into:

  1. Generating a public key from a private key.

    • We use the secp256k1_ec_pubkey_create to generate a public key.

      • It returns 0 upon failure.

    • We use the secp256k1_ec_pubkey_serialize to compress a public key into a 33-byte string.

    • Your for loop should resemble this:

      //Test different private keys	
      for(uint8_t privateKey = 1; privateKey <= upperBound; privateKey++)
      {
      	uint8_t privateKeyString[32] = {0};
      	privateKeyString[31] = privateKey;
      	assert(secp256k1_ec_seckey_verify(secpContext, privateKeyString) != 0);
      	printf("\nPrivate Key:\n");PrintHexadecimalString(32, privateKeyString);
      	
      	//Generate public key
      	secp256k1_pubkey publicKey;
      	size_t publicKeyLength = 33;
      	uint8_t publicKeyString[33];
      	
      	assert(secp256k1_ec_pubkey_create(secpContext, &publicKey, privateKeyString) != 0);
      	secp256k1_ec_pubkey_serialize(secpContext, publicKeyString, &publicKeyLength, &publicKey, SECP256K1_EC_COMPRESSED);
      	printf("Public Key:\n");PrintHexadecimalString(33, publicKeyString);
      }
    • This is the expected output:

      Private key and corresponding public keys
  2. Writing a wrapper function to convert a public key into a wallet address.

    • We write a function called PublicKeyToBitcoinWalletAddress. It takes as input a compressed public key: publicKeyString and a string to hold the generated bitcoin address: generatedAddress.

    • We assume publicKeyString is 33 bytes and generatedAddress is 64 bytes:

      //Assumes publicKeyString is 33 bytes
      //Assumes generatedAddress is 64 bytes
      void PublicKeyToBitcoinWalletAddress(uint8_t *publicKeyString, char *generatedAddress)
      {
      	
      }
    • The function is called inside the main for loop:

      //Generate wallet address
      char generatedAddress[64];
      PublicKeyToBitcoinWalletAddress(publicKeyString, generatedAddress);
  3. Finding the SHA256 hash of the public key.

    • We use OpenSSL’s SHA256:

      uint8_t sha256Hash[32];
      SHA256(publicKeyString, 33, sha256Hash);
  4. Finding the RipeMD160 hash of the SHA256 hash.

    • We use OpenSSL’s RIPEMD160:

      uint8_t ripemd160Hash[20];
      RIPEMD160(sha256Hash, 32, ripemd160Hash);
  5. Setting the version payload.

    • This is the mainnet’s version byte and it is 21 bytes:

      uint8_t versionedPayload[21];
      versionedPayload[0] = 0x00; // version byte for mainnet
      memcpy(versionedPayload + 1, ripemd160Hash, 20);
  6. Finding a SHA256 checksum of the payload.

    • We double SHA256 hash the payload:

      uint8_t checksumInput[25];
      memcpy(checksumInput, versionedPayload, 21);
      	
      uint8_t hash1[32], hash2[32];
      SHA256(checksumInput, 21, hash1);
      SHA256(hash1, 32, hash2);
      memcpy(checksumInput + 21, hash2, 4);
  7. Encode all the hashes in Base58.

    • We call the function like this:

      EncodeInBase58(25, checksumInput, 64, generatedAddress);
    • Then we write it:

    const char *BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    
    void EncodeInBase58(size_t inputLength, uint8_t *input, size_t outputLength, char *output)
    {
    	uint8_t temp[32] = {0};memcpy(temp, input, inputLength);
    	uint8_t result[64] = {0};int resultLength = 0;
    
    	for(size_t i = 0; i < inputLength; i++)
    	{
    		int carry = temp[i];
    		for(int j = 0; j < resultLength || carry; j++)
    		{
    			if(j == resultLength) resultLength++;
    			carry += 256 * result[j];
    			result[j] = carry % 58;
    			carry /= 58;
    		}
    	}
    
    	int leading_zeros = 0;
    	for(size_t i = 0; i < inputLength && input[i] == 0; i++) {output[leading_zeros++] = '1';}
    	for(int i = resultLength - 1; i >= 0; i--) {output[leading_zeros++] = BASE58_ALPHABET[result[i]];}
    	output[leading_zeros] = '\0';
    }
  8. Your final PublicKeyToBitcoinWalletAddress function should resemble this:

    Complete PublicKeyToBitcoinWalletAddress function
  9. We add a comparison function at the bottom of our for loop:

    //Generate wallet address
    char generatedAddress[64];
    PublicKeyToBitcoinWalletAddress(publicKeyString, generatedAddress);
    printf("Generated Address:%s\n",generatedAddress);
    
    if(strcmp(generatedAddress, walletAddress) == 0)
    {
    	printf("**MATCH FOUND: Private Key = %u\n", privateKey);
    	break;
    }

If everything is running fine then your output should resemble this:

We found 7 as the solution to Bitcoin Puzzle #3

Compare your code with mine here.

2.3 Generating Public Keys using Elliptic Curves

This section goes into the fine details of generating public keys from private keys using elliptic curve theory.

We shall learn how to write our own secp256k1_ec_pubkey_create from Section 2.2.

This section is split into four parts:

  1. Quick introduction to the elliptic curve discrete logarithm problem.

  2. Understanding the group laws of elliptic curves.

  3. Defining the data structures needed for elliptic curves in C.

  4. Implementing scalar multiplication and point addition on elliptic curves.

This section is math heavy. Take your time.

2.3.1 Elliptic Curve Discrete Logarithm Problem

Let E be an elliptic curve defined over a finite field 𝔽p and let S, T ∈ E(𝔽p). The Elliptic Curve Discrete Logarithm Problem (ECDLP) is the challenge of finding an integer m satisfying (Silverman, 2007)3:

Elliptic curve discrete logarithm problem taken from (Silverman, 2007)

In the simplest terms, bitcoin’s secp256k1 is secure because it is difficult to divide two points on an elliptic curve.

You can think of T as the public key and S as the private key. This section writes a library that multiplies the private key by itself m times.

More formally, the intractibility of the discrete logarithm problem provides a basis for the security of many public-key cryptosystems (Howell, 1998)4.

The next section introduces the mathematical structure of elliptic curves.

2.3.2 Elliptic Curve Group Laws

Elliptic curves are part of a mathematical structure called a group.

A group law is a rule that defines how to add two points in the group to yield a valid third point.

For two points P and Q and a resulting point R in an elliptic curve, the group laws are (Washington, 2008)5:

  1. Closure: adding two points yields another point on the curve.

  2. Associativity: (P+Q)+R = P+(Q+R).

  3. Commutativity: (P+Q) = (Q+P).

  4. Identity element: The point at infinity (∞) acts like 0. i.e P + ∞ = P

  5. Inverse element: Every point P=(x, y) has an inverse −P = (x, −y)

    • In elliptic curves, the inverse element is found by reflecting over the x-axis.

To add two points P and Q on an elliptic curve, one needs to handle these cases:

  1. Either P or Q is a point at infinity:

    • If P is the point at infinity, return Q because ∞ + Q = Q.

    • If Q is the point at infinity, return P because ∞ + P = P.

  2. P equals Q (Point Doubling ie, P == Q).

    • Compute 2P using the tangent line slope.

  3. P equals -Q. (P == -Q)

    • Here, P and Q have the same x value but y differs or is zero. Remember the group law, P - Q = 0 (0 is the point at infinity)

    • Return the point at infinity.

  4. P is NOT equal to Q (Point Addition ie, P ≠ Q).

    • Use the slope of the chord to return P + Q.

2.3.3 Coding an Elliptic Curve Library in C

This section defines a struct to hold points, and writes allocators and deallocators. Important things to note:

Keep reading with a 7-day free trial

Subscribe to LeetArxiv to keep reading this post and get 7 days of free access to the full post archives.

Already a paid subscriber? Sign in
© 2025 Murage Kibicho
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share