---
title: "Bitcoin Private Key Format: WIF, Base58 and Hex Explained"
url: "https://bip39-phrase.com/private-key-btc/"
language: "en"
published: "2024-05-26T00:00:00+00:00"
updated: "2026-10-16T00:00:00+00:00"
---

[ Keys & Addresses ](/topics/keys-and-addresses/) Published May 26, 2024 Updated October 16, 2026 

# Bitcoin Private Key Format: WIF, Base58 and Hex Explained

![Bitcoin Private Key Format: WIF, Base58 and Hex Explained](/images/blog/private-key-btc-hero.webp) 

A Bitcoin private key is a 256-bit number. The string most wallets show for it is that number in Wallet Import Format (WIF): a version byte, the key, an optional compression flag and a 4-byte checksum, all written in Base58\. This guide takes one key through every step of the encoding, shows how to read a WIF string at a glance, and explains what its checksum can and cannot protect you from. Every key below comes from a published test vector and is public knowledge: never send coins to the addresses they control.

## What a Private Key Is

A private key is any number from 1 to n − 1, where n is the order of secp256k1, the elliptic curve Bitcoin uses:

```text
n = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
```

As raw hexadecimal, a key is 64 characters (32 bytes). The public key and the address are calculated from it, never the other way round. In a modern wallet you rarely meet a key on its own: an HD wallet derives every key from the seed phrase through [BIP32](/bip-32/), and the guide to [exporting a private key](/convert-seed-phrase-to-private-key-guide/) shows which wallets let you see one. When they do, they usually show it in WIF.

## The Base58 Alphabet

Base58 uses 58 characters: all digits and Latin letters except 0 (zero), O (capital o), I (capital i) and l (lowercase L).

| Group     | Characters                | Count |
| --------- | ------------------------- | ----- |
| Digits    | 123456789                 | 9     |
| Uppercase | ABCDEFGHJKLMNPQRSTUVWXYZ  | 24    |
| Lowercase | abcdefghijkmnopqrstuvwxyz | 25    |

Bitcoin Core’s [base58.h](https://github.com/bitcoin/bitcoin/blob/master/src/base58.h) explains the choice: the excluded characters look alike in many fonts, and a string without punctuation is easier to accept as input, survives email line-wrapping and is selected whole by a double-click. Each leading zero byte is written as `1`, which is why legacy addresses, whose version byte is 0x00, start with 1.

## How WIF Encoding Works, Step by Step

The example key is the master private key of BIP32 test vector 1 (seed `000102…0f`), read from the `xprv9s21ZrQH143K3QTDL4…` string published in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki):

1. **Start with the 32-byte key:** `e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35`.
2. **Add the version byte** `0x80` in front for mainnet (`0xef` for testnet).
3. **Add the compression flag** `0x01` at the end if the key is used with a compressed public key, the default in current wallets.
4. **Compute the checksum:** hash the bytes with SHA-256 twice and keep the first 4 bytes.
5. **Append the checksum** and encode the whole byte string in Base58\. This combination of version byte, double-SHA-256 checksum and Base58 is called Base58Check.

|          | Compressed                                           | Uncompressed                                        |
| -------- | ---------------------------------------------------- | --------------------------------------------------- |
| Payload  | 80 \+ key + 01 (34 bytes)                            | 80 \+ key (33 bytes)                                |
| Checksum | f387029f                                             | 2bf720ff                                            |
| WIF      | L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW | 5KasyVKwgbH5VmDomdJdevZXRMMrbWcePkW17vxeg8daJWoeqHQ |
| Length   | 52 characters                                        | 51 characters                                       |

The same steps in Python, using only the standard library, plus a decoder that reverses them:

```python
import hashlib

B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

def to_wif(key_hex: str, compressed: bool = True, testnet: bool = False) -> str:
    payload = (b"\xef" if testnet else b"\x80") + bytes.fromhex(key_hex)
    if compressed:
        payload += b"\x01"
    payload += hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
    n, out = int.from_bytes(payload, "big"), ""
    while n:
        n, r = divmod(n, 58)
        out = B58[r] + out
    return out  # the payload starts with 0x80 or 0xef, so no leading "1" is needed

def from_wif(wif: str) -> tuple[str, bool]:
    n = 0
    for ch in wif:
        n = n * 58 + B58.index(ch)
    raw = n.to_bytes(38 if len(wif) == 52 else 37, "big")
    payload, checksum = raw[:-4], raw[-4:]
    if hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] != checksum:
        raise ValueError("checksum mismatch: the key was mistyped")
    return payload[1:33].hex(), len(payload) == 34   # (hex key, compressed?)

key = "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
print(to_wif(key))                     # L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW
print(to_wif(key, compressed=False))   # 5KasyVKwgbH5VmDomdJdevZXRMMrbWcePkW17vxeg8daJWoeqHQ
```

## Reading a WIF String at a Glance

Because the version byte and the length are fixed, the first character and the length tell you the network and the compression flag:

| Starts with | Length | Network | Public key   |
| ----------- | ------ | ------- | ------------ |
| 5           | 51     | Mainnet | Uncompressed |
| K or L      | 52     | Mainnet | Compressed   |
| 9           | 51     | Testnet | Uncompressed |
| c           | 52     | Testnet | Compressed   |

A 64-character hex string is the same key without any of this framing. A 111-character string starting with `xprv` is not a single key but a BIP32 extended private key, which derives a whole branch of keys and also uses Base58 with a checksum.

## Compressed or Uncompressed: Why the Flag Matters

The flag does not shrink the private key. It tells the wallet which public key to calculate: the 65-byte uncompressed form or the 33-byte compressed form. The two forms hash to different legacy addresses, so a key imported with the wrong flag shows a different, usually empty, address. SegWit needs the compressed form: under [BIP143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki), transactions that use uncompressed keys in version 0 witness programs are not relayed or mined by default.

WIF does not record the address type either: one compressed key controls a legacy (1…), a nested SegWit (3…) and a native SegWit (bc1q…) address, each with its own balance ([address types explained](/what-is-a-bitcoin-address/)). Electrum therefore writes a script type such as `p2wpkh:` in front of the keys it exports. Bitcoin Core 30.0 removed the old `importprivkey` and `dumpprivkey` RPCs ([release notes](https://bitcoincore.org/en/releases/30.0/)); a descriptor wallet takes the key inside a [descriptor](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) that names the type, such as `wpkh(<WIF>)`, imported with `importdescriptors` and the checksum that `getdescriptorinfo` returns.

## What the Checksum Catches, and What It Doesn’t

The checksum is 32 bits, so a randomly corrupted key passes it only about once in 4.3 billion tries (1 in 2^32). Change one character of `L52Xz…KZeW` and `from_wif` raises an error instead of returning a different key. That protects you from importing a mistyped key, but the checksum only says that something is wrong, not where. It also [does nothing for secrecy](/bip38/): a WIF string is the key itself, and anyone who sees it, photographs it or receives it in a chat can spend the coins it controls.

## A Test Vector from a Seed Phrase

To follow the chain from words to address, take the public BIP39 test phrase (`abandon` eleven times, then `about`) and the path m/84’/0’/0’/0/0\. [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) publishes the result, and we reproduced it from the phrase:

| Field             | Value                                                              |
| ----------------- | ------------------------------------------------------------------ |
| Private key (WIF) | KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d               |
| Private key (hex) | 4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3   |
| Public key        | 0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c |
| Address           | bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu                         |

The WIF starts with K, so it is a compressed mainnet key, as a native SegWit address requires.

## Base58 vs Base64 vs Bech32

|                     | Base58 with checksum                               | Base64                                                                                                  | Bech32 / Bech32m                                         |
| ------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| Alphabet            | 58 characters, no 0, O, I, l                       | 64 characters including + and /, \= as padding                                                          | 32 characters, lowercase, no 1, b, i, o                  |
| Checksum            | 4 bytes of double SHA-256                          | None                                                                                                    | BCH code that always detects up to 4 mistyped characters |
| Used in Bitcoin for | WIF keys, legacy and P2SH addresses, xprv and xpub | PSBT files exchanged as text ([BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)) | Native SegWit (bc1q…) and Taproot (bc1p…) addresses      |

Bech32 ([BIP173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)) replaced Base58 for SegWit addresses: its single-case alphabet is easier to read aloud and type, and its checksum guarantees that small typos are caught.

## Handling WIF Keys Safely

- **Treat a WIF like cash.** It cannot be changed or revoked, so if it may have been seen, move the funds to a new wallet. Never paste it into a website, chat or “support” form.
- **Sweep instead of importing** when a wallet offers it, so the coins end up under the receiving wallet’s own backup.
- **Generate keys locally.** Our [private key generator](/private-key-generator/) creates them in your browser.

## Frequently Asked Questions

### Is a WIF private key the same as a seed phrase?

No. A WIF string is one key and controls only the addresses calculated from it. [A seed phrase recreates every key in an HD wallet](/seed-phrase-vs-private-key/). You cannot get the seed phrase back from a WIF key.

### How do I convert a WIF key to hex?

Decode the Base58 string, check the 4-byte checksum, then drop the version byte, the checksum and the `01` flag if present. The remaining 32 bytes are the hex key; `from_wif` above does exactly that. Do it offline.

### Why does my imported key show an empty balance?

Usually the wallet calculated a different address from the one that holds the coins: the compression flag differs, or the wallet assumed another address type. Look up the funded address in a block explorer and import the key with the matching type, such as Electrum’s `p2wpkh:` prefix or a `pkh()`, `sh(wpkh())` or `wpkh()` descriptor in Bitcoin Core.

[ Back to Blog ](/blog/)
