WIF Private Key

Wallet Import Format

A WIF (Wallet Import Format) private key is an address-style format for a private key.

It's used when exporting and importing private keys between bitcoin wallets.

It's mainly a Base58 encoding of the private key, but also includes some extra useful data and a checksum.

tool-676a2e6839c76
Tool Icon

WIF Private Key

Convert between a raw private key and Wallet Import Format.

1 byte
Network
0 bytes
1 byte
Compressed
0 bytes

Base58 encoding of above data

0 characters

Never enter your private key into a website, or use a private key generated by a website. Websites can easily save the private key and use it to steal your bitcoins.

0 secs

A WIF private key is just another way of representing your private key. If you have a WIF private key, you can always convert it back to a raw private key.

Never reveal your WIF private key. A WIF private key is not encrypted in any way, so you need to protect it as much as you would a raw private key.

Benefits

Why do we use WIF private keys?

The main benefit WIF is that it's a Base58 encoding of a private key, so it's shorter and easier to copy.

There are a few other benefits:

Encoding

Convert a private key to WIF

Diagram showing how to convert a private key to WIF.

Converting a raw private key to WIF is fairly straightforward:

  1. Start with a 32-byte hexadecimal private key.
  2. Add a version byte at the start. This indicates whether the private key is being used on mainnet or testnet:
    • 80 = mainnet
    • ef = testnet
  3. Add a compression byte at the end (optional). This indicates whether the private key is being used to create a compressed or uncompressed public key:
    • 01 = compressed public key (most common)
    • (no byte) = uncompressed public key
  4. Create a checksum from the above data and add it to the end.
    • A checksum in Bitcoin is the first 4 bytes of the Hash256 of some data.
  5. Convert all of the above data to Base58.

And there you have a WIF private key.

Code

# ---------
# Functions
# ---------

require 'digest'

# hash256 function (checksums use hash256)
def hash256(hex)
  binary = [hex].pack("H*")
  hash1 = Digest::SHA256.digest(binary)
  hash2 = Digest::SHA256.digest(hash1)
  result = hash2.unpack("H*")[0]
  return result
end

# checksum function
def checksum(hex)
  hash = hash256(hex) # Hash the data through SHA256 twice
  return hash[0...8]  # Return the first 4 bytes (8 characters)
end

# base58 encode function
def base58_encode(hex)
  chars = %w[
      1 2 3 4 5 6 7 8 9
    A B C D E F G H   J K L M N   P Q R S T U V W X Y Z
    a b c d e f g h i j k   m n o p q r s t u v w x y z
]
  base = chars.length

  i = hex.to_i(16)
  buffer = String.new

  while i > 0
    remainder = i % base
    i = i / base
    buffer = chars[remainder] + buffer
  end

  # add '1's to the start based on number of leading bytes of zeros
  leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2

  ("1"*leading_zero_bytes) + buffer
end

# ----------
# WIF Encode
# ----------

# 1. start with a 32-byte hexadecimal private key
privatekey = "ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2" # example, do not use

# 2. add prefix (80 = mainet, ef = testnet)
data = "80" + privatekey

# 3. add compression byte (optional)
data = data + "01"

# 4. add checksum
data = data + checksum(data)

# 5. base58 encode
wif = base58_encode(data)

# result
puts wif #=> L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6
import hashlib # SHA-256 hash function

# 1. start with a 32-byte hexadecimal private key
privatekey = "ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2" # example, do not use

# 2. add prefix (80 = mainet, ef = testnet)
data = "80" + privatekey

# 3. add compression byte (optional)
data = data + "01"

# 4. add checksum (hash256 the prefix+data+checksum, then take the first 4 bytes)
hash1 = hashlib.sha256(bytes.fromhex(data)).digest() # convert hex string to raw bytes before hashing
hash2 = hashlib.sha256(hash1).hexdigest() # return result as hex string
checksum = hash2[0:8] # checksum is the first 4 bytes
data = data + checksum # add checksum to the end of the data

# 5. set base58 characters
characters = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

# 6. convert hex data to integer so we can convert it to base58
i = int(data, 16)

# 7. create a buffer for holding the base58 string
base58 = ''

# 8. keep dividing the integer by 58 and taking the remainder to work out each base58 character
while i > 0:
  i, remainder = divmod(i, 58) # divide and get the remainder
  base58 = characters[remainder] + base58 # use the remainder to get the character, and add it to the start of the string

# note: during normal base58 encoding you convert leading 00 bytes in hex to 1s in base58, but leading zero bytes will not be present when creating a wif private key so we're skipping that step here

# 9. show result (wif private key)
print(base58) #=> L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6
  • On mainnet, a WIF should start with a K, L, or 5.
  • On testnet, a WIF should start with a c or a 9.

Decoding

Convert from WIF to a private key

Diagram showing how to convert a WIF to a private key.

It's easy enough to extract a raw private key from WIF:

  1. Start with the 51 or 52 character WIF private key.
  2. Decode it from Base58.
    • This will give you 37 or 38 hexadecimal bytes.
  3. The private key is contained within bytes 1-33 (i.e. ignore the first 2 characters, then take the next 64 characters).

You can also extract the extra data from the WIF private key (if you need to):

Code

# ---------
# Functions
# ---------

# base58 decode function
def base58_decode(base58_string)

  # base58 characters
  base58_chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

  # set starting integer value
  integer = 0

  # run through each character in the string from right to left
  base58_string.reverse.each_char.with_index do |char, index|
  
    # get the position of the character in the list of base58 characters
    char_index = base58_chars.index(char)
    
    # check its a valid base58 character
    if !char_index
     puts "Not a valid Base58 character: #{char}"
     exit
    end
    
    # multiply the position of the character by a power of 58 (increasing for the index of each character)
    integer += char_index * (58**index)
  end
  
  # convert the integer to a hexadecimal string
  hexadecimal = integer.to_s(16)
  
  # pad out to make sure it's an even number of bytes
  if hexadecimal.bytesize.odd?
    hexdecimal = "0" + hexadecimal
  end
  
  # ignore empty base58 strings
  if hexadecimal == "00"
    hexadecimal = ""
  end
  
  # count the number of leading 1's
  leading_zero_bytes = (base58_string.match(/^([1]+)/) ? $1 : '').size
  
  # convert leading 1's to leading 00's
  if leading_zero_bytes > 0
    hexadecimal = ("00" * leading_zero_bytes) + hexadecimal
  end
  
  # return hexadecimal bytes
  return hexadecimal
end

# ----------
# WIF Decode
# ----------

# 1. start with a wif private key
wif = "L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6" # example, do not use

# 2. base58 decode
data = base58_decode(wif)

# 3. extract the private key (bytes 1 to 33)
privatekey = data[2...66]

# result
puts privatekey #=> ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2
# 1. start with a wif private key
wif = "L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6" # example, do not use

# 2. set base58 characters
characters = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

# 3. convert base58 string to integer

# 3a. reverse the base58 string so we can work from right to left
wif = wif[::-1]

# 3b. set starting value
sum = 0

# 3c. run through each character in the wif private key (the base58 string)
for i, c in enumerate(wif):

  # 3d. get the index number of this character from the list of base58 characters
  for index, base58_character in enumerate(characters): # this is a basic way to find the key from the list, but it works
    if (c == base58_character):
      character_index = index
      break # stop looking

  # 3e. multiply the index of the character by a power of 58 (increasing the power based on the position of the character in the base58 string), then add this to the sum
  sum += character_index * (58**i)

# 4. convert integer to a hexadecimal
hexadecimal = hex(sum)[2:] # remove the 0x prefix using [2:]

# note: during normal base58 decoding you convert leading 1s in base58 to leading 00 bytes in hex, but wif private keys never start with a 1 so we're skipping that step here

# 5. extract the private key (ignore the prefix, compression byte, and checksum)
privatekey = hexadecimal[2:66]

# 6. pad out the private key so it always shows as 32 bytes (64 hexadecimal characters)
privatekey = privatekey.zfill(64) # zfill pads with zeros at the start to the desired length

# 6. show result
print(privatekey) #=> ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2

Usage

Where are WIF private keys used in Bitcoin?

Screenshot of the private key sweep tool in Sparrow Wallet.
Sparrow Wallet requires you to use WIF when importing private keys.

As mentioned, WIF is used when you're exporting or importing private keys between wallets.

Some examples of popular wallets that use WIF are:

From my experience it's more common for wallets to require you to work with WIF private keys as opposed to allowing you to import/export raw private keys.

History

When were WIF private keys introduced in Bitcoin?

Pieter Wuille created the WIF format in 2011 for importing and exporting private keys via Bitcoin Core.

https://github.com/bitcoin/bitcoin/pull/574

Thanks to Murch and Ava Chow for the help with the origin of WIF private keys.

Summary

WIF is just designed to be a more user-friendly encoding of a private key.

You can spot a WIF private key because it's a Base58 string and starts with a K or L. On the odd occasion it will start with a 5 (if you're working with uncompressed public keys for some reason). For example:

These are static examples of WIF private keys. Do not use them.

So if you're working on mainnet with compressed public keys like a normal person, look for the K or L at the start.

From my experience it can be a bit annoying to have to convert between raw private key and WIF when you want to import a private key into a wallet, but it's common practice for wallets to work with WIF instead of raw private keys, so you might as well get used to it.

If you're working with raw private keys locally (and occasionally want to import/export them), it's a good idea to write your own WIF encoder/decoder. The last thing you want to do is trust a website or tool to handle your private keys, and it's not hard to create a tool that converts between WIF and a raw private key yourself.

It will save time and stress later on. Trust me.

Because as that famous old saying goes, "Not Your Own WIF Tool, Not Your Coins".

Or something like that.

Resources

Tools