Once upon a time, people using Bitcoin had to generate brand new private key for every new Bitcoin address. And remember all the private keys they generated. It was inconvenient and dangerous. Then in 2012, hierarchical deterministic wallets described in BIP 32 came to save the mankind. Now one has to remember only one value, the seed, and everything else can be calculated from it. This article attempts to bring understanding how BIP 32 functions by implementing it.
The article is part of BIP by BIP series. The complete Haskell code is located in fossil repository that can be viewed online or obtained locally by following these steps. You need to have cabal and GHC installed for it:
fossil clone https://jirijakes.com/code/bip-by-bip
cd bip-by-bip
ls src/BipByBip/Bip32.hs
cabal run bip32-test
Cryptography Link to heading
Throughout the implementation, we will need a few basic cryptographic functions. Let us delegate them all to cryptonite library.
-- | Calculates HMAC-SHA512 for secret key and message.
hmacSha512 :: BS.ByteString -> BS.ByteString -> HMAC SHA512
hmacSha512 = hmac
-- | Calculates SHA256(SHA256(.)) of the message.
doubleSha256 :: (ByteArrayAccess a) => a -> Digest SHA256
doubleSha256 = sha256 . sha256
-- | Calculates SHA256(.) of the message,
sha256 :: (ByteArrayAccess a) => a -> Digest SHA256
sha256 = hash
-- | Calculates RIPEMD160(SHA256(.)) of the message.
hash160 :: (ByteArrayAccess a) => a -> Digest RIPEMD160
hash160 = hash . sha256
Data types Link to heading
At the end of this endeavor, we want to have a data type representing an extended key and some operations on it. But first we will need a few basic building blocks. Specification of the serialization format will help us understand what types we have to define:
Serialization format
Extended public and private keys are serialized as follows:
- 4 bytes
- version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
- 1 byte
- depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ….
- 4 bytes
- the fingerprint of the parent’s key (0x00000000 if master key)
- 4 bytes
- child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
- 32 bytes
- the chain code
- 33 bytes
- the public key or private key data (serP(K) for public keys, 0x00 || ser256(k) for private keys)
Version bytes Link to heading
Version bytes specify network on which the key was generated and whether the key is private or public.
4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
We also know that the values were not chosen randomly:
Because of the choice of the version bytes, the Base58 representation will start with “xprv” or “xpub” on mainnet, “tprv” or “tpub” on testnet.
Thanks to this choice, when having a Base58-encoded form of an extended key, it is easy to determine network and whether it is private or public key.
We will make our lives a bit simpler by only considering mainnet in this text. We can reuse Haskell’s Word32
type
and also its Binary
instance. This will be later needed for serialization of our extended keys:
-- | Version bytes.
newtype Version = Version Word32 deriving (Binary, Eq, Show)
versionXprv, versionXpub :: Version
versionXprv = Version 0x0488ADE4
versionXpub = Version 0x0488B21E
Depth Link to heading
The type depth indicates the level of derivation, with master node having depth equal to zero
and each child derived from a previous one having depth increased by one. Depth is one byte long and can be represented by Word8
.
Default value, i. e. the depth of master node, is 0
.
1 byte — depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ….
To serialize depth, we directly serialize the underlying one-byte value. The only operation we need to have on
depth is to increase it by one, let’s call this function deeper
. This particular implementation is a bit unsafe
as it may overflow when called on depth 255. Here we rely on the fact that deriving to such a depth is not common.
However, production-ready library should take care of this case.
-- | Level of derivation of an extended key. Master node has depth = 0,
-- each subsequent child has depth increased by one.
newtype Depth = Depth Word8 deriving (Binary, Default, Eq, Show)
-- | Increases depth by one level.
deeper :: Depth -> Depth
deeper (Depth d) = Depth (d + 1)
Key identifiers Link to heading
Extended keys can be identified by the Hash160 (RIPEMD160 after SHA256) of the serialized ECDSA public key K, ignoring the chain code. This corresponds exactly to the data used in traditional Bitcoin addresses. It is not advised to represent this data in base58 format though, as it may be interpreted as an address that way (and wallet software is not required to accept payment to the chain key itself).
Therefore, to obtain a key identifier from public key we have to serialize it
and then apply SHA256 and RIPEMD160. We already have function hash160
that does exactly that.
-- | Identifier of public key.
newtype KeyId = KeyId BS.ByteString
-- | Returns identifier of the given public key.
keyId :: Secp.PubKey -> KeyId
keyId = KeyId . BA.convert . hash160 . Secp.exportPubKey True
We do not derive any class instances at the moment. KeyId
is not going to be serialized and whether it should
have some other instances depends on its future usage.
The function hash160
returns value of type Digest RIPEMD160
, type coming from cryptographic library cryptonite.
Digest a
holds the digest in an opaque way but gives us access to the underlying bytes using a type class
ByteArrayAccess
from package memory. And the function BA.convert
from the same package helps us convert these
bytes into ByteString
.
Fingerprint Link to heading
The first 32 bits of the [key] identifier are called the key fingerprint.
Note that the fingerprint of the parent only serves as a fast way to detect parent and child nodes in software, and software must be willing to deal with collisions. Internally, the full 160-bit identifier could be used.
We have two options how to represent these 32 bits (four bytes) in memory. We can either use Word32
type or ByteString
.
Word32
would certainly be more efficient but without revealing spoilers, it would be harder to manipulate than
ByteString
. Let us choose suboptimal ByteString
for reasons that will be clear later. Perhaps Data.ByteString.Short
would also be usable.
-- | Fingerprint of parent's key.
newtype Fingerprint = Fingerprint BS.ByteString deriving (Eq, Show)
Knowing that fingerprints always have four bytes, we can easily write a Binary
instance. We cannot automatically
derive Binary
in this case because the default instance for ByteString
serializes first the length of the data
and then the data itself. We only need to serialize the four bytes and nothing else.
4 bytes: the fingerprint of the parent’s key (0x00000000 if master key)
instance Binary Fingerprint where
put (Fingerprint bs) = putByteString bs
get = Fingerprint <$> getByteString 4
And while we are at it, let us also create a Default
instance for Fingerprint
, which will represent
fingerprints of master keys, i. e. 0x00000000
:
instance Default Fingerprint where
def = Fingerprint $ BS.pack [0, 0, 0, 0]
We can generate fingerprint by providing either KeyId
or public key directly. Later it will be more convenient
to get fingerprint of a public key rather than key identifier, therefore:
-- | Returns fingerprint of the given public key.
fingerprint :: Secp.PubKey -> Fingerprint
fingerprint pub = let KeyId k = keyId pub in Fingerprint $ BS.take 4 k
Child number Link to heading
4 bytes: child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
Each extended key has 231 normal child keys, and 231 hardened child keys. Each of these child keys has an index. The normal child keys use indices 0 through 231-1. The hardened child keys use indices 231 through 232-1. To ease notation for hardened key indices, a number iH represents i+231.
This description can be hard to follow at this point. Later we will write a function for deriving child extended keys
from a given extended key. Each derived child will be identified by a number called child number. Those are four-byte numbers.
Half of the child numbers, numbered 0 to 231−1 (or 0x00000000
to 0x7FFFFFFF
), are called normal while the rest, numbered
231 to 232−1 (0x80000000
to 0xFFFFFFFF
), are called hardened.
Child numbers are important, human-facing values. To increase readability when used in text, hardened child numbers
are written in form iH or i’, which both mean i+231. Therefore, in order to convert a hardened child number
into this compact notation, we have to subtract 231 or 0x80000000
.
-- | Child number, either normal or hardened.
data ChildNumber = Normal Word32 | Hardened Word32 deriving (Show, Eq)
Although the distinction between normal and hardened child numbers on the level of data type is not strictly necessary, it will make our lives a bit easier when we start deriving children.
We will also need to construct values of this new type. Again, these implementations are not perfectly safe as they would allow to construct child numbers with values greater than 231−1. But they are sufficient for our purpose.
-- | Creates hardened child number.
hardened :: Word32 -> ChildNumber
hardened = Hardened
-- | Creates non-hardened child number.
normal :: Word32 -> ChildNumber
normal = Normal
-- | Creates a child number from its absolute value.
childNumber :: Word32 -> ChildNumber
childNumber n | n >= 0x80000000 = Hardened $ n - 0x80000000
childNumber n = Normal n
Child numbers are serialized in their full form:
instance Binary ChildNumber where
put (Normal w) = put w
put (Hardened w) = put (0x80000000 + w)
get = childNumber <$> get
Chain code Link to heading
Chain codes are 32-byte data that make private and public keys “extended”. They are important part of children derivation, as we will later see. But on the data type level, they are not more than 32 bytes of data:
-- | Chain code of an extended key.
newtype ChainCode = ChainCode BS.ByteString deriving (Eq, Show)
instance Binary ChainCode where
put (ChainCode bs) = putByteString bs
get = ChainCode <$> getByteString 32
Extended key Link to heading
Generic data type Link to heading
We have defined all the components required for extended keys. For the very last piece of data, the key itself, we
will use directly data types from secp256k1-haskell library: Secp.SecKey
and Secp.PubKey
. Since the extended private
key and extended public key differ only in the key part, let us define a data type for extended keys generic in the key:
-- | An extended key.
data Extended key = Extended
{ xVersion :: Version,
xDepth :: Depth,
xFingerprint :: Fingerprint,
xChildNumber :: ChildNumber,
xChainCode :: ChainCode,
xKey :: key
}
deriving (Show, Eq)
Key serialization Link to heading
The important part here is that Secp.SecKey
and Secp.PubKey
do not have built-in instances of Binary
. If we wanted to
create them, we would have to introduce two newtypes wrapping the original types. That would probably bring
some inconvenience.
Alternative approach is to introduce a new type class for types that represent a cryptographic key that can be serialized
and deserialized using Binary
:
-- | Class to mark types that represent a cryptographic key with an ability
-- to serialize and deserialize itself using `Binary`.
class Key k where
getKey :: Get k
putKey :: k -> Put
Serializing and deserializing public key does not have any restrictions, we only have to remember to operate on compressed form of the key:
- ser256(p): serializes the integer p as a 32-byte sequence, most significant byte first.
- serP(P): serializes the coordinate pair P = (x,y) as a byte sequence using SEC1’s compressed form: (0x02 or 0x03) || ser256(x), where the header byte depends on the parity of the omitted y coordinate.
and
- 33 bytes: the public key or private key data (serP(K) for public keys, 0x00 || ser256(k) for private keys)
The secp256k1-haskell library can do this for us, let us use its functions. Not all 32-byte strings are valid public keys, deserialization should fail in such a case:
instance Key Secp.PubKey where
getKey = getByteString 33 >>= maybe (fail "Not a pub key") return . Secp.importPubKey
putKey = putByteString . Secp.exportPubKey True
Private is slightly more complicated. We have to prefix the key with 0x00
(so the length of both private and public
keys are the same 33 bytes) and deserialization will require a few additional validity checks. Let’s start with
serialization, which is straightforward:
instance Key Secp.SecKey where
getKey = undefined
putKey key = putWord8 0 >> putByteString (Secp.getSecKey key)
For deserialization, we have to validate that the key starts with 0x00
and:
- In case parse256(IL) ≥ n or ki = 0, the resulting key is invalid, and one should proceed with the next value for i. (Note: this has probability lower than 1 in 2127.)
The n is order of the curve secp256k1, as stated in specification (PDF) in section 2.4.1. Every Bitcoiner knows its value by
heart: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
. Therefore, the deserialized key must not be 0
and
must be less than n. Now we can finalize the instance:
instance Key Secp.SecKey where
putKey key = putWord8 0 >> putByteString (Secp.getSecKey key)
getKey = do
w <- getWord8
unless (w == 0) $ fail "Private key prefix is not 0"
getByteString 32 >>= either fail return . parsePrivateKey
-- | Attempts to parse given bytes as private key.
parsePrivateKey :: BS.ByteString -> Either String Secp.SecKey
parsePrivateKey bs =
if
| bs == nullkey -> Left "Private key 0 is invalid"
| bs >= nkey -> Left "Private key >= n is invalid"
| otherwise -> maybe (Left "Not a secret key") Right . Secp.secKey $ bs
where
nullkey = BS.pack $ take 32 [0, 0 ..]
nkey = BS.pack [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41]
Extended key serialization Link to heading
We now have available all the tools for serialization and deserialization of extended keys. During deserialization we
perform checks that verify that if depth = 0 then both parent fingerprint and child number are 0x00000000
:
- 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ….
- 4 bytes: the fingerprint of the parent’s key (0x00000000 if master key)
- 4 bytes: child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
instance (Key key) => Binary (Extended key) where
put (Extended v d f n c k) = put v >> put d >> put f >> put n >> put c >> putKey k
get = do
v <- get
d <- get
f <- get
when (d == def && f /= def) $ fail "Zero depth with non-zero parent fingerprint"
n <- get
when (d == def && n /= def) $ fail "Zero depth with non-zero index"
c <- get
Extended v d f n c <$> getKey
Serialization and deserialization of the key is delegated to the instance of Key
we created before. The def
function
obtain default values for the relevant data type: for Depth
(d
), Fingerprint
(f
) and ChildNumber
(n
). Text of BIP32
uses the term index interchangeably with child number.
Concrete data types Link to heading
To make life of users easier, we also define concrete data types for extended private key and extended public key. We can reuse everything we have defined so far:
-- | Extended private key.
newtype XPrv = XPrv (Extended Secp.SecKey) deriving (Binary, Eq, Show)
-- | Extended public key.
newtype XPub = XPub (Extended Secp.PubKey) deriving (Binary, Eq, Show)
Master key Link to heading
The total number of possible extended keypairs is almost 2512, but the produced keys are only 256 bits long, and offer about half of that in terms of security. Therefore, master keys are not generated directly, but instead from a potentially short seed value.
- Generate a seed byte sequence S of a chosen length (between 128 and 512 bits; 256 bits is advised) from a (P)RNG.
- Calculate I = HMAC-SHA512(Key = “Bitcoin seed”, Data = S)
- Split I into two 32-byte sequences, IL and IR.
- Use parse256(IL) as master secret key, and IR as master chain code.
In case parse256(IL) is 0 or parse256(IL) ≥ n, the master key is invalid.
Next, these three points from section about serialization tell us what are some default values for master keys. Since we already
defined default values for their data types (using type class Default
), we can summon them by calling def
.
- 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ….
- 4 bytes: the fingerprint of the parent’s key (0x00000000 if master key)
- 4 bytes: child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
We will not concern ourselves with random generation. The seed will be given. And once we have it, we perform HMAC-SHA512 and split the result into two halves, which will become new private key and new chain code. These two are enough to form new extended private key. The last line is the same check as during private key deserialization. Fortunately, we can reuse everything we have defined previously:
-- | Creates new master key for version bytes using given seed.
masterKey :: BS.ByteString -> Either String XPrv
masterKey seed =
let hmac = BA.convert $ hmacSha512 (BC.pack "Bitcoin seed") seed
(l, r) = BS.splitAt 32 hmac
in XPrv . Extended versionXprv def def def (ChainCode r) <$> parsePrivateKey l
Having this function gives us the ability to generate new extended private key from a seed. Master key, then, allows us to derive virtually unlimited amount of other extended private keys and extended public keys in a deterministic fashion. That is the whole purpose of BIP32 – predictably and repeatedly generate new keys from a single short value.
Deriving children Link to heading
Now we have one extended private key, the master key, and we can use it to derive other private keys and later, after we can also create an extended public key, we can add derivation of public keys, too.
We represent an extended private key as (k, c), with k the normal private key, and c the chain code. An extended public key is represented as (K, c), with K = point(k) and c the chain code.
[…]
Child key derivation (CKD) functions
Given a parent extended key and an index i, it is possible to compute the corresponding child extended key. The algorithm to do so depends on whether the child is a hardened key or not (or, equivalently, whether i ≥ 231), and whether we’re talking about private or public keys.
„Index” is what we call child number. Let us then create a first child key derivation function, one for extended private key.
Private key Link to heading
Private parent key → private child key
The function CKDpriv((kpar, cpar), i) → (ki, ci) computes a child extended private key from the parent extended private key:
- Check whether i ≥ 231 (whether the child is a hardened key).
- If so (hardened child): let I = HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i)). (Note: The 0x00 pads the private key to make it 33 bytes long.)
- If not (normal child): let I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)).
- Split I into two 32-byte sequences, IL and IR.
- The returned child key ki is parse256(IL) + kpar (mod n).
- The returned chain code ci is IR.
- In case parse256(IL) ≥ n or ki = 0, the resulting key is invalid, and one should proceed with the next value for i. (Note: this has probability lower than 1 in 2127.)
Inputs to this function are private key of parent, chain code of parent (in our case, these two will be represented by
an extended key, i. e. XPrv
and XPub
types) and the number of the derived child. Output will be new extended private key.
The derivation may fail. Let us first see the complete function:
1deriveXPrv :: XPrv -> ChildNumber -> Maybe XPrv
2deriveXPrv (XPrv (Extended {xChainCode = ChainCode c, ..})) i =
3 let pub = Secp.derivePubKey xKey
4 d = case i of
5 Hardened _ -> encodeKey xKey
6 Normal _ -> encodeKey pub
7 hmac = hmacSha512 c (d <> BL.toStrict (encode i))
8 (l, r) = BS.splitAt 32 $ BA.convert hmac
9 parsed = either (const Nothing) (Just . Secp.getSecKey) $ parsePrivateKey l
10 tweaked = parsed >>= Secp.tweak >>= Secp.tweakAddSecKey xKey
11 in fmap
12 (XPrv . Extended xVersion (deeper xDepth) (fingerprint pub) i (ChainCode r))
13 tweaked
In the first step, lines 4–6, we need to prepare data for performing HMAC. The data are encoded private
or public key, depending on whether the child number is hardened or not. With the data and key being the current
chain code, we perform HMAC-SHA512(key, data)
. First half of the resulting 64 bytes will be next private key,
second half next chain code (line 8). We have to verify that the next private key is valid (line 9). Before assembling
new extended private key, we need to tweak its private key with previous private key. To tweak a private key means to add
a 32-byte scalar (in our case created from previous private key) modulo n (n is order of the curve).
The new extended private key will have the same version bytes, depth one level higher and fingerprint of the previous public key.
Public key Link to heading
Public parent key → public child key
The function CKDpub((Kpar, cpar), i) → (Ki, ci) computes a child extended public key from the parent extended public key. It is only defined for non-hardened child keys.
- Check whether i ≥ 231 (whether the child is a hardened key).
- If so (hardened child): return failure
- If not (normal child): let I = HMAC-SHA512(Key = cpar, Data = serP(Kpar) || ser32(i)).
- Split I into two 32-byte sequences, IL and IR.
- The returned child key Ki is point(parse256(IL)) + Kpar.
- The returned chain code ci is IR.
- In case parse256(IL) ≥ n or Ki is the point at infinity, the resulting key is invalid, and one should proceed with the next value for i.
In principle, derivation of public child key is not too different from the private one:
deriveXPub :: XPub -> ChildNumber -> Maybe XPub
deriveXPub (XPub (Extended {xChainCode = ChainCode c, ..})) i =
case i of
Hardened _ -> Nothing
Normal _ ->
let hmac = hmacSha512 c (encodeKey xKey <> BL.toStrict (encode i))
(l, r) = BS.splitAt 32 $ BA.convert hmac
parsed = Secp.exportPubKey True <$> Secp.importPubKey l
tweaked = parsed >>= Secp.tweak >>= Secp.tweakAddPubKey xKey
in fmap
(XPub . Extended xVersion (deeper xDepth) (fingerprint xKey) i (ChainCode r))
tweaked
It is not defined for hardened child keys. The value called parsed
is first 32 bytes of hmac parsed as public key
(to verify its validity), having then its binary representation extracted and used as tweak of the original public key
(this operation is described as point(parse256(IL)) + Kpar in the specification).
The new extended public key is formed analogously to extended private key.
Type class for derivation Link to heading
Although having two separate functions for derivation – one for extended private key, another for extended public key –
certainly works, I found it more convenient (the reason will become clear a bit later) to abstract child derivation
into a type class. The name deriveChild1
says that we derive one child with the given number:
-- | Type class for types that can derive children of the same type.
class Derive a where
-- | Derives new child key with the given number.
deriveChild1 :: a -> ChildNumber -> Maybe a
instance Derive XPrv where
deriveChild1 = deriveXPrv
instance Derive XPub where
deriveChild1 = deriveXPub
Private to public Link to heading
BIP 32 specification also contains a subsection titled Private parent key → public child key:
The function N((k, c)) → (K, c) computes the extended public key corresponding to an extended private key (the “neutered” version, as it removes the ability to sign transactions).
- The returned key K is point(k).
- The returned chain code c is just the passed chain code.
To compute the public child key of a parent private key:
- N(CKDpriv((kpar, cpar), i)) (works always).
- CKDpub(N(kpar, cpar), i) (works only for non-hardened child keys).
This tells us that if we want to derive a child extended public key from an extended private key, we should either first get public key from private key and then derive public key or first derive new private key and then get public key from it (which way to choose depends on our situation). In order to do that, we need to be able to get extended public key from an extended private key:
-- | Returns public extended key derived from private extended key.
xprvToXPub :: XPrv -> XPub
xprvToXPub (XPrv Extended {..}) =
XPub
( Extended
versionXpub
xDepth
xFingerprint
xChildNumber
xChainCode
(Secp.derivePubKey xKey)
)
This is simple, we only need to derive public key from inner private key and use the same chain code. Also, since our data type holds the version bytes, we need to change them into the public key version.
Encoding and decoding extended keys Link to heading
This 78 byte structure can be encoded like other Bitcoin data in Base58, by first adding 32 checksum bits (derived from the double SHA-256 checksum), and then converting to the Base58 representation. This results in a Base58-encoded string of up to 112 characters. Because of the choice of the version bytes, the Base58 representation will start with “xprv” or “xpub” on mainnet, “tprv” or “tpub” on testnet.
To Base58 Link to heading
-- | Returns Base58 checksummed reperesentation of a binary representation
-- of the given value.
toBase58 :: (Binary b) => b -> T.Text
toBase58 b =
let payload = BL.toStrict $ encode b
checksum = BA.convert $ BA.takeView (doubleSha256 payload) 4
in T.pack $ BC.unpack $ encodeBase58 bitcoinAlphabet (payload <> checksum)
A checksum of Base58 representation is formed by first 32 bits (four bytes) of the binary data with SHA256 twice applied. To encode the binary data and its checksum, we use external library base58-bytestring. Perhaps in the future, we could also examine how that works.
This new function can be applied to both XPrv
and XPub
, they both have Binary
instances.
From Base58 Link to heading
Decoding Base58 strings is a bit more complicated. Let’s start with defining a generic function that takes a string
and attempts to decode it as Base58, including checksum verification. If the Base58 is correctly decoded, we can try
to decode the result as binary representation of some type b
. In order to do so, we need to make sure the b
has an
instance of Binary
. It is a reverse of toBase58
with multiple checks added:
-- | Attempts to decode Base58 string using an instance if Binary.
decodeWithChecksum :: (Binary b) => T.Text -> Either String b
decodeWithChecksum t =
case decodeBase58 bitcoinAlphabet (BC.pack $ T.unpack t) of
Nothing -> Left "Could not decode input as Base58"
Just bs ->
case decodeOrFail (BL.fromStrict payload) of
Left (_, _, err) -> Left err
Right (_, _, value) ->
if checksum /= BA.convert (BA.takeView (doubleSha256 payload) 4)
then Left "Invalid checksum"
else Right value
where
(payload, checksum) = BS.splitAt (BS.length bs - 4) bs
We could use this function directly to decode extended keys however we would like to first verify that the decoded extended key has correct version bytes. There may be multiple ways how to do it, I chose to write two separate functions that verify first four characters in the Base58-encoded string:
-- | Attempts to decode given string as Base58-encoded extened private key.
decodeXPrv :: T.Text -> Either String XPrv
decodeXPrv t =
if T.take 4 t == T.pack "xprv"
then decodeWithChecksum t
else Left "Does not have prefix 'xprv'"
-- | Attempts to decode given string as Base58-encoded extened public key.
decodeXPub :: T.Text -> Either String XPub
decodeXPub t =
if T.take 4 t == T.pack "xpub"
then decodeWithChecksum t
else Left "Does not have prefix 'xpub'"
The disadvantage of these two functions is that we need to have prior knowledge about which type of extended key is supposed be represented by the Base58 string. We can also have a convenient function that will try all options and will give us the one that first succeeds:
-- | Attempts to decode given string as Base58-encoded extended key.
fromBase58 :: T.Text -> Either String (Either XPrv XPub)
fromBase58 t = (Left <$> decodeXPrv t) <> (Right <$> decodeXPub t)
It is a rather rudimentary function, it will not provide too nice error messages. But it may be useful anyway.
Derivation path Link to heading
We can now derive a single child key of an extended key. However, more commonly we need to recursively derive multiple children from a master key. We could use the child derivation function repeatedly. Or we could write a few helpful types and functions to help us with it.
To shorten notation, we will write CKDpriv(CKDpriv(CKDpriv(m,3H),2),5) as m/3H/2/5. Equivalently for public keys, we write CKDpub(CKDpub(CKDpub(M,3),2),5) as M/3/2/5.
Although BIP 32 does not specify it, this notation is often called derivation path. Letter M is a master key, letter H in subscript denotes a hardened child. In other, future BIPs, an apostrophe is also used.
Ideally, we would like to have a function that would perform multiple child key derivations, according to the derivation path, and provide us with the result. Let’s start by defining a new type representing a derivation path, which is, in essence, a list of child numbers:
-- | Derivation path.
newtype DerivationPath = DerivationPath (NonEmpty ChildNumber) deriving (Show, Eq)
The type tells us that we need to have at least one child number. After all, what use has a path that brings us nowhere?
Derivation path can be represented in a multiple ways. Not only it can be provided as a string (in the form m/3/2/5
) but also
as a single child number (that is also a valid derivation path). Therefore, it may be useful to allow to specify the derivation
path in any of these ways. In order to do so, let us introduce a type class for types that can represent a derivation path
together with two trivial instances:
-- | Class for types that can represent a derivation path.
class AsDerivationPath a where
derivationPath :: a -> Maybe DerivationPath
instance AsDerivationPath DerivationPath where
derivationPath = Just
instance AsDerivationPath ChildNumber where
derivationPath c = Just $ DerivationPath (c :| [])
The operator :|
is a constructor of NonEmpty
.
Now we have a way to represent derivation path and we can revisit our Derive
type class by adding a new function that
would derive child using a derivation path instead of just by one child number:
-- | Type class for types that can derive children of the same type.
class Derive a where
-- | Derives new child key with the given number.
deriveChild1 :: a -> ChildNumber -> Maybe a
-- | Derives new child key determined by the given derivation path.
deriveChild :: (AsDerivationPath p) => a -> p -> Maybe a
deriveChild a p =
derivationPath p >>= (\(DerivationPath cs) -> foldM deriveChild1 a cs)
Using foldM
we recursively apply deriveChild1
to previously derived child along the derivation path. How fortunate are we
that we do not have to change implementation of any instance.
The very last thing we need in this section is the ability to provide derivation path as a string:
instance AsDerivationPath T.Text where
derivationPath t =
case T.split (== '/') t of
(x : rs)
| T.unpack (T.toLower x) == "m" ->
DerivationPath <$> (mapM parseChildNumber rs >>= nonEmpty)
_ -> Nothing
where
parseChildNumber s = case reads $ T.unpack s of
[(n, "")] -> Just $ normal n
[(n, "'")] -> Just $ hardened n
[(n, "h")] -> Just $ hardened n
[(n, "H")] -> Just $ hardened n
_ -> Nothing
instance AsDerivationPath String where
derivationPath = derivationPath . T.pack
Testing Link to heading
So much text and code and we have not yet verified that anything actually works. Luckily the specification offers a few testing cases to verify our implementation. On the other hand, if there is something more boring than writing tests, it is writing about writing tests. And as tests of this code are quite trivial, I refer the readership to the code repository.
One can also use GHCi for a quick test:
$> cabal repl
ghci> :set -XOverloadedStrings
ghci> import BipByBip.Bip32
ghci> fromBase58 "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"