Extended Keys
Private keys and public keys that you can derive children from.
An extended key is a private key
or public key
that you can use to derive new keys in a hierarchical deterministic wallet.
Therefore, you can have a single extended private key
, and use it as the source for all the child private keys
and public keys
in your wallet. In addition, a corresponding extended public key
will generate the same child public keys
.
1. Master Extended Keys
Your first extended keys (master keys) are created by putting a seed through the HMAC-SHA512 hash function.
You can think of a HMAC as a hash function that allows you to pass data along with in an additional secret key to produce a new set of random bytes.

The HMAC function returns 64 bytes
of data (which is totally unpredictable). We split this in to two halves to create our master extended private key
:
- The left half will be the
private key
, which is just like any other private key. - The right half will be the
chain code
, which is just an extra 32 bytes of random data.
The chain code is required for generating child keys. If you got hold of the private key but not the chain code, you wouldn’t be able to derive the descendant keys (thereby protecting them).
Extended Private Key
So an extended private key
is ultimately just a normal private key
coupled with a chain code
.

Extended Public Key
We can also create a corresponding extended public key
. This just involves taking the private key
and calculating its corresponding public key
, and coupling that with the same chain code
.
And there we have our initial master extended private key
and master extended public key
.
Tip: As you can see, extended keys are nothing special in themselves; they are just a set of normal keys that share the same chain code (an extra 32 bytes of entropy). The real magic of extended keys is how we generate their children.
Code (Ruby)
'openssl' # HMAC
require 'ecdsa' # private key to public key
require
# ----
# Seed
# ----
"67f93560761e20617de26e0cb84f7234aaf373ed2e66295c3d7397e6d7ebe882ea396d5d293808b0defd7edd2babd4c091ad942e6a9351e6d075a29d4df872af"
seed = "seed: #{seed}"
puts
puts
# --------------------
# Generate Master Keys
# --------------------
# seed
# |
# m
# HMAC
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, "Bitcoin seed", [seed].pack("H*")) # digest, key, data
hmac = 0..63] # left side of digest
master_private_key = hmac[64..-1] # right side of digest
master_chain_code = hmac[
# > The SHA512-HMAC function is reused because it is already part of the standard elsewhere, but it takes a key in addition to the data being hashed.
# As the key can be arbitrary, we opted to use to make sure the key derivation was Bitcoin-specific. -- Pieter Wuille
# Get Public Key (multiply generator point by private key)
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(master_private_key.to_i(16)) # multiply generator point by private key
master_public_key = ECDSA::Format::PointOctetString.encode(master_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format
master_public_key =
"master_chain_code: #{master_chain_code}" #=> 463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b
puts "master_private_key: #{master_private_key}" #=> f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9
puts "master_public_key: #{master_public_key}" #=> 0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9 puts
2. Extended Key Tree
All extended keys can derive child extended keys.
extended private keys
can generate child keys with newprivate keys
andpublic keys
.extended public keys
can generate child keys with newpublic keys
only.
Each child also has an index number (up to 2**32
).

For security, you can derive two types of children from an extended private key
:
- Normal - The
extended private key
andextended public key
can generate the samepublic key
.
Indexes0
to2147483647
(the first half of all possible children) - Hardened - Only the
extended private key
can generate thepublic key
.
Indexes2147483648
to4294967295
(the last half of all possible children)
In other words, a hardened child gives you the option of creating a “secret” or “internal” public key, as the extended public key cannot derive them.
3. Child Extended Key Derivation
Both extended private keys
and extended public keys
can derive children, each with their own unique index
number.
There are 3 methods for deriving child keys:
- Normal Child
extended private key
- Hardened Child
extended private key
- Normal Child
extended public key
Tip: Derived child extended keys (and parent keys) are independent of each other. In other words, you wouldn’t know that two public keys in an extended tree are connected in any way.
1. Normal Child extended private key

- Work out the public key. (This is so a corresponding extended public key can put the same data in to the HMAC function when deriving their children.)
- Use an index between
0
and2147483647
. Indexes in this range are designated for normal child extended keys. - Put data and key through HMAC.
- data =
public key
+index
(concatenated) - key =
chain code
- data =
The new chain code is the last 32 bytes of the result from the HMAC. This is just a unique set of bytes that we can use for the new chain code.
The new private key is the first 32 bytes of the result from the HMAC added to the original private key. This essentially just takes the original private key and increases it by a random 32-byte number. We modulus the new private key by the order of the curve to keep the new private key within the valid range of numbers for the elliptic curve.
So in summary, we use the data inside the parent extended private key (public key
+index
, chain code
) and put it through the HMAC function to produce some new random bytes. We use these new random bytes to construct the next private key from the old one.
Code (Ruby)
'openssl' # HMAC
require 'ecdsa' # private key to public key
require
# ---------------------------------
# Normal Child Extended Private Key
# ---------------------------------
# m
# |- m/0
# |- m/1
# |- m/2
# ...
"463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_chain_code = "f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9"
parent_private_key = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9"
parent_public_key = 0 # child index number
i =
# Prepare data and key to put through HMAC function
"H*") + [i].pack("N") # public key + index
data = [parent_public_key].pack("H*") # chain code is key for hmac
key = [parent_chain_code].pack(
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, key, data
hmac = 0..63] # left side of intermediate key [32 bytes]
il = hmac[64..-1] # right side of intermediate key [32 bytes]
ir = hmac[
# Chain code is last 32 bytes
child_chain_code = ir
# Check the chain code is valid.
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
"Chain code is greater than the order of the curve. Try the next index."
raise end
# Calculate child private key
16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + parent_key) % n
child_private_key = (il.to_i(16).rjust(64, '0') # convert to hex (and make sure it's 32 bytes long)
child_private_key = child_private_key.to_s(
# Work out the corresponding public key too (optional)
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # work out the public key for this too
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format
child_public_key =
# Results
"child_chain_code: #{child_chain_code}" #=> 05aae71d7c080474efaab01fa79e96f4c6cfe243237780b0df4bc36106228e31
puts "child_private_key: #{child_private_key}" #=> 39f329fedba2a68e2a804fcd9aeea4104ace9080212a52ce8b52c1fb89850c72
puts "child_public_key: #{child_public_key}" #=> 030204d3503024160e8303c0042930ea92a9d671de9aa139c1867353f6b6664e59 puts
2. Hardened Child extended private key
- Use an index between
2147483647
and4294967295
. Indexes in this range are designated for hardened child extended keys. - Put data and key through HMAC.
- data =
private key
+index
(concatenated) - key =
chain code
- data =
The new chain code is the last 32 bytes of the result from the HMAC.
The new private key is the first 32 bytes of the result from the HMAC added to the original private key. This again just takes the original private key and increases it by a random 32-byte number.
However, this hardened child key was constructed by putting the private key in to the HMAC function (which an extended public key does not have access to), which means that child extended private keys derived in this way will have a public key that cannot be derived by a corresponding extended public key.
Hardened derivation should be the default unless there is a good reason why you need to be able to generate public keys without access to the private key. – Pieter Wuille2
Code (Ruby)
'openssl' # HMAC
require 'ecdsa' # private key to public key
require
# -----------------------------------
# Hardened Child Extended Private Key
# -----------------------------------
# m
# ...
# |- m/2147483648
# |- m/2147483649
# |- m/2147483650
# ...
"463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_chain_code = "f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9"
parent_private_key = 2147483648 # child index number (must between 2**31 and 2**32-1)
i =
# Prepare data and key to put through HMAC function
"00"].pack("H*") + [parent_private_key].pack("H*") + [i].pack("N") # 0x00 + private_key + index
data = ["H*") # chain code is key for hmac
key = [parent_chain_code].pack(
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, key, data
hmac = 0..63] # left side of intermediate key [32 bytes]
il = hmac[64..-1] # right side of intermediate key [32 bytes]
ir = hmac[
# Chain code is last 32 bytes
child_chain_code = ir
# Check the chain code is valid.
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
"Chain code is greater than the order of the curve. Try the next index."
raise end
# Calculate child private key
16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + parent_key) % n
child_private_key = (il.to_i(16).rjust(64, '0') # convert to hex (and make sure it's 32 bytes long)
child_private_key = child_private_key.to_s(
# Work out the corresponding public key too (optional)
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # work out the public key for this too
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format
child_public_key =
"child_chain_code: #{child_chain_code}" #=> cb3c17166cc30eb7fdd11993fb7307531372e565cd7c7136cbfa4655622bc2be
puts "child_private_key: #{child_private_key}" #=> 7272904512add56fef94c7b4cfc62bedd0632afbad680f2eb404e95f2d84cbfa
puts "child_public_key: #{child_public_key}" #=> 0355cff4a963ce259b08be9a864564caca210eb4eb35fcb75712e4bba7550efd95 puts
3. Normal Child extended public key
- Use an index between
0
and2147483647
. Indexes in this range are designated for normal child extended keys. - Put data and key through HMAC.
- data =
public key
+index
(concatenated) - key =
chain code
- data =
The new chain code is the last 32 bytes of the result from the HMAC. This will be the same chain code as the normal child extended private key above, because if you look back you will see that we put the same inputs in to the HMAC function.
The new public key is the original public key point added to the first 32 bytes of the result of the HMAC as a point on the curve (multiply by the generator to get this as a pont).
So in summary, we put the same data and key in to the HMAC function as we did when generating the child extended private key. We can then work out the child public key
via elliptic curve point addition with the same first 32 bytes of the HMAC result (which means it corresponds to the private key
in the child extended private key
).
Code (Ruby)
'openssl' # HMAC
require 'ecdsa'
require
# --------------------------------
# Normal Child Extended Public Key
# --------------------------------
# m -------- p
# |- p/0
# |- p/1
# |- p/3
"463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_chain_code = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9"
parent_public_key = 0 # child index number
i =
if i >= 2**31
"Can't create hardened child public keys from parent public keys."
raise end
# Prepare data and key to put through HMAC function
"H*")
key = [parent_chain_code].pack("H*") + [i].pack("N") # 32-bit unsigned, network (big-endian) byte order
data = [parent_public_key].pack(
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
hmac = 0..63] # left side of intermediate key [32 bytes]
il = hmac[64..-1] # right side of intermediate key [32 bytes]
ir = hmac[
# Chain code is last 32 bytes
64..-1]
child_chain_code = hmac[
if il.to_i(16) >= ECDSA::Group::Secp256k1.order
"Result of digest is greater than the order of the curve. Try the next index."
raise end
# Work out the child public key
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(il.to_i(16)) # convert hmac il to a point
point_hmac = ECDSA::Format::PointOctetString.decode([parent_public_key].pack("H*"), ECDSA::Group::Secp256k1) # convert parent_public_key to a point
point_public = # point addition
point = point_hmac.add_to_point(point_public)
if (point == ECDSA::Group::Secp256k1.infinity)
"Child public key point is at point of infinitiy. Try the next index."
raise end
ECDSA::Format::PointOctetString.encode(point, compression: true).unpack("H*")[0] # encode to compress public key
child_public_key =
"child_chain_code: #{child_chain_code}"
puts "child_public_key: #{child_public_key}" puts
4. Hardened Child extended public key
Not possible.
4. Why does this work?
In other words, how is it possible that a public key
derived from an extended public key
corresponds to a private key
derived from an extended private key
?
Well, for both child extended keys, we are putting the same inputs in the HMAC function, so we’re getting the same data as a result. Using the first 32 bytes of this data (which is basically a number) we then:
- Increase the parent
private key
by this number to create the childprivate key
. - Increase the parent
public key
by the same amount to create the childpublic key
.
And due to the way elliptic curve mathematics works, the child private key
will correspond to the child public key
.
Come again?
First of all, remember that a public key is just the generator point on an elliptic curve multiplied by a private key:
Now, if you increase this private key by a number (i.e. the first 32 bytes of the HMAC result) we get a new private key. When you multiply this new private key by the generator point, we get the new public key:
Similarly, if you take the original public key and add that same number to it (as a point on the curve), you end up with the same new public key as above:
Code (Ruby)
'ecdsa' # (ECDSA Math) sudo gem install ecdsa
require
# Original Private Key and Public Key
12345
private_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_key)
public_key = 1000000) # used to modify the private key and public key independently
number = rand(
# Child Public Key 1: (Private Key + Number) * Generator
private_and_number = private_key + numberECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_and_number)
child_public_key1 =
# Child Public Key 2: Public Key + (Number * Generator)
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(number)
number_point =
child_public_key2 = public_key.add_to_point(number_point)
# Results
ECDSA::Format::PointOctetString.encode(child_public_key1, compression: true).unpack("H*") #=> e.g. 022861a4809b2d8eb9269abea605f47db91deb9f5385bcc10b94aac6a0b81cba3d
puts ECDSA::Format::PointOctetString.encode(child_public_key2, compression: true).unpack("H*") #=> e.g. 022861a4809b2d8eb9269abea605f47db91deb9f5385bcc10b94aac6a0b81cba3d
puts #=> true puts child_public_key1 == child_public_key2
Security Note
Due to the way normal child keys are derived, if you have a extended public key
and any child private key
, it’s possible to work out the parent extended private key
.
In other words, if your extended public key
is publicly known, be very careful not to reveal a child private key
. If you do, anyone can work backwards to calculate the extended private key
and steal the bitcoins from all the child keys at that level in the tree.
Tip: This is why hardened children are useful, because losing a child private key at one level in the tree will never leave the other child private keys at risk of being derived.
Code (Ruby)
'openssl'
require
# ------
# Secret
# ------
"081549973bafbba825b31bcc402a3c4ed8e3185c2f3a31c75e55f423e9629aa3"
parent_private_key =
# --------------------
# Revealed Information
# --------------------
# parent extended public key
"0343b337dec65a47b3362c9620a6e6ff39a1ddfa908abab1666c8a30a3f8a7cccc"
parent_public_key = "1d7d2a4c940be028b945302ad79dd2ce2afe5ed55e1a2937a5af57f8401e73dd"
parent_chain_code =
# child private key
"c41cd73a9df26db3bccfad12e9bbe66d4d619b6c9510f43a618fbef27fa78ce4" # index=0
child_private_key =
# ------------------------------------------
# We can now work out the parent private key
# ------------------------------------------
# 1. Get the left side of the HMAC from the parent extended public key
"H*")
key = [parent_chain_code].pack("H*") + [0].pack("N") # the 0 refers to the same index as the child private key
data = [parent_public_key].pack(OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
hmac = 0..63]
hmac_left = hmac[
# 2. Calculate the parent private key (child private key minus the hmac left)
115792089237316195423570985008687907852837564279074904382605163141518161494337 # order of the curve
n = 16) - hmac_left.to_i(16)) % n
calculated_key = (child_private_key.to_i(
# -------
# Results
# -------
"parent private key: #{parent_private_key.to_i(16)}"
puts
puts"child private key: #{child_private_key.to_i(16)}"
puts "hmac left: #{hmac_left.to_i(16)}"
puts "calculated: #{calculated_key}" #=> 081549973bafbba825b31bcc402a3c4ed8e3185c2f3a31c75e55f423e9629aa3 puts
5. Serialization
An extended key can be serialized to make it easier to pass around. This serialized data contains the private key
/public key
and chain code
, along with some additional metadata.
A serialized key contains the following fields:
4 bytes | Version |
Places “xprv” 0488ade4 or “xpub” 0488b21e at the start.
|
1 byte | Depth | How many derivations deep this extended key is from the master key. |
4 bytes | Parent Fingerprint | The first 4 bytes of the hash160 of the parent’s public key. This helps to identify the parent later. |
4 bytes | Child Number | The index number of this child from the parent. |
32 bytes | Chain Code | The extra 32 byte secret. This prevents others from deriving child keys without it. |
33 bytes | Key |
The private key (prepend 0x00 ) or public key .
|
Notes
Version Bytes:
Master Extended Keys:
In the Key field, a |
A checksum is then added to this data (to help detect errors), before finally converting everything to Base58 (to create a human-friendly extended key format).
An extended private key
looks like this:
xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq
An extended public key
looks like this:
xpub67uA5wAUuv1ypp7rEY7jUZBZmwFSULFUArLBJrHr3amnymkUEYWzQJz13zLacZv33sSuxKVmerpZeFExapBNt8HpAqtTtWqDQRAgyqSKUHu
As you can see they’re pretty long, but that’s because they contain extra useful information about the extended key.
Tip: The fingerprint, depth, and child number are not required for deriving child extended keys – they just help you to identify the current key’s parent and position in the tree.
Note: The 4-byte field for the child number is the reason why extended keys are limited to deriving children with indexes between 0 and 4,294,967,295 (0xffffffff
).
Code (Ruby)
# -----
# Utils - Needed for creating the fingerprint and checksum, and converting hex string to Base58
# -----
'digest'
require
def create_fingerprint(parent_public_key)
Digest::RMD160.digest(Digest::SHA256.digest([parent_public_key].pack("H*"))) # hash160 it
hash160 = 0...4].unpack("H*").join # take first 4 bytes (and convert to hex)
fingerprint = hash160[return fingerprint
end
def hash256(hex)
"H*")
binary = [hex].pack(Digest::SHA256.digest(binary)
hash1 = Digest::SHA256.digest(hash1)
hash2 = "H*")[0]
result = hash2.unpack(return result
end
def checksum(hex)
# Hash the data through SHA256 twice
hash = hash256(hex) return hash[0...8] # Return the first 4 bytes (8 characters)
end
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
16)
i = hex.to_i(String.new
buffer =
while i > 0
@base
remainder = i % @base
i = i / @chars[remainder] + buffer
buffer = end
# add '1's to the start based on number of leading bytes of zeros
/^([0]+)/) ? $1 : '').size / 2
leading_zero_bytes = (hex.match(
"1"*leading_zero_bytes) + buffer
(end
# --------------------------
# Extended Key Serialization
# --------------------------
"0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9" # needed to create fingerprint
parent_public_key =
"05aae71d7c080474efaab01fa79e96f4c6cfe243237780b0df4bc36106228e31" # m/0
chain_code = "39f329fedba2a68e2a804fcd9aeea4104ace9080212a52ce8b52c1fb89850c72" # m/0
private_key =
# version + depth + fingerprint + childnumber + chain_code + key + (checksum)
#
# version: puts xprv or xpub at the start
# depth: how many times this child has been derived from master key (0 = master key)
# fingerprint: created from parent public key (allows you to spot adjacent xprv and xpubs)
# childnumber: the index of this child key from the parent
# chain_code: the current chain code being used for this key
# key: the private or public key you want to create a serialized extended key for (prepend 0x00 for private)
"0488ade4" # private = 0x0488ade4 (xprv), public = 0x0488b21e (xpub)
version = "01"
depth = #=> "018c1259"
fingerprint = create_fingerprint(parent_public_key) "00000000"
childnumber =
chain_code = chain_code"00" + private_key # prepend 00 to private keys (to make them 33 bytes, the same as public keys)
key =
serialized = version + depth + fingerprint + childnumber + chain_code + key
extended_private_key = base58_encode(serialized + checksum(serialized))
"extended_private_key: #{extended_private_key}" #=> xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq puts
Code
The following are complete code snippets for creating, deriving, and serializing extended keys.
Ruby
'openssl' # HMAC
require 'ecdsa' # private key to public key
require
# ----
# Seed
# ----
"67f93560761e20617de26e0cb84f7234aaf373ed2e66295c3d7397e6d7ebe882ea396d5d293808b0defd7edd2babd4c091ad942e6a9351e6d075a29d4df872af"
seed = "seed: #{seed}"
puts
puts
# --------------------
# Generate Master Keys
# --------------------
# seed
# |
# m
# HMAC
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, "Bitcoin seed", [seed].pack("H*")) # digest, key, data
hmac = 0..63] # left side of digest
master_private_key = hmac[64..-1] # right side of digest
master_chain_code = hmac[
# > The SHA512-HMAC function is reused because it is already part of the standard elsewhere, but it takes a key in addition to the data being hashed.
# As the key can be arbitrary, we opted to use to make sure the key derivation was Bitcoin-specific. -- Pieter Wuille
# Get Public Key (multiply generator point by private key)
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(master_private_key.to_i(16)) # multiply generator point by private key
master_public_key = ECDSA::Format::PointOctetString.encode(master_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format
master_public_key =
"master_chain_code: #{master_chain_code}" #=> 463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b
puts "master_private_key: #{master_private_key}" #=> f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9
puts "master_public_key: #{master_public_key}" #=> 0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9
puts
puts
# --------------------------
# Child Extended Private Key
# --------------------------
# m
# |- m/0
# |- m/1
# |- m/2
# .
# .
# .
# |- m/2147483648 (hardened - m/0')
parent_private_key = master_private_key
parent_chain_code = master_chain_code0 # child number
i =
# Normal (parent public key can create child public key for this private key)
if i < 2**31
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(parent_private_key.to_i(16)) # multiply generator point by private key
point = ECDSA::Format::PointOctetString.encode(point, compression: true).unpack("H*")[0] # encode to compressed public key format
point = "H*") + [i].pack("N") # point(private_key) i
data = [point].pack(end
# > This means that it is possible to give someone a parent extended key (public key + chain code), and they can derive all public keys in the chain. -- Pieter Wuille
# Hardened (parent public key cannot create public key for this private key)
if i >= 2**31
"00"].pack("H*") + [parent_private_key].pack("H*") + [i].pack("N") # 0x00 private_key i
data = [end
# > Hardened derivation should be the default unless there is a good reason why you need to be able to generate public keys without access to the private key. -- Pieter Wuille
# > You cannot derive hardened public keys from xpubs, because hardening is specifically designed to make exactly that impossible. -- Pieter Wuille
# Put data and key through hmac
"H*") # chain code is key for hmac
key = [parent_chain_code].pack(OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, key, data
hmac = 0..63] # left side of intermediate key [32 bytes]
il = hmac[64..-1] # right side of intermediate key [32 bytes]
ir = hmac[
# Chain code is last 32 bytes
child_chain_code = ir
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
"Chain code is greater than the order of the curve. Try the next index."
raise end
# Work out the corresponding public key too (optional)
16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + parent_key) % n
child_private_key = (il.to_i(16).rjust(64, '0') # convert to hex (and make sure it's 32 bytes long)
child_private_key = child_private_key.to_s(
if child_private_key.to_i(16) == 0
"Child private key is zero. Try the next index."
raise end
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # work out the public key for this too
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format
child_public_key =
"child_chain_code: #{child_chain_code}"
puts "child_private_key: #{child_private_key}"
puts "child_public_key: #{child_public_key}"
puts
puts
# -------------------------
# Child Extended Public Key
# -------------------------
# m -------- p
# |- p/0
# |- p/1
# |- p/3
parent_public_key = master_public_key
parent_chain_code = master_chain_code0 # child number
i =
if i >= 2**31
"Can't create hardened child public keys from parent public keys."
raise end
"H*")
key = [parent_chain_code].pack("H*") + [i].pack("N") # 32-bit unsigned, network (big-endian) byte order
data = [parent_public_key].pack(
# Put data and key through hmac
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
hmac = 0..63] # left side of intermediate key [32 bytes]
il = hmac[64..-1] # right side of intermediate key [32 bytes]
ir = hmac[
# Chain code is last 32 bytes
64..-1]
child_chain_code = hmac[
if il.to_i(16) >= ECDSA::Group::Secp256k1.order
"Result of digest is greater than the order of the curve. Try the next index."
raise end
# Work out the child public key
ECDSA::Group::Secp256k1.generator.multiply_by_scalar(il.to_i(16)) # convert hmac il to a point
point_hmac = ECDSA::Format::PointOctetString.decode([parent_public_key].pack("H*"), ECDSA::Group::Secp256k1) # convert parent_public_key to a point
point_public = # point addition
point = point_hmac.add_to_point(point_public)
if (point == ECDSA::Group::Secp256k1.infinity)
"Child public key point is at point of infinitiy. Try the next index."
raise end
ECDSA::Format::PointOctetString.encode(point, compression: true).unpack("H*")[0] # encode to compress public key
child_public_key =
"child_chain_code: #{child_chain_code}"
puts "child_public_key: #{child_public_key}"
puts
puts
# --------------------------
# Extended Key Serialization
# --------------------------
# Utils - Needed for creating the fingerprint and checksum, and converting hex string to Base58
# -----
'digest'
require
def create_fingerprint(parent_public_key)
Digest::RMD160.digest(Digest::SHA256.digest([parent_public_key].pack("H*"))) # hash160 it
hash160 = 0...4].unpack("H*").join # take first 4 bytes (and convert to hex)
fingerprint = hash160[return fingerprint
end
def hash256(hex)
"H*")
binary = [hex].pack(Digest::SHA256.digest(binary)
hash1 = Digest::SHA256.digest(hash1)
hash2 = "H*")[0]
result = hash2.unpack(return result
end
def checksum(hex)
# Hash the data through SHA256 twice
hash = hash256(hex) return hash[0...8] # Return the first 4 bytes (8 characters)
end
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
16)
i = hex.to_i(String.new
buffer =
while i > 0
@base
remainder = i % @base
i = i / @chars[remainder] + buffer
buffer = end
# add '1's to the start based on number of leading bytes of zeros
/^([0]+)/) ? $1 : '').size / 2
leading_zero_bytes = (hex.match(
"1"*leading_zero_bytes) + buffer
(end
# ---------
# Serialize
# ---------
# needed to create fingerprint
parent_public_key = master_public_key
# m/0
chain_code = child_chain_code # m/0
private_key = child_private_key
# version + depth + fingerprint + childnumber + chain_code + key + (checksum)
#
# version: puts xprv or xpub at the start
# depth: how many times this child has been derived from master key (0 = master key)
# fingerprint: created from parent public key (allows you to spot adjacent xprv and xpubs)
# childnumber: the index of this child key from the parent
# chain_code: the current chain code being used for this key
# key: the private or public key you want to create a serialized extended key for (prepend 0x00 for private)
"0488ade4" # private = 0x0488ade4 (xprv), public = 0x0488b21e (xpub)
version = "01"
depth = #=> "018c1259"
fingerprint = create_fingerprint(parent_public_key) "00000000" # 4 byte hexadecimal string
childnumber =
chain_code = chain_code"00" + private_key # prepend 00 to private keys (to make them 33 bytes, the same as public keys)
key =
serialized = version + depth + fingerprint + childnumber + chain_code + key
extended_private_key = base58_encode(serialized + checksum(serialized))
"extended_private_key: #{extended_private_key}" puts
PHP
<?php
// Note: Requires https://github.com/Bit-Wasp/secp256k1-php
// ------------------
// Seed to Master Key
// ------------------
$seed = "67f93560761e20617de26e0cb84f7234aaf373ed2e66295c3d7397e6d7ebe882ea396d5d293808b0defd7edd2babd4c091ad942e6a9351e6d075a29d4df872af";
echo "seed: $seed".PHP_EOL;
echo PHP_EOL;
// Hash it through HMAC-SHA512 and split in to two halves
//
// seed
// |
// |----|
// |HMAC|
// |----|
// |
// | priv | chain code |
//
$hmac = hash_hmac("sha512", hex2bin($seed), "Bitcoin seed"); // algo, data, key, raw_output (optional)
$master_private_key = substr($hmac, 0, 64); // first half is private key
$master_chain_code = substr($hmac, 64); // second half is chain code
//
// m (priv, chain_code)
//
echo "master_chain_code: $master_chain_code".PHP_EOL;
echo "master_private_key: $master_private_key".PHP_EOL;
// Use elliptic curve mathematics to go from private key to the public key
//
// priv -> pub
//
$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); // set curve context
$point = null; secp256k1_ec_pubkey_create($context, $point, hex2bin($master_private_key)); // get public key point
$serialized = ''; secp256k1_ec_pubkey_serialize($context, $serialized, $point, SECP256K1_EC_COMPRESSED); // serialize to compressed public key
$master_public_key = bin2hex($serialized);
//
// m (priv, pub, chain_code)
//
echo "master_public_key: $master_public_key".PHP_EOL;
echo PHP_EOL;
// ---------------------------------------------------------
// Parent Extended Private Key to Child Extended Private Key (m/0)
// ---------------------------------------------------------
// m (priv, pub, chain code)
// |
// m/0 (priv, pub, chain code)
$parent_private_key = $master_private_key; // get private key of parent
$parent_chain_code = $master_chain_code; // get chain code of parent
$i = 0; // the index of this child from parent
// Normal Child (extended public key can derive its public keys)
if ($i < 2**31) { // first half of possible indexes are normal children
// Get public key point for the parent private key
$parent_public_key = $master_public_key; // you can derive this from the parent private key using the elliptic curve mathematics above
// The `data` for the upcoming HMAC is the parent public key + index
// data = | parent public key (33 bytes) | i (4 bytes) |
$data = pack("H*", $parent_public_key).pack("N", $i);
}
// Hardened Child (extended public key cannot derive its public keys)
if ($i >= 2**31 ) { // second half of possible indexes are hardened children
// data = | 00 | parent private key (32 bytes) | i (4 bytes) |
$data = pack("H*", "00").pack("H*", $master_private_key).pack("N", $i);
}
// Put data (public key or private key) and chain code through HMAC-SHA512
$hmac = hash_hmac("sha512", $data, hex2bin($parent_chain_code)); // algo, data, key
$left = substr($hmac, 0, 64);
$right = substr($hmac, 64);
// Child chain code is right side of HMAC-SHA512
// m
// |
// m/0 (chain code)
$child_chain_code = $right;
echo "child_chain_code: $child_chain_code".PHP_EOL;
// Check child chain code is valid (not greater than the number of points on the curve)
if (hexdec($child_chain_code) >= 115792089237316195423570985008687907852837564279074904382605163141518161494337) {
throw new Exception("Child chain code is greater than or equal to the order of the elliptic curve. Try next index.");
}
// Calculate child private key (left side of hmac + parent private key) % order
function bchexdec($hex) { // need a utility function to work with addition of large hexadecimal numbers
if(strlen($hex) == 1) {
return hexdec($hex);
else {
} $remain = substr($hex, 0, -1);
$last = substr($hex, -1);
return bcadd(bcmul(16, bchexdec($remain)), hexdec($last));
}
}
function bcdechex($dec) { // need a utility function to convert large integer back to hexadecimal
$last = bcmod($dec, 16);
$remain = bcdiv(bcsub($dec, $last), 16);
if($remain == 0) {
return dechex($last);
else {
} return bcdechex($remain).dechex($last);
}
}
// child private key = (left + parent private key) % order
// m
// |
// m/0 (priv)
$add = bcadd(bchexdec($left), bchexdec($parent_private_key)); // add left side of hmac to parent private key
$mod = bcmod($add, "115792089237316195423570985008687907852837564279074904382605163141518161494337"); // modulus the order of the curve
$child_private_key = str_pad(bcdechex($mod), 64, "0", STR_PAD_LEFT); // ensure it is 64 chars (prepend zeros)
// Check child private key is valid (not zero)
if (hexdec($child_private_key === 0)) {
throw new Exception("Child private key is zero (so it's invalid). Try next index.");
}echo "child_private_key: $child_private_key".PHP_EOL;
// calculate corresponding child public key from this child private key
// m
// |
// m/0 (pub)
$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); // set curve context
$point = null; secp256k1_ec_pubkey_create($context, $point, hex2bin($child_private_key)); // get public key point
$serialized = ''; secp256k1_ec_pubkey_serialize($context, $serialized, $point, SECP256K1_EC_COMPRESSED); // serialize to compressed public key
$child_public_key = bin2hex($serialized);
echo "child_public_key: $child_public_key".PHP_EOL; // note may need to pad hex
echo PHP_EOL;
// -------------------------------------------------------
// Parent Extended Public Key to Child Extended Public Key (m/0)
// -------------------------------------------------------
// m -------- p (pub, chain code)
// |
// p/0 (pub, chain code)
$parent_public_key = $master_public_key; // get public key of parent
$parent_chain_code = $master_chain_code; // get chain code of parent
$i = 0; // the index of this child from parent
// Check that you're not trying to create a hardened child public key (extended public keys cannot access public keys of hardened extended private key children)
if ($i >= 2**31) {
throw new Exception("Cannot create a hardened extended child public key. Hardened children of extended private key are private.");
}
// Put data (parent public key + i) and chain code through HMAC-SHA512
$hmac = hash_hmac("sha512", pack("H*", $parent_public_key).pack("N", $i), hex2bin($parent_chain_code)); // same data as when creating normal child extended private key
$left = substr($hmac, 0, 64);
$right = substr($hmac, 64);
// Child chain code is right side of HMAC-SHA512
// m -------- p
// |
// |p/0 (chain code)
$child_chain_code = $right;
echo "child_chain_code: $child_chain_code".PHP_EOL;
// Check child chain code is valid (not greater or equal to than the number of points on the curve)
if (hexdec($child_chain_code) >= 115792089237316195423570985008687907852837564279074904382605163141518161494337) {
throw new Exception("Child chain code is greater than or equal to the order of the elliptic curve. Try next index.");
}
// Calculate child public key: point(parent public key) + hmac left
// m -------- p
// |
// |p/0 (pub)
// point (parent public key)
$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); // set curve context
$point_public = null; secp256k1_ec_pubkey_parse($context, $point_public, hex2bin($parent_public_key)); // point(parent public key)
// add point(hmac left) to point(parent public key)
($context, $point_public, hex2bin($left)); // add hmac left to the parent public key point
secp256k1_ec_pubkey_tweak_add$add = ''; secp256k1_ec_pubkey_serialize($context, $add, $point_public, SECP256K1_EC_COMPRESSED); // serialize to compressed key
$child_public_key = unpack("H*", $add)[1];
echo "child_public_key: $child_public_key".PHP_EOL;
echo PHP_EOL;
// ----------------------
// Serialize Extended Key
// ----------------------
$parent_public_key = $master_public_key; // needed for creating fingerprint
$chain_code = $child_chain_code;
$private_key = $child_private_key;
// | version | depth | parent fingerprint | index | chain code | key (private or public) |
// | 4 bytes | 1 byte | 4 bytes | 4 bytes | 32 bytes | 33 bytes | = 78 bytes
function create_fingerprint($parent_public_key) {
$hash160 = hash("ripemd160", hash("sha256", hex2bin($parent_public_key), true)); // hash160 the parent public key
return substr($hash160, 0, 8); // return first 4 bytes (8 hex characters)
}
$version = "0488ade4"; // 0488ade4 = private (xprv), 0488b21e = public (xpub)
$depth = "01"; // how many times this child has been derived from master key (0 = master key)
$fingerprint = create_fingerprint($parent_public_key); // fingerprint of parent key - first 4 bytes of hash160(parent public key) (00000000 = master key)
$index = "00000000"; // the index of this child key from the parent (4 byte hexadecimal string)
$chain_code = $chain_code; // chain code for this key
$key = "00".$private_key; // private or public key you want to create a serialized extended key for (prepend 0x00 for private)
// Serialize the data
$serialized = $version.$depth.$fingerprint.$index.$chain_code.$key;
// Create a checksum for it (hash twice through sha256 and take first 4 bytes)
$checksum = substr(hash("sha256", hash("sha256", hex2bin($serialized), true)), 0, 8);
// Base58 encode the serialized+checksum
function base58_encode($hex){
$base58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (strlen($hex) == 0) {
return '';
}
// Convert the hex string to a base10 integer
$num = gmp_strval(gmp_init($hex, 16), 58);
// Check that number isn't just 0 - which would be all padding.
if ($num != '0') {
$num = strtr($num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv', $base58chars);
}else {
$num = '';
}
// Pad the leading 1's
$pad = '';
$n = 0;
while (substr($hex, $n, 2) == '00') {
$pad .= '1';
$n += 2;
}
return $pad . $num;
}
$master_extended_private_key = base58_encode($serialized.$checksum);
echo "master_extended_private_key: $master_extended_private_key".PHP_EOL;
Related
- ECDSA — How to sign (and verify) data using private and public keys.
Links
- BIP 32 (Original specification by Pieter Wuille. This page is just a rewrite of the original BIP.)
- Ian Coleman BIP39 Tool (Fantastic tool for deriving extended keys from a seed)
- Bitcoin Ruby: ext_key.rb (Nice implementation in Ruby)
StackExchange Questions
- What makes an extended public or private key?
- Why hardened xpub key cannot generate child public key?
- What is the difference between MAC and HMAC?