Bitcoin Core
Building Bitcoin Core from source and running a pruned node, on macOS. Every command below is meant to be copied verbatim into a terminal.
Cloning The Repository
git clone https://github.com/bitcoin/bitcoin btc_core
cd btc_core Building The Repository
Check the documentation for your OS
Bitcoin Core ships one build document per platform. This walkthrough is macOS, so the file is doc/build-osx.md; on Linux or Windows read the matching one instead — the dependencies differ.
less doc/build-osx.md # Read this, etc. Building it
Check out a release tag, not master. Tags ending in rc are release candidates — pick the latest one without that suffix. Berkeley DB 4 is only needed if you want legacy wallet support.
git tag
# Look for the latest tag without rc, e.g.:
# v28.0
# v28.0rc1
# v28.0rc2
# v28.1 # In this example, it's v28.1
# v28.1rc1
# v28.1rc2
# v29.0rc1
# v29.0rc2
# v29.0rc3
git checkout v28.1
# Pre-install everything
brew install berkeley-db@4 # This is needed for legacy support
# Building
CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++
./autogen.sh
# We also want legacy support, so install Berkeley DB
./configure --with-gui=no # I don't want a graphical user interface in this case
make
make check # Running tests with Python 3 (you can skip this) Location information
Data, wallet and blocks land in the default data directory unless you pass -datadir:
# This is where Bitcoin data, wallet, blocks, etc., will be stored by default:
/Users/${USER}/Library/Application Support/Bitcoin/ Bitcoin config
Set prune in the config to avoid wasting computer space on many blocks. prune=550 is the minimum the node accepts, keeping roughly 550 MiB of recent blocks instead of the full chain.
# Setting Prune in the Config
# We should set prune in the config to avoid wasting computer space on many blocks
echo "prune=550" >> /Users/${USER}/Library/Application\ Support/Bitcoin/bitcoin.conf Running The Binaries
Three binaries built in src do the work. Pass -help to any of them to learn more.
./src/bitcoind
./src/bitcoin-cli
./src/bitcoin-wallet Starting the node
Starting the Bitcoin daemon, from scratch, with Bitcoin Core:
./src/bitcoind -daemon # Starts the Bitcoin daemon. Wallets
The commands in this section create, print and export real private keys. Anything that holds a private key holds the coins: whoever reads the file spends the money, and there is no recovery, no chargeback and no support line.
Run these against a wallet you are willing to lose until you know exactly what each command writes to disk.
Legacy wallet
# Creating a Legacy Wallet
./src/bitcoin-wallet -legacy -wallet="test" create
# Activating and Loading the Wallet in bitcoin-cli
./src/bitcoin-cli loadwallet test
# Fetching Wallet Data
./src/bitcoin-cli getwalletinfo Exporting a legacy wallet
# Creating an Address and Exporting Data (Showing it's a Legacy Wallet)
# This returns a public key address to receive Bitcoin:
./src/bitcoin-cli getnewaddress "label"
# e.g.: bc1qnxh055rdn73dutsspkyg8c8t962pefduymxj3q
# If no label,this will list the public key, you can fetch it with:
./src/bitcoin-cli getaddressesbylabel ""
# exporting one private key
./src/bitcoin-cli dumpprivkey the_public_address_you_created
# Dump all addresses
./src/bitcoin-cli dumpwallet /path/to/dump_info.txt Observation. If you look for the previous address, you can find its private key in this file. In the dump, you will have the master private key, followed by a bunch of private_key // public_key pairs.
Current wallet format, with descriptors
Descriptor wallets are the current default. The legacy export functions do not work on them — dumpprivkey will fail, and you read the keys through the descriptors instead.
# Creating a Wallet with Descriptors (Non-Legacy)
./src/bitcoin-wallet -wallet="my_descriptor_wallet" create
./src/bitcoin-wallet -wallet="my_descriptor_wallet" info
# Loading the Wallet
./src/bitcoin-cli loadwallet my_descriptor_wallet
./src/bitcoin-cli getwalletinfo
# Listing Descriptors, fetching data from them:
./src/bitcoin-cli listdescriptors
# The exporting keys Legacy Functions Don't Work
# (e.g., dumpprivkey will not work with descriptor wallets) Encrypting The Wallet File For Safe Storage
Encrypting wallet.dat and backing it up elsewhere. Bitcoin Core does not handle PGP keys itself, but GPG covers the backup and the secure communication around it.
# To encrypt the wallet and store it elsewhere:
./src/bitcoin-cli encryptwallet "your_secure_passphrase"
# This encrypts the wallet.dat file. Back it up securely!
# Store it in a safe location like an external drive or encrypted storage.
# Storing PGP Private Key and Public Key
# Bitcoin Core doesn't directly handle PGP keys, but you can:
# 1. Generate a PGP key pair using a tool like GPG:
gpg --generate-key
# Follow the prompts to create your key pair
gpg --export -a "Your Name" > public_key.asc
gpg --export-secret-key -a "Your Name" > private_key.asc
# 2. Store the private_key.asc and public_key.asc files securely.
# 3. Use these keys to encrypt backups or communicate securely. The passphrase is not recoverable. Lose it and the encrypted wallet.dat is a dead file — the coins stay on the chain and nobody, including you, can move them.
private_key.asc is your GPG secret key in plain text. Treat that file the way you treat the wallet itself: encrypted storage or an offline drive, never a synced folder.
Creating And Importing Mnemonics
Bitcoin Core does not generate a BIP-39 mnemonic. There is no bitcoin-cli command that prints twelve or twenty-four words, and there never was one. Core speaks descriptors and keeps its own HD seed; BIP-39 is a wallet-interchange format it deliberately stayed out of. So the phrase is generated elsewhere, turned into an extended private key, and imported as a descriptor. That last step is the only part Core participates in.
Generating the phrase outside Core
BIP-39 is a pipeline: entropy becomes a word list, the word list becomes a seed through PBKDF2, the seed becomes BIP-32 keys. The tool below walks it end to end and prints the xprv and WIF at the other side.
# There is no bitcoin-cli command here. Bitcoin Core does not generate,
# store or read BIP-39 mnemonics. It works with descriptors and with its own
# HD seed. The phrase has to come from somewhere else, generated offline.
# This one is mine, a BIP-39 implementation in C:
git clone https://github.com/afa7789/mnmncs
cd mnmncs
# macOS
gcc -w mnemonics.c -I$(brew --prefix openssl)/include -L$(brew --prefix openssl)/lib -lssl -lcrypto -o out
gcc -w bip32.c -lssl -lcrypto -o bip32
# Linux
# gcc -w mnemonics.c -lssl -lcrypto -o out
# gcc -w bip32.c -lssl -lcrypto -o bip32
# 256 bits of entropy -> 24 words, plus the BIP-39 seed (PBKDF2 over the phrase)
./out 256 1
# Feed the hex seed into BIP-32 derivation. This prints the xprv and the WIF.
./bip32 <the_hex_seed_from_the_previous_step> Generate on a machine that is offline, and read the words off the screen rather than out of a file. Entropy that leaks is a wallet somebody else owns.
From xprv to a descriptor Core accepts
# A descriptor is a script template plus a key plus a derivation path.
# wpkh() is pay-to-witness-pubkey-hash: native segwit, bc1... addresses.
# The path is BIP-84: 84h / 0h (mainnet) / 0h (account) / chain / index,
# where chain 0 is receiving addresses and chain 1 is change.
# The trailing /* is what makes it ranged: one descriptor, many addresses.
# importdescriptors REFUSES a descriptor without a checksum.
# getdescriptorinfo computes it:
./src/bitcoin-cli getdescriptorinfo "wpkh(xprv.../84h/0h/0h/0/*)"
# It answers with, among other fields:
# {
# "descriptor": "wpkh(xpub.../84h/0h/0h/0/*)#abcdefgh",
# "checksum": "abcdefgh",
# "isrange": true,
# "issolvable": true,
# "hasprivatekeys": true
# }
# Read the echoed "descriptor" carefully: it is the canonical form WITHOUT
# private keys — the xprv came back as an xpub. Import that string and you
# get a watch-only wallet that cannot sign. Take only the 8-character
# "checksum" and append it to your own string, the one that still has the
# xprv: wpkh(xprv.../84h/0h/0h/0/*)#abcdefgh
# Repeat for the change chain:
./src/bitcoin-cli getdescriptorinfo "wpkh(xprv.../84h/0h/0h/1/*)" Importing with importdescriptors
# A blank wallet has no keys and no HD seed of its own — nothing to collide
# with what you are about to import.
./src/bitcoin-cli -named createwallet wallet_name="mnemonic_wallet" blank=true
# Both chains in one call. Read the timestamp warning below before running it.
./src/bitcoin-cli -rpcwallet=mnemonic_wallet importdescriptors '[
{
"desc": "wpkh(xprv.../84h/0h/0h/0/*)#checksum_of_the_first",
"active": true,
"internal": false,
"range": [0, 999],
"timestamp": 0
},
{
"desc": "wpkh(xprv.../84h/0h/0h/1/*)#checksum_of_the_second",
"active": true,
"internal": true,
"range": [0, 999],
"timestamp": 0
}
]'
# Success is [{"success":true},{"success":true}]. Anything else, read the
# "error" field — it names the offending field, it does not guess for you.
./src/bitcoin-cli -rpcwallet=mnemonic_wallet getwalletinfo
./src/bitcoin-cli -rpcwallet=mnemonic_wallet listdescriptors
./src/bitcoin-cli -rpcwallet=mnemonic_wallet getnewaddress timestamp is where the rescan starts, in UNIX epoch seconds. The string "now" skips the scan entirely — it is correct only for a key that has never received anything. Put "now" on a descriptor rebuilt from an old mnemonic and Core will not look at any of the history: the wallet reports a zero balance and every past transaction is missing. The coins are on the chain and untouched, but nothing on your screen says so. Use 0 to scan from genesis, or the epoch seconds of the day the wallet was first created.
The rescan cannot read blocks the node no longer has. With the prune=550 from earlier on this page, only the last few hundred megabytes of chain remain on disk, and an old descriptor has nothing to scan. Recovering an existing wallet means an unpruned node.
Using These Keys In Electrum
Electrum reads the same mnemonic and the same WIF keys, and gets a useful amount of it wrong by default.
Restoring the mnemonic
Electrum has its own seed format, which is not BIP-39. Paste a BIP-39 phrase into the restore screen without opening Options and ticking BIP39 seed, and Electrum interprets those same words under its own scheme, derives a completely different set of keys, and opens an empty wallet. No error, no warning — the phrase is valid, it just means something else there. This is the single most common way people conclude they have lost funds that were never lost.
# In the Electrum wizard: "I already have a seed" -> paste the 24 words ->
# click OPTIONS and tick "BIP39 seed" BEFORE continuing.
#
# Skip that tick and Electrum reads the phrase in its own seed format, which
# is not BIP-39, and derives a different wallet. It will open, it will be
# empty, and it will look exactly like you lost the coins.
# Then pick the derivation path matching what you imported into Core:
m/84'/0'/0' # native segwit, bc1... <- what the descriptor above uses
m/49'/0'/0' # wrapped segwit, 3...
m/44'/0'/0' # legacy, 1...
# If Electrum offers "Detect Existing Accounts", use it: it scans the common
# paths instead of making you guess which one holds the money. The derivation path has to match too. A BIP-39 phrase does not carry one, so a correct phrase read at the wrong path also produces a valid, empty wallet — the same symptom from a different cause.
A single key: sweep or import
# Sweep: spend everything the key controls into an address your existing
# Electrum wallet already owns. The key does not become part of the wallet,
# so your current seed still backs up the funds afterwards.
electrum sweep <the_WIF_private_key> <a_destination_address_of_your_wallet>
# It returns a signed transaction and does NOT broadcast it. That is on you.
# Import is the other thing entirely: the key joins a wallet that has no
# seed at all. Nothing regenerates it. The wallet file is the only backup
# that exists, and losing it loses the coins. Sweep unless you have a reason not to. It ends with the coins under a key your seed already covers; import ends with a wallet whose only backup is a file.
Pointing Electrum at your own node
A public Electrum server answers your queries by being told which addresses you care about. That hands its operator the map: every address in your wallet, grouped, linked to each other and to your IP. Running the node was the point of this whole page, and connecting the wallet on top of it to somebody else's server gives the privacy back at the last step.
# Bitcoin Core does not speak the Electrum protocol. An indexer sits in
# between — ElectrumX or Fulcrum — reading your node and answering Electrum's
# address queries. Both need every block, so the pruned node configured
# earlier on this page cannot back one: that is a full chain on disk.
# One session:
electrum --oneserver --server 127.0.0.1:50002:s
# Trailing :s is TLS, :t is plaintext. --oneserver means connect to that
# server and nothing else, which is only safe because it is yours.
# Persisted:
electrum setconfig oneserver true
electrum setconfig server 127.0.0.1:50002:s Setting up ElectrumX or Fulcrum itself is its own walkthrough and is not covered here.