Generating Cold Storage for Bitcoin

ExperiMENTAL
5 min readFeb 14, 2024

Managing private keys securely is essential to safeguard the funds in your Bitcoin public addresses. Cold storage involves keeping private keys offline, away from potential online threats. The method I’m going to outline in this tutorial doesn’t include hardware devices(like Ledger etc). This is specifically to show people what the core elements of Cold Storage actually are. Devices like the Ledger I mentioned above, can indeed either be used in conjunction or as a substitute for the method I’m outlining here. The main concept to bear in mind is ‘Self Sovereignty’ and having minimal trust in, devices, software or services, which at times can abstract, and introduce potential risks to your keys.

Therefore I wanted to show how a user can generate their own cold storage addresses, and how they can ensure that they maintain a reduced risk footprint when spending from said address later.

I’ll demonstrate how to generate cold storage for Bitcoin using Python on an air-gapped machine( which we can also use to prepare, and sign our transactions, exposing only a pre-signed hex representing our transaction to an online machine, later when we want to spend, which has no private key on it at all). This greatly reduces the chances of your addresses being compromised.

Prerequisites:

  • Basic understanding of Bitcoin and general cryptography
  • Python installed on an air-gapped machine you are going to use.
  • Familiarity with the ecdsa and Crypto libraries

Step 1: Generating Private and Public Keys

We’ll use Python’s ecdsa library to generate a private-public key pair for Bitcoin's curve secp256k1. The private key should never be exposed to the internet. Here's a Python script to generate the keys.

import ecdsa
import codecs
def generate_secp256k1_keypair():
private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
public_key = private_key.verifying_key
private_key_bytes = private_key.to_string()
public_key_bytes = public_key.to_string("compressed")
private_key_hex = codecs.encode(private_key_bytes, 'hex').decode()
public_key_hex = codecs.encode(public_key_bytes, 'hex').decode()
return private_key_hex, public_key_hex
private_key_hex, public_key_hex = generate_secp256k1_keypair()
print("Private Key (hex):", private_key_hex)
print("Public Key (hex, compressed 33 bytes):", public_key_hex)
Example of a secp256k1 key pair being generated on my offline machine

Step 2: Deriving Bitcoin Address

Now, let’s derive a Bitcoin address from the public key. We can also generate the address on the offline machine. We’ll use SHA-256 and RIPEMD-160 hashing algorithms followed by base58 encoding. The critical thing to remember here is, if we have a keypair, and know the encoding format used for Bitcoin, we can generate an address without even having to touch a Bitcoin node, or be online at all.

Here’s a script to derive the address:

import hashlib
import base58
from Crypto.Hash import RIPEMD160
def pubkey_to_address(pubkey_hex):
pubkey_bytes = bytes.fromhex(pubkey_hex)
sha256 = hashlib.sha256(pubkey_bytes).digest()
ripemd160 = RIPEMD160.new(sha256).digest()
version = b'\x6f' # Use '\x00' for mainnet addresses
payload = version + ripemd160
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
full_payload = payload + checksum
address = base58.b58encode(full_payload).decode('utf-8')
return address
public_key_hex = "<your_public_key_hex_here>"
address = pubkey_to_address(public_key_hex)
print(f"Bitcoin Address: {address}")
Address generated offline for Public key: 03c28daa0c2aa6fe52a81c32dbfa5a9c049041091d913f06f947b4a278021150e0
Showing the WIF(Wallet Import Format)

Step 3: Creating a Cold Storage Wallet
To create a cold storage wallet, securely store the private key generated in Step 1. Use methods like writing it down on paper or engraving it on a metal plate. I have a 3d printer, and this is one of the use cases for it. I generate a QR code. Make multiple copies and store them in different secure locations(you can consider encrypting the key, and then retaining the symmetric key used to encrypt and also store this, preferably in a different location)

An example of using 3d printing to create a plate for storing the private key

And that’s it, you now have a cold storage address, which you can fund(or others can fund for you).

Now, how can we spend from it without compromising our key?

Step 4: Sending Bitcoin from the Cold Storage Address at a later date

If you want to send Bitcoin from your funded cold storage address in future, follow these steps:

  • Prepare the Transaction Offline: Use your air-gapped machine to craft the transaction. Fetch the UTXOs associated with your cold storage address using a block explorer or other offline methods. Determine the recipient address and the amount to be sent.
  • Let’s assume we are going to spend the funds we stored in the cold storage address. In order to do so, we must first(on an online computer with an internet browser) do the following:
  1. Use the block explorer to find transactions associated with the given address (mwSFNxDeCbw46UX484t8gaDWdLtzuHy5fC).

2) Identify the transaction outputs (vout) that are unspent, indicating UTXOs available for spending.

Collect information about these UTXOs, including the transaction ID (txid), the output index (vout), and the value (amount) associated with each UTXO.

In our example, the details we need to create the raw transaction are:
“txid”: “ee34a75a39c211477118cd2c9cc4c877443fda712d3eb9593e2ea98864360a19”,
“vout”: 0,
“address”: “mwSFNxDeCbw46UX484t8gaDWdLtzuHy5fC”

Now back to the totally offline machine: Sign the Transaction Offline. Construct the transaction inputs and outputs offline. Use your private key to sign the transaction offline, ensuring that the private key remains secure and never exposed to an internet-connected device. ( your offline machine can use Python libraries to sign, or you can use an offline version of Bitcoin Core)

We craft a raw transaction spending the utxo from the cold storage wallet, doing so while being completely offline.

On any Bitcoin node(one which doesn’t have the corresponding private key): Broadcast the Transaction: Once the transaction is signed, you can broadcast it to the Bitcoin network using a live node or a service. Copy the signed raw transaction to a device connected to the internet and send the transaction.

Sending the raw signed hex from a live node, which does not have the private key associated with our address.
Here is the transaction we created, spending the funds from our cold storage public address.

This process ensures that your private key remains securely offline throughout the entire transaction process, from creating to signing, and sending of the singed hex.

Generating cold storage for Bitcoin enables you to secure your funds offline, minimizing the risk of online threats. It makes sense for anyone who is serious about protecting their bitcoin to be versed in the mechanics of a Cold Storage solution.

Hope this helps people understand Cold Storage a bit better :)

#coldstorage #Bitcoin #ExperiMENTAL

--

--