Private Key Tools

WIF → private key HEX (with checksum proof)

Take a Wallet Import Format string apart: Base58Check payload, version byte, the 32-byte key body, the optional compression flag and the 4-byte double-SHA256 checksum — then re-derive the whole key set for the detected network. Mainnet and testnet, compressed and uncompressed, are all accepted.

Base58Check only: 51 or 52 characters from the alphabet 1–9 A–Z a–z, which excludes 0, O, I and l. The 4-byte checksum is verified first; a single mistyped character is rejected with an error instead of producing a wrong key.
No input yet

Enter a value above and press Convert. Not sure what to type? Press “Use example” to load the sample from the placeholder.

How it works

What this page computes

You paste a WIF — a Wallet Import Format string, the thing every wallet since 2011 expects in its "import private key" box. This page takes it apart byte by byte, verifies the checksum that protects it, recovers the 32-byte scalar, reads the compression flag and the network byte, and then re-runs the whole derivation chain on the recovered key.

payload = 0x80 ‖ k (32 bytes) ‖ optional 0x01   →   WIF = Base58Check(payload)
WIF text→ Base58 decode→ payload + 4-byte checksum→ verify SHA256d→ version ‖ key ‖ flag→ addresses

WIF comes in four flavours and all four are accepted here: mainnet compressed (K…/ L…), mainnet uncompressed (5…), testnet compressed (c…) and testnet uncompressed (9…). The network is detected from the version byte and reported, and the address derivation follows the detected network rather than assuming mainnet.

1. What WIF is and why it was invented

In 2011 the only way to move a private key between two Bitcoin clients was to copy 64 hexadecimal characters by hand. That is a terrible transport format, for three reasons:

The Wallet Import Format fixes all three. It wraps the key in Base58Check: a radix-58 alphabet that deliberately omits the four confusable characters 0, O, I and l (and also +, / and =), plus a 4-byte checksum that makes almost every typo detectable before anything is derived.

PropertyWIFBare hex key
Characters51 or 5264
AlphabetBase58: 1–9 A–Z a–z minus 0 O I lhex: 0–9 a–f
Typo detection4-byte SHA256d checksum — a wrong character is rejected with an errornone — a wrong character is a different key
Records the networkyes, version byte 0x80 / 0xEFno
Records compressionyes, trailing 0x01no
Safe to write on paperdesigned for itnot really

2. The exact byte layout

A WIF is nothing more than a Base58Check encoding of a short byte string. For the mainnet compressed case the payload is 0x80 ‖ key ‖ 0x01, 34 bytes in total, and Base58Check appends the 4-byte checksum, giving 38 bytes which encode to 52 Base58 characters. Here is the real demo WIF taken apart byte by byte:

OffsetBytesValue (demo WIF)Meaning
0180version / network byte — mainnet
1 – 32320824c314…07a171cathe private key itself
33101compression flag (present only in the compressed form)
34 – 3743a0a0417checksum = first 4 bytes of SHA256(SHA256(payload))

Two rows deserve emphasis.

The version byte is network metadata, not part of the secret. It tells a wallet which chain the key is for, so that the address derived from it uses the right prefix. Change it and you have a valid WIF for a different network — the same 32 bytes, different addresses.

The trailing 0x01 is not part of the key. It is a one-byte flag that answers a question the 32-byte scalar cannot answer by itself: when this key's public key is serialized, should it be the 33-byte compressed form or the 65-byte uncompressed form? The flag is not a checksum, not a counter and not padding. It has exactly one legal value — 0x01 means "compressed", and its absence means "uncompressed". A 33-byte body ending in any other byte is malformed and is refused. Determining the key, in code, is a length test plus a one-byte test:

rest = payload[1:]                     # strip the version byte
if len(rest) == 33 and rest[-1] == 0x01:
    compressed = True;  key = rest[:32]
elif len(rest) == 32:
    compressed = False; key = rest
else:
    reject("payload is neither 33 nor 34 bytes")

3. The checksum, and how it catches a typo

Base58Check computes the checksum over the payload — the version byte, the key and the flag — and appends its first four bytes to the string that gets Base58-encoded. Verification recomputes it and compares:

checksum = SHA256(SHA256(payload))[0 … 3]   (four bytes = 32 bits)

Because the checksum is part of what the decoder sees, a single wrong character almost always makes the two disagree. The probability that a random typo still matches is 1 in 232, about 1 in 4.3 billion. Here is the demo WIF and what a real near-miss looks like — these are computed values, not illustrations:

InputPayload the decoder seesChecksum in the stringRequired checksumResult
…GK5tVqG (correct) 800824c314…171ca01 3a0a0417 3a0a0417 valid
…GK5tVqH — last character G→H 800824c314…171ca01 (unchanged!) 3a0a0418 3a0a0417 rejected
…GK5tVqF — last character G→F 800824c314…171ca01 3a0a0416 3a0a0417 rejected
two characters swapped in the middle 800824c314772b53…dd97265df501 3a0a0417 a completely different value rejected
last character deleted (51 chars) 02351b1dd8a0f380…cacf94704e84 6f5872d4 a completely different value rejected
an O typed into the middle not decodable at all — rejected

The first near-miss is the most instructive. Changing the final character of a Base58 string only perturbs the last byte of the decoded data, so the payload comes out byte-for-byte identical and only the checksum changes: 3a0a0417 became 3a0a0418, one bit different. The key you would have had is still the right key; the string is still refused. That is the whole point of putting the checksum on the end.

The O row shows the second layer of defence: Base58 has no O, so the string cannot even be decoded. A mistyped paper wallet is caught by the alphabet before the checksum is ever consulted.

4. Prefixes: which network, which form

The version byte is what the first character of the string ultimately reflects. All four variants of the demo key are listed here, each with its real computed checksum:

Version byteFormNetworkFirst charLengthDemo key
0x80uncompressedmainnet5515HssbguBnjbaUVZCsH9YuzUG7T4vYQf51iiodi4izdVy6aVNekU
0x80compressed (+0x01)mainnetK / L52KwVYL77Lcs1ckM39NvGjrtfKhU4KKdR2iB9Wq2FSMN6rbGK5tVqG
0xEFuncompressedtestnet95191eWBRijNxfiSZ4VVd3Tnb2Dm7RdhaCGMfakiLRELNF1saLqhym
0xEFcompressed (+0x01)testnetc52cMrXo27C3vhsunWQmL5sEDAPKhMiz5WinDHywShwrUkrr1RkCJ4i

Their checksums, all computed over the respective payloads: mainnet uncompressed b6f3d7f5, mainnet compressed 3a0a0417, testnet uncompressed 7271604c, testnet compressed 892c3e23.

Why K or L, and not just one letter?

The first character of a Base58 string is the top of a big number, and the compressed mainnet payload sits right at a boundary: the payload's leading byte is always 0x80, but the value of the whole 34-byte number varies with the key, so the most significant Base58 digit can land on K or on L. There is nothing to interpret there — both mean exactly the same thing. Only the version byte carries meaning.

5. The compression flag is in the WIF, not in the key

This is the single most consequential fact on this page. The 32-byte scalar k does not know, and cannot know, whether it should be used with a compressed or an uncompressed public key. That choice lives in the WIF's trailing byte, and it changes the address — because the address is a hash of the serialized public key, and the two serializations are different byte strings.

Compressed WIF — K…

KwVYL77Lcs1ckM39NvGjrtfKhU4KKdR2iB9Wq2FSMN6rbGK5tVqG

public key 02ff812e…67ee80 (33 bytes)
hash160 21f57b6debcfc5b67182dfb4af1641f0a8189012
canonical address 146ZNjH7XTXnMd1ofe3z7CL2i8WM7N2UqT

Uncompressed WIF — 5…

5HssbguBnjbaUVZCsH9YuzUG7T4vYQf51iiodi4izdVy6aVNekU

public key 04ff812e…b424ae (65 bytes)
hash160 3f0e966dc089c611a9c1d03bd4f69ea53b0223f9
canonical address 16kR3eiswUY6tmGnxZhwQmW6LAvTLGWG2G

Same scalar, two strings, two addresses. Both are spendable with the same key; only a wallet that knows both forms will find both. Note too that the SegWit and Taproot address types (bc1q…, bc1p…) are always built from the compressed public key, because witness programs are defined only over compressed keys — the uncompressed form is a legacy path that only ever produces a P2PKH address.

"My wallet shows zero" is usually a compression mismatch

If you import the compressed WIF but the coins were received at the uncompressed address, the wallet will look at the wrong address and show an empty balance. The reverse case is just as common with keys exported before 2012. Do not conclude the funds are lost: derive both forms — which is exactly what the result panel below does — and see which address actually holds them. Also note that you cannot "fix" this by editing the flag and recomputing the checksum: that produces a valid WIF whose address is the other one, which is the correct behaviour but not a recovery.

6. Worked example

For the sample WIF KwVYL77Lcs1ckM39NvGjrtfKhU4KKdR2iB9Wq2FSMN6rbGK5tVqG:

StepValue
Length52 Base58 characters, 38 decoded bytes
Decoded payload (34 bytes)800824c314772b2c2859c889471390a36bf66e53a1ca1642f96f0f2c9307a171ca01
Version byte0x80 → Bitcoin mainnet
Key body (32 bytes)0824c314772b2c2859c889471390a36bf66e53a1ca1642f96f0f2c9307a171ca
Compression suffix0x01 → compressed
SHA256(SHA256(payload))3a0a0417ffa98881ccdd0c7b283639584e0e909389f6bfbbdbac54547d2b732d
Checksum carried in the string3a0a0417 — matches the first 4 bytes above
Recovered scalar0824c314…07a171ca, in range [1, n−1]
Compressed public key02ff812e26116a9aa140abe629e7f8a38401653caf925119def59ed3758c67ee80
Canonical P2PKH address146ZNjH7XTXnMd1ofe3z7CL2i8WM7N2UqT
P2SH-P2WPKH address3DZT76KyKyc5zkzvLugP8umgt4RPY4XPQw
P2WPKH addressbc1qy86hkm0telzmvuvzm76279jp7z5p3yqjnaerk6
P2TR addressbc1pmsrhxnvmj8tjxxu9wj69ntvwvhvr28qufyc26tshz3rjj777tx3sh54u63

Re-encoding the recovered key on the other network gives cMrXo27C3vhsunWQmL5sEDAPKhMiz5WinDHywShwrUkrr1RkCJ4i, whose addresses are micWfnN6LUy38jVRPD2Mw7YMa873zjixGw (compressed) and mmGNLhorkVyMfskQg8gKEgiRCAXAGnxeBv (uncompressed). Identical secret, four different "first addresses" depending on two bytes of metadata.

7. Security and interoperability notes

8. Common mistakes

9. Quick reference

ItemValue
Payloadversion ‖ key or version ‖ key ‖ 0x01
Payload length33 bytes (uncompressed) or 34 bytes (compressed)
Checksumfirst 4 bytes of SHA256(SHA256(payload))
Total decoded length37 or 38 bytes
String length51 or 52 characters
Mainnet version byte0x80 → 5… uncompressed, K…/L… compressed
Testnet version byte0xEF → 9… uncompressed, c… compressed
Compression flag0x01 present = compressed; absent = uncompressed
Encrypted?No. A WIF is plaintext.
Reversible?Yes, with no key — decoding is all that is required