- HD wallet
- A hierarchical deterministic HD wallet deterministically derives an infinite hierarchy of private and public keys from a single 512 bits seed. The seed itself is derived from a mnemonic and an optional passphrase. Only the mnemonic with an optional passphrase is required to backup, export, and import the entire HD wallet with all derived keys. The key derivation process is deterministic, so the entire hierarchy of private and public keys can be re-derived from the same mnemonic with an optional passphrase at any time. The derived keys are organized in a tree hierarchy with an unlimited depth. Different branches of a tree can be used for selective sharing and different purposes e.g. different accounts, different cryptocurrencies, different accounting purposes.
- Security of derived keys
- While all private and public keys are derived from a single mnemonic with an optional passphrase, all derived keys are as secure as independently generated keys. To an external observer all derived keys look indistinguishable from independently generated keys. There is no feasible way to determine whether a key is a child or a sibling of another key. The main difference between a HD wallet that derives all keys from a single mnemonic with an optional passphrase and a non-deterministic wallet that generates each key independently is that only a single mnemonic with an optional passphrase is required to backup, export, and import a HD wallet, while every independently generated key must be backup as soon as it is generated by a non-deterministic wallet. Generating a new receiving address with a new spending private key for every transaction is recommended to preserve privacy of transactions on a blockchain
- Derivation of public keys
- The hierarchy of public keys to receive funds can be derived in a less secure environment e.g. web server from an extended public key without having access to the corresponding private extended key to generated respective private keys to spend received funds. Later, corresponding private keys can be derived in a more secure environment e.g. hardware wallet to spend received funds
- Seed derivation
- A 512 bits (64 bytes) seed used to derive master extended private and public keys of a HD wallet is derived from a 128 to 256 bits entropy mnemonic with an optional passphrase. The seed derivation process uses the PBKDF2-SHA512 key derivation function with 2048 iterations of the HMAC-SHA512 message authentication code function. The mnemonic is used as a password and the passphrase is used as a salt for the PBKDF2-SHA512 key derivation function. The passphrase, if used, is an additional factor of security. A mnemonic is useless without a passphrase. A backup of a passphrase should be kept in a different place that a backup of a mnemonic. Different passphrases result in a completely different set of derived keys. The salt and the high number of HMAC-SHA512 iterations makes brute force attacks and table-based attacks much more difficult
- Derivation of extended master keys
- The master extended private and public keys are used to derive all other keys in a HD wallet. The master extended private key is used to derive all private keys (and all public keys if necessary) in a HD wallet. The master extended public key can be used to derive only all public keys in a HD wallet. The master extended private and public keys are at the depth 0 and at the index 0. Extended private and public keys in general, and the master extended private and public keys in particular must be kept in secret to prevent others from generating child public keys to receive funds and child private keys to spend funds. A 512 bits seed derived from a mnemonic with an optional passphrase is used as data and a fixed “Bitcoin seed” string is used as a key to the HMAC-SHA512 message authentication code function to produce a 512 bits authenticated hash. The first 32 bytes of the authenticated hash are used as a master private key. The last (next) 32 bytes of the authenticated hash are used as a master chain code needed to derive both child private and child public keys. The master chain code prevents others from deriving child private and child public keys if they gained access to the corresponding parent private or parent public key. A master public key is derived from the master private key using the secp256k1 curve multiplication by multiplying the master private key by the generator point of the secp256k1 elliptic curve. A 64 bytes concatenation of the master private key with the master chain code is called the master extended private key. A 64 bytes concatenation of the master public key with the same master chain code is called the master extended public key
- Derivation of extended private keys
- The extended private keys are identified in the tree hierarchy of a HD wallet by a depth that defines the parent-child positioning of keys, and by an index that defines the sibling positioning between derived keys. A child private key is derived from the parent extended private key, a depth, and an index of the child private key. The parent extended private key is split into a parent private key and a parent chain code. Then, a parent public key is derived from the parent private key using the secp256k1 multiplication. Next, the HMAC-SHA512 authenticated hash is computed using a concatenation of the compressed parent public key and the index of the child private key as data, and the parent chain code as a key. The first 32 bytes of the authenticated hash are used to derive a child private key by adding the parent private key to the first 32 bytes of the authenticated hash. The last (next) 32 bytes of the authenticated hash are used as a child chain code. A 64 bytes concatenation of the child private key with the child chain code is the extended private key. A child public key is derived from the child private key using the secp256k1 multiplication. A 64 bytes concatenation of the child public key with the child chain code is the extended public key
- Leak extended parent public key and any child private key
- An extended parent private key can be calculated from the leaked extended parent public key and any child private key. The extended parent private key is used to derive all child public keys to received funds (track ownership) and all child private keys to spend (steal) funds. The hardened derivation prevents deriving an extended parent private key from a leaked extended parent public key and a leaked child private key
- Derivation of extended hardened keys
- While the extended parent and public
keys are derived using the parent public compressed key, the hardened extended
keys are derived using the parent private key prefixed with the
0x00
prefix. The hardened derivation is more secure as it is not possible to derive child public keys from a hardened extended parent public key. Only a hardened extended parent private key is used to derive hardened child private keys and hardened child public keys. The hardened derivation is more secure than the private derivation and should be used by default, unless there is the need to derive public keys without having access to the corresponding private keys, in which case the public derivation and the private derivation has to be used. While the extended parent and public keys use indices between 0 and 231 - 1, the hardened extended keys use indices between 231 and 232 - 1. A hardened child private key is derived from the parent extended private key, a depth, and an index of the hardened child private key starting from 231. The parent extended private key is split into a parent private key and a parent chain code. Next, the HMAC-SHA512 authenticated hash is computed using a concatenation of the parent private key prefixed with the0x00
prefix and the index of the hardened child private key as data, and the parent chain code as a key. The first 32 bytes of the authenticated hash are used to derive a hardened child private key by adding the parent private key to the first 32 bytes of the authenticated hash. The last (next) 32 bytes of the authenticated hash are used as a child chain code. A 64 bytes concatenation of the hardened child private key with the child chain code is the hardened extended private key. A hardened child public key is derived from the hardened child private key using the secp256k1 multiplication. A 64 bytes concatenation of the hardened child public key with the child chain code is the hardened extended public key
- Derivation of extended public keys
- A big advantage of a HD wallet key derivation mechanism is that extended public keys needed to receive funds can be derived in a less secure environment e.g. web server without the corresponding extended private keys needed to spend funds. An child public key is derived from the parent extended public key, a depth, and an index of the child public key. The parent extended public key is split into a compressed parent public key and a parent chain code. Next, the HMAC-SHA512 authenticated hash is computed using a concatenation of the compressed parent public key and the index of the child public key as data, and the parent chain code as a key. The first 32 bytes of the authenticated hash are used to derive a child public key by adding the parent public key to the first 32 bytes of the authenticated hash multiplied by the secp256k1 generator point. The last (next) 32 bytes of the authenticated hash are used as a child chain code. A 64 bytes concatenation of the child public key with the child chain code is the extended public key
- HD path
- A HD path uniquely identifies an extended private or public key in
the hierarchy of derived keys of a HD wallet. A HD path starts from either a
master private key
m
or a master public keyM
and indicates the derivation path by appending derivation segments in the form/index
for the normal derivation or in the form/index'
for hardened derivation. The indices for hardened derivation start from 231, so this number is automatically added to the index of a hardened key e.g./0'
means 0 + 231. Each segment in a HD path represents the next level in the hierarchy of keys and increments the depth by one
HD path | Description |
---|---|
m/0' | The first child private key of the master key (hardened derivation) |
M/1 | The second child public key of the master key (normal derivation) |
- Encoding of extended keys
- A 64 bytes concatenation of a 32 bytes private or
public key with a 32 bytes chain code is called the extended private key or
the extended public key respectively. Extended keys are difficult to manage by
humans. A 128 bytes hex representation of a 64 bytes extended key is quite
large. An encoded extended key includes in order a version indicating whether
the key is a private key or a public key, a depth of the key from the master
key, a hash of the compressed parent public key, an index of the key from a
parent key, a chain code, a private key prefixed with the
0x00
prefix, or a compressed public key, and the error detecting checksum. A 82 bytes concatenation of the above fields in order is encoded using the base58 encoding. The base58 encoding of an extended key is less error prone and represents an extended key in a way that humans can manage more easily. Along with a prefixed private or a compressed public key and the corresponding chain code, an encoded extended key includes the following fields in order
Field | Size | Description |
---|---|---|
Version | 4 bytes | Private key version 0x0488ade4 , encoded prefix xprv |
Public key version 0x0488b21e , encoded prefix xpub | ||
Depth | 1 byte | Depth of a key from the master key |
Parent | 4 bytes | First 4 bytes of the hash of a compressed parent public key |
Index | 4 bytes | Index of a key from the parent key |
Chain Code | 32 bytes | Chain code |
Key | 33 bytes | Private key prefixed with the 0x00 prefix |
Compressed public key | ||
Checksum | 4 bytes | First 4 bytes of the hash of all the above fields |
- Decoding of extended keys
- The decoding of an encoded extended key converts a base58 encoded extended key to its components: a version, a depth, a hash of the compressed parent public key, an index, a chain code, a private key, or a compressed public key, and an error-detecting checksum. The error detecting checksum prevents mistype extended keys from being used by a wallet
ExtKey
type- The
ExtKey
type represents either an extended private key or an extended public key. The extended key type embeds thePrvKey
type, which, in turn, embeds thePubKey
type. The extended key type is a super set of the private key and the public key types. Along with the private key and the public key types, the extended key type contains a 32 bytes HD chain code, a depth of an extended key from the master key, an index of an extended key from the parent key, a base58 encoded extended private keyxprv
, a base58 encoded extended public keyxpub
type ExtKey struct { PrvKey Code []byte // A chain code 32 bytes Depth uint8 // A depth of an extended key from the master key Index uint32 // An index of an extended key from the parent key Xprv string // An encoded HD extended private key Xpub string // An encoded HD extended public key } func NewExtPrvKey( prvd, pubx, puby *big.Int, code []byte, depth uint8, index uint32, ) *ExtKey { prv := NewPrvKey(prvd, pubx, puby) return &ExtKey{PrvKey: *prv, Code: code, Depth: depth, Index: index} } func NewExtPubKey( pubx, puby *big.Int, code []byte, depth uint8, index uint32, ) *ExtKey { pub := NewPubKey(pubx, puby) prv := PrvKey{PubKey: *pub} return &ExtKey{PrvKey: prv, Code: code, Depth: depth, Index: index} }
- Seed derive
- The
SeedDerive
function takes a mnemonic as a password and an optional passphrase as a salt and produces a 512 bits seed by applying the PBKDF2-SHA512 key derivation function to the data and the salt with 2048 iterations of the HMAC-SHA512 message authentication code function. The seed derive function- Create a salt by prepending a fixed “mnemonic” string to the passphrase
- Produce a 512 bits seed by applying the PBKDF2-SHA512 key derivation function using the mnemonic as a password, the salt, and 2048 iterations of the HMAC-SHA512 message authentication code function
func SeedDerive(mnemonic, passphrase string) []byte { salt := []byte("mnemonic" + passphrase) seed := crypto.PBKDF2SHA512([]byte(mnemonic), salt, 2048, 64) return seed }
- Master derive
- The
MasterDerive
function takes a 512 bits seed and derives extended master private and public keys. The master derive function- Produces a 512 bits authenticated hash using the seed as data and a fixed “Bitcoin seed” string as a key
- The first 32 bytes of the authenticated hash is the master private key
- The master public key is derived from the master private key by using the secp256k1 multiplication
- The last (next) 32 bytes of the authenticated hash is the master chain code
- A concatenation of the master private key prefixed with the
0x00
prefix with the master chain code is the extended master private keyxprv
- A concatenation of the compressed master public key with the master chain
code is the extended master public key
xpub
func MasterDerive(seed []byte) *ExtKey { depth, index := uint8(0), uint32(0) hmac := crypto.HMACSHA512(seed, []byte("Bitcoin seed")) prv, code := hmac[:32], hmac[32:] key := KeyDerive(prv) ekey := &ExtKey{PrvKey: *key, Code: code, Depth: depth, Index: index} ekey.Xprv = EkeyEncode(xprvVer, depth, nil, index, code, prv) ekey.Xpub = EkeyEncode(xpubVer, depth, nil, index, code, ekey.Pubc) return ekey }
- Private derive
- The
PrivateDerive
function takes an extended private key, a depth of a child key from the master key, an index of a child key from the parent key, and produces child extended private and public keys. The private derive function- Split the parent extended private key into a 32 bytes parent private key and a 32 bytes parent chain code
- Derive a parent public key from the parent private key by using the secp256k1 multiplication
- Produce an authenticated hash by using the HMAC-SHA512 message authenticated code function with a concatenation of the compressed parent public key and the index of the child key as data, and the parent chain code as a key
- Split the authenticated hash into a 32 bytes child private key base and a 32 bytes child chain code
- Add the parent private key to the child private key base to derive the child private key
- Derive a child public key from the child private key by using the secp256k1 multiplication
- A concatenation of the child private key prefixed with the
0x00
prefix with the child chain code is the extended child private keyxprv
- A concatenation of the compressed child public key with the child chain
code is the extended child public key
xpub
func PrivateDerive(prve []byte, depth uint8, index uint32) *ExtKey { parPrv, parCode := prve[:32], prve[32:] parKey := KeyDerive(parPrv) idx := make([]byte, 4) binary.BigEndian.PutUint32(idx, index) var data bytes.Buffer data.Write(parKey.Pubc) // Parent public compressed data.Write(idx) hmac := crypto.HMACSHA512(data.Bytes(), parCode) prv, code := hmac[:32], hmac[32:] prvi := new(big.Int).SetBytes(prv) prvi.Add(prvi, new(big.Int).SetBytes(parPrv)) prvi.Mod(prvi, ecc.P256k1().Params().N) key := KeyDerive(prvi.Bytes()) ekey := &ExtKey{PrvKey: *key, Code: code, Depth: depth, Index: index} ekey.Xprv = EkeyEncode(xprvVer, depth, parKey.Pubc, index, code, ekey.Prv) ekey.Xpub = EkeyEncode(xpubVer, depth, parKey.Pubc, index, code, ekey.Pubc) return ekey }
- Hardened derive
- The
HardenedDerive
function takes an extended private key, a depth of a child key from the master key, an index of a child key from the parent key, and produces child hardened extended private and public keys. The hardened derive function- Split the parent extended private key into a 32 bytes parent private key and a 32 bytes parent chain code
- Derive a parent public key from the parent private key by using the secp256k1 multiplication
- Create a hardened index by adding 231 to the index
- Produce an authenticated hash by using the HMAC-SHA512 message authenticated
code function with a concatenation of the parent private key prefixed with
the
0x00
prefix and the hardened index of the child key as data, and the parent chain code as a key - Split the authenticated hash into a 32 bytes hardened child private key base and a 32 bytes child chain code
- Add the parent private key to the hardened child private key base to derive the hardened child private key
- Derive a hardened child public key from the hardened child private key by using the secp256k1 multiplication
- A concatenation of the hardened child private key prefixed with the
0x00
prefix with the child chain code is the hardened extended child private keyxprv
- A concatenation of the hardened compressed child public key with the child
chain code is the hardened extended child public key
xpub
func HardenedDerive(prve []byte, depth uint8, index uint32) *ExtKey { parPrv, parCode := prve[:32], prve[32:] parKey := KeyDerive(parPrv) // Only for xprv and xpub index += uint32(1 << 31) // Hardened key index idx := make([]byte, 4) binary.BigEndian.PutUint32(idx, index) var data bytes.Buffer data.WriteByte(0x00) data.Write(parPrv) // Parent private prefixed data.Write(idx) hmac := crypto.HMACSHA512(data.Bytes(), parCode) prv, code := hmac[:32], hmac[32:] prvi := new(big.Int).SetBytes(prv) prvi.Add(prvi, new(big.Int).SetBytes(parPrv)) prvi.Mod(prvi, ecc.P256k1().Params().N) key := KeyDerive(prvi.Bytes()) ekey := &ExtKey{PrvKey: *key, Code: code, Depth: depth, Index: index} ekey.Xprv = EkeyEncode(xprvVer, depth, parKey.Pubc, index, code, ekey.Prv) ekey.Xpub = EkeyEncode(xpubVer, depth, parKey.Pubc, index, code, ekey.Pubc) return ekey }
- Public derive
- The
PublicDerive
function takes an extended public key, a depth of a child key from the master key, an index of a child key from the parent key, and produces an extended child public key. The public derive function- Split the parent extended public key into a 32 bytes parent public key and a 32 bytes parent chain code
- Produce an authenticated hash by using the HMAC-SHA512 message authenticated code function with a concatenation of the compressed parent public key and the index of the child key as data, and the parent chain code as a key
- Split the authenticated hash into a 32 bytes child public key base and a 32 bytes child chain code
- Calculate a point on the secp256k1 elliptic curve by multiplying the child public key base by the generator point
- Add the uncompressed parent public key to the point on the secp256k1 elliptic curve to derive the child public key
- A concatenation of the compressed child public key with the child chain
code is the extended child public key
xpub
func PublicDerive(pube []byte, depth uint8, index uint32) *ExtKey { parPubc, parCode := pube[:33], pube[33:] idx := make([]byte, 4) binary.BigEndian.PutUint32(idx, index) var data bytes.Buffer data.Write(parPubc) // Parent public compressed data.Write(idx) hmac := crypto.HMACSHA512(data.Bytes(), parCode) pb, code := hmac[:32], hmac[32:] pub := new(ecdsa.PublicKey) pub.Curve = ecc.P256k1() pub.X, pub.Y = pub.ScalarBaseMult(pb) parX, parY := ecc.UnmarshalCompressed(ecc.P256k1(), parPubc) pubx, puby := pub.Add(pub.X, pub.Y, parX, parY) ekey := NewExtPubKey(pubx, puby, code, depth, index) ekey.Xpub = EkeyEncode(xpubVer, depth, parPubc, index, code, ekey.Pubc) return ekey }
- Path derive
- The
PathDerive
function takes a mnemonic, an optional passphrase, and a HD path, and derives extended private and public keys as specified by the HD path e.g.m/0'/1
. The path derive function derives a 512 bits seed from the mnemonic and the optional passphrase. Next, the extended master private and public keys are derived from the seed. Then, the HD path is parsed into the derivation segments starting from either the master private keym
or the master public keyM
. Each derivation segment increases by one the depth of the child keys from the master key. Each derivation segment indicates the index of the child key from the parent key and whether the normal derivation or the hardened derivation marked with the prime'
symbol has to be applied. The path derive function process each segment in order starting from the extended master private or public key derived from the seed, which, in turn, is derived from the mnemonic and the optional passphrase. The extended keys derived of the current derivation segment are used as input to derive child extended keys of the next derivation segment- Private derivation
- If a HD path starts from an extended private key
m
, then either the normal private derivation/index
or the hardened private derivation/index'
is possible. The normal private derivation uses the compressed parent public key, while the hardened derivation uses the parent private key prefixed with the0x00
prefix. The input for both the normal private derivation and the hardened private derivation is the same: an extended parent private key, a depth of the child from the master key, and an index of the child private key - Public derivation
- If a HD path starts from an extended public key
M
, only the normal public derivation/index
is possible. The normal public derivation uses only the compressed parent public key. The input for the normal public derivation is an extended parent public key, a depth, and an index of the child public key
The path derive function
- Reject an invalid private
m
or publicM
HD path - Derive a 512 bits seed from the mnemonic and the optional passphrase
- Derive the extended private and public keys from the seed
- For the private derivation
- Parse the private derivation HD path. For each derivation segment
- Increase by one the depth of a child key
- Parse the index of a child key
- Perform the hardened derivation if the derivation segment indicates the hardened derivation, otherwise perform the private derivation by passing the extended parent private key, the depth, and the index in both cases
- Use the extended child private key as input to process the next derivation segment
- Return the final extended private key
- Parse the private derivation HD path. For each derivation segment
- For the public derivation
- Parse the public derivation HD path. For each derivation segment
- Increase by one the depth of a child key
- Parse the index of a child key
- Perform the public derivation by passing the extended parent public key, the depth, and the index
- Use the extended child public key as input to process the next derivation segment
- Return the final extended public key
- Parse the public derivation HD path. For each derivation segment
func PathDerive(mnemonic, passphrase, path string) (*ExtKey, error) { if !rePrvPath.MatchString(path) && !rePubPath.MatchString(path) { return nil, fmt.Errorf("path derive: invalid path: %s", path) } seed := SeedDerive(mnemonic, passphrase) ekey := MasterDerive(seed) depth := uint8(0) if strings.HasPrefix(path, "m") { // Private key derivation for _, seg := range rePrvSeg.FindAllStringSubmatch(path, -1) { depth++ index, _ := strconv.ParseInt(seg[1], 10, 32) hardened := len(seg[2]) != 0 prve := append(ekey.Prv, ekey.Code...) if hardened { ekey = HardenedDerive(prve, depth, uint32(index)) } else { ekey = PrivateDerive(prve, depth, uint32(index)) } } } else { // Public key derivation for _, seg := range rePubSeg.FindAllStringSubmatch(path, -1) { depth++ index, _ := strconv.ParseInt(seg[1], 10, 32) pube := append(ekey.Pubc, ekey.Code...) ekey = PublicDerive(pube, depth, uint32(index)) } } return ekey, nil }
- Extended key encode
- The
EkeyEncode
function takes a 4 bytes version indicating either a private or a public key has to be encoded, a depth of the key from the master key, a compressed parent public key, or a hash of a compressed parent key, ornil
for a master key, an index of the key from the parent key, a chain code, a private key, or a compressed public key, appends an error detecting checksum, and base58 encodes the extended key including all the above fields. The extended key encode function- Write the 4 bytes version indicating whether a private or a public key has to be encoded
- Write the 1 byte depth of the key from the master key
- Write the first 4 bytes of the hash of the compressed parent public key. The
parent hash is a combination of the SHA256 and the RIPEMD160 hash functions
applied in this order to the compressed parent public key. The parent hash
of a master key is
0x00000000
- Write the 4 bytes index of the key from the parent key
- Write either the 33 bytes private key prefixed with the
0x00
prefix or the 33 bytes compressed public key - Write the first 4 bytes of the error detecting checksum. The hash of the error detecting checksum is the double SHA256 hash of the above data
- Base58 encode the 82 bytes resulting data including the checksum
- Return the base58 encoded extended private key or the extended public key
func EkeyEncode( version []byte, depth uint8, parent []byte, index uint32, code, key []byte, ) string { var data bytes.Buffer data.Write(version) data.Write([]byte{depth}) switch { case parent == nil: // Master key data.Write([]byte{0x00, 0x00, 0x00, 0x00}) case len(parent) == 4: // Parent hash data.Write(parent) default: // Parent pubc hash := crypto.RIPEMD160(crypto.SHA256(parent)) data.Write(hash[:4]) } idx := make([]byte, 4) binary.BigEndian.PutUint32(idx, index) data.Write(idx) data.Write(code) if len(key) == 32 { // Private key data.Write([]byte{0x00}) } data.Write(key) csum := crypto.SHA256(crypto.SHA256(data.Bytes())) data.Write(csum[:4]) str := crypto.Base58Enc(data.Bytes()) return str }
- Extended key decode
- The
EkeyDecode
function takes an encoded extended private or public key and decodes the key into the extended private or public key, the depth of the key from the master key, and the index of the key from the parent key. The extended key decode function checks the embedded error detecting checksum and rejects invalid keys. The extended key decode function- Base58 decode the encoded extended private or public key
- Reject an invalid key if the embedded checksum does not match the computed checksum
- Parse the 4 bytes version
- Parse the 1 byte depth of the key from the master key
- Parse the 4 bytes hash of the compressed parent public key
- Parse the 4 bytes index of the key from the parent key
- Parse the 32 bytes chain code
- Parse either the 33 bytes private key prefixed with the
0x00
prefix, or the 33 bytes compressed public key - Return either the extended private key or the extended public key
func EkeyDecode(str string) (*ExtKey, error) { data, err := crypto.Base58Dec(str) if err != nil { return nil, err } csum := data[78:] hash := crypto.SHA256(crypto.SHA256(data[:78])) if !slices.Equal(hash[:4], csum) { return nil, fmt.Errorf("extended key decode: invalid checksum") } version := data[:4] depth := uint8(data[4]) parent := data[5:9] index := binary.BigEndian.Uint32(data[9:13]) code := data[13:45] if slices.Equal(version, xprvVer) { // Decode a private key prv := data[46:78] key := KeyDerive(prv) ekey := &ExtKey{PrvKey: *key, Code: code, Depth: depth, Index: index} ekey.Xprv = EkeyEncode(xprvVer, depth, parent, index, code, ekey.Prv) ekey.Xpub = EkeyEncode(xpubVer, depth, parent, index, code, ekey.Pubc) return ekey, nil } else { // Decode a public key pubc := data[45:78] pubx, puby := ecc.UnmarshalCompressed(ecc.P256k1(), pubc) ekey := NewExtPubKey(pubx, puby, code, depth, index) ekey.Xpub = EkeyEncode(xpubVer, depth, parent, index, code, ekey.Pubc) return ekey, nil } }
go build -o wallet; ./hdwallet/cli-test.nu
Show the help and usage instructions of the wallet hd
command
./wallet hd
# NAME:
# wallet hd - Derive extended master and child private and public keys
# USAGE:
# wallet hd [command [command options]]
# COMMANDS:
# seed Derive a seed from a mnemonic and an optional passphrase
# stdin: a mnemonic string
# stdout: a seed in hex
# master Derive extended master private and public keys from a seed
# stdin: a seed in hex
# stdout: extended master private and public keys in hex in YAML
# private Derive extended private and public keys from an extended parent private key
# stdin: an extended parent private key in hex
# stdout: extended child private and public keys in hex in YAML
# hardened Derive hardened extended private and public keys from an extended parent private key
# stdin: an extended parent private key in hex
# stdout: hardened extended child private and public keys in hex in YAML
# public Derive an extended public key from an extended parent public key
# stdin: an extended parent public key in hex
# stdout: an extended child public key in hex in YAML
# path Derive extended private and public keys defined by a HD path
# stdin: a mnemonic string
# stdout: extended private and public keys in hex in YAML
# decode Decode a base58 encoded extended private or public key
# stdin: a base58 encoded extended private or public key
# stdout: an extended private or public key in hex in YAML
# OPTIONS:
# --help, -h show help
Generate a mnemonic with 128 bits of entropy. Derive a 512 bits seed from the mnemonic and an optional passphrase. Pass the seed to derive the extended master private and public keys
$env.PATH = $env.PATH | prepend ("." | path expand)
let mnem = wallet mnemonic generate --bits 128
print $mnem
# inspire fox supply idle museum quarter fade venture hammer setup illegal draft
let seed = $mnem | wallet hd seed --passphrase "secret"
print $seed
# 953de269b05b9a75ee4b0f4e73395f637b42a3a6df5874dc6ff161cc2e064698e03cfca0007705ad3902f66ddb053b03b580fa0cba9d103baa66cd0ea25651c7
let mst = $seed | wallet hd master | from yaml
print $mst
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ 85902f89bf0ab828b418e392eb2adcb3dcdf5d2ff3653b58447b474b17c962fb │
# │ pub │ 04b9954badc8ad563863e8f69f0340f3a8d4e74eb83f93fd6f99d4aaae058b505791c3a0495717cbb │
# │ │ d84643e79571941711591ad05523ef802642c43e9ee5f8314 │
# │ pubc │ 02b9954badc8ad563863e8f69f0340f3a8d4e74eb83f93fd6f99d4aaae058b5057 │
# │ code │ e57eb516b3bf29e41be3fa99eb9eaa299d5cc43bae0bac52deeb58e1bc9868ec │
# │ depth │ 0 │
# │ index │ 0 │
# │ xprv │ xprv9s21ZrQH143K4LswDQrMzrVaj6b5t48daRD3oV2Kk8bUvU1iAFga1gQzAzYA1mHQYyzwMKELusAEv │
# │ │ mVFq9fRwZx7U7WsF8cexRx5kgj4S3j │
# │ xpub │ xpub661MyMwAqRbcGpxQKSPNMzSKH8RaHWrUwe8ebsRwJU8ToGLrhnzpZUjU2FxqEeWM4DQbXA461gbyi │
# │ │ w3XVaa4Yixw1kKqC2i4rDQtzeUtc2A │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Pass the extended master private key to the private derivation to derive the extended private and public keys of the depth 1 and the index 0
let prve = $mst.prv ++ $mst.code
let prv = $prve | wallet hd private --depth 1 --index 0 | from yaml
print $prv
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ b3d3066c86d78e2abb56906b4218f52faccf40dba298ce4eb8424ba49049549f │
# │ pub │ 049075cfc92e3960e65d7b562f15484627c11765be4cc970138b1fe7614e78611597a400aff5b8368 │
# │ │ d897ff7b164639ec4d6c994ce20ec2488ddfb4ac610c52d8c │
# │ pubc │ 029075cfc92e3960e65d7b562f15484627c11765be4cc970138b1fe7614e786115 │
# │ code │ d619c8594fd251953ecaf5427dcf5e5103f71f7ccd7f95c1efd396d103215220 │
# │ depth │ 1 │
# │ index │ 0 │
# │ xprv │ xprv9vewQDR7GSxHmbd17QusMZyKksczNVxLQCok1DcaLjnUvhS4xwibCKj6jVR2D6PEGUmDrkeGpZdf8 │
# │ │ wfEc6QcfcfQk1tUaUG4LBivUmttvv9 │
# │ xpub │ xpub69eHoix16pWaz5hUDSSsihv4JuTUmxgBmRjLoc2Bu5KToVmDWV2qk83aakBDKfAibMs9Jo1viGnXL │
# │ │ YvhZam76iKVz1RJc4k2ZPnp19A7qq7 │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Pass the extended master private key to the hardened derivation to derive the hardened extended private and public keys of the depth 1 and the index 0. Note, that the keys from the private derivation and the keys from the hardened derivation are different. Also note, that the index of the hardened key starts from 231
let hrd = $prve | wallet hd hardened --depth 1 --index 0 | from yaml
print $hrd
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ cd9d4def320fd514ce41863f2974be5b9e899efa3a2b6ac87b07f56c9ceb3af4 │
# │ pub │ 0415b96c5f8897abffd58da78f2e53b1714f3b6de90c738db6d40c3b165917d799b97d4ccae3bf649 │
# │ │ 09869e55d7e43bfe886a9f3924a5a140308a3174fa4d0f154 │
# │ pubc │ 0215b96c5f8897abffd58da78f2e53b1714f3b6de90c738db6d40c3b165917d799 │
# │ code │ b509e6dc4aa193c5b6bd1b7d16fc0608d7ce8eab345091906b40a843efec7cc0 │
# │ depth │ 1 │
# │ index │ 2147483648 │
# │ xprv │ xprv9vewQDRFc7VFwYpTerFSLbMKxXktsnTsvjGAfmdvjSMd8yiFNCdiNgvZKngY4bQbKG1YEDzoABMSY │
# │ │ 7BkLQt6tRzCWnDzLrApQTjVXuMXKaV │
# │ xpub │ xpub69eHoix9SV3ZA2tvksnShjJ4WZbPHFBjHxBmUA3YHmtc1n3PujwxvVF3B2KKH3feV4vcZg1HNpTj3 │
# │ │ FgaMwuUM4srVNneo8D9boJr1VpsUBC │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Pass the extended master public key to the public derivation to derive the extended public key of the depth 1 and the index 0. Note, that the extended public key of the public derivation is equal to the extended public key of the private derivation
let pube = $mst.pubc ++ $mst.code
let pub = $pube | wallet hd public --depth 1 --index 0 | from yaml
print $pub
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ pub │ 049075cfc92e3960e65d7b562f15484627c11765be4cc970138b1fe7614e78611597a400aff5b8368 │
# │ │ d897ff7b164639ec4d6c994ce20ec2488ddfb4ac610c52d8c │
# │ pubc │ 029075cfc92e3960e65d7b562f15484627c11765be4cc970138b1fe7614e786115 │
# │ code │ d619c8594fd251953ecaf5427dcf5e5103f71f7ccd7f95c1efd396d103215220 │
# │ depth │ 1 │
# │ index │ 0 │
# │ xpub │ xpub69eHoix16pWaz5hUDSSsihv4JuTUmxgBmRjLoc2Bu5KToVmDWV2qk83aakBDKfAibMs9Jo1viGnXL │
# │ │ YvhZam76iKVz1RJc4k2ZPnp19A7qq7 │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Keccak256 hash a “transaction”. ECDSA sign the hash with the private key derived using the private derivation. Verify the signature using the public key derived using the public derivation. Confirm that that signature is valid, while the private signing key and the public verifying keys have been derived using different derivation paths: the private derivation and the public derivation
let hash = "transaction" | wallet keccak256
print $hash
# bb2a99297e1d12a9b91d4f90d5dd4b160d93c84a9e3b4daa916fec14ec852e05
let sig = $hash | wallet ecdsa sign --prv $prv.prv
print $sig
# 21cabdea3a37e5642b16e0e345446de3fcebf46829bf2d39e2611d34c38925ea1ce624c827614a7e20e7018142f9afcfed413fa8408920d5cfac2c5cb62edc3d00
let valid = $hash | wallet ecdsa verify --sig $sig --pub $pub.pubc | into bool
print $valid
# true
Derive a mnemonic with 128 bits of entropy. Pass the mnemonic along with an
optional passphrase and a HD path m/1/2/3
to derive the extended private and
public keys using the private derivation
$env.PATH = $env.PATH | prepend ("." | path expand)
let mnem = wallet mnemonic generate --bits 128
print $mnem
# solar theory purchase swim memory craft obscure horse crumble casual mixture patch
let prv = $mnem | wallet hd path --passphrase "secret" --path "m/1/2/3"
| from yaml
print $prv
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ 69670b963d137dd82513b022016e84398c9fa9df043876f0822c031c1a7eb4ae │
# │ pub │ 04942054c003684a4327fe57383f8d8036775b779d4fa8f9b9197567a00cb3190e98b0450f9eaef09 │
# │ │ 3d704eacbf6b072bcc3255d4366193ade20728263705ff873 │
# │ pubc │ 03942054c003684a4327fe57383f8d8036775b779d4fa8f9b9197567a00cb3190e │
# │ code │ 15ee7cca39efa21ea59466d237b83bde19472aa98105b62eafb7dbd424fdd19b │
# │ depth │ 3 │
# │ index │ 3 │
# │ xprv │ xprv9ycZe59jJBCFwxrBqNkkndwH8pgnwrqogab13vDqYjifSbH59F2zWbYAkc6odmJNyBXrsptKXGUF8 │
# │ │ SRdqfFSmQNGtGpVcuQrauqw7Xc2bQc │
# │ xpub │ xpub6Cbv3agd8YkZASvewQHm9mt1grXHMKZf3oWbrJdT75FeKPcDgnMF4PrebuQ8aH61yBm1TPfPrLwXa │
# │ │ dMQ6PFHg2uwdVhDwTu2eHKvHPTUbrj │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Pass the mnemonic along with the optional passphrase and a HD path m/1'/2/3
to
derive the extended private and public keys using the hardened derivation for
the first derivation segment and using the private derivation for the next two
derivation segments. Note, that even a single hardened derivation derives
completely different keys
let hrd = $mnem | wallet hd path --passphrase "secret" --path "m/1'/2/3"
| from yaml
print $hrd
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ af2f45c6183270c9bf9d4f8a6b4c9869cad3698aaa089614790b6159735a1565 │
# │ pub │ 043b5b3b087fa7c829e9655a7958c419979c1b9509eee48ee8e8f40d05b22b96b7f3f5367703b3954 │
# │ │ 2599b14ac6cda26670ada1cc3bca81cb70db1d0dd32fe5378 │
# │ pubc │ 023b5b3b087fa7c829e9655a7958c419979c1b9509eee48ee8e8f40d05b22b96b7 │
# │ code │ 279fcf8e47a09ae23223b12edadcc951913377c928c2fef5dd2006ba12e3187d │
# │ depth │ 3 │
# │ index │ 3 │
# │ xprv │ xprv9y3CbGsDkQLEH554QxZ9gA3C3bE2whPB8wHeooMpKDL1UD1eZP54FmEFR1z9gKTpQz9Yxcn3w8eWv │
# │ │ jQCKshCmW4Y8ejRxhopi2Lfq8S2mBw │
# │ xpub │ xpub6C2YznQ7amtXVZ9XWz6A3Hyvbd4XMA72WADFcBmRsYrzM1Lo6vPJoZYjGG8uSd76XoTdZRm5UHWSg │
# │ │ JdjQYZmDwFPUgzrGHCbwwQ3xz4QPU1 │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Pass the mnemonic along with the optional passphrase and a HD path M/1/2/3
to
derive the extended public key using the public derivation. Note, that the
extended public key of the public derivation is equal to the extended public key
of the private derivation, as the HD path is the same, however derivation
mechanisms are different
let pub = $mnem | wallet hd path --passphrase "secret" --path "M/1/2/3"
| from yaml
print $pub
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ pub │ 04942054c003684a4327fe57383f8d8036775b779d4fa8f9b9197567a00cb3190e98b0450f9eaef09 │
# │ │ 3d704eacbf6b072bcc3255d4366193ade20728263705ff873 │
# │ pubc │ 03942054c003684a4327fe57383f8d8036775b779d4fa8f9b9197567a00cb3190e │
# │ code │ 15ee7cca39efa21ea59466d237b83bde19472aa98105b62eafb7dbd424fdd19b │
# │ depth │ 3 │
# │ index │ 3 │
# │ xpub │ xpub6Cbv3agd8YkZASvewQHm9mt1grXHMKZf3oWbrJdT75FeKPcDgnMF4PrebuQ8aH61yBm1TPfPrLwXa │
# │ │ dMQ6PFHg2uwdVhDwTu2eHKvHPTUbrj │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Derive a mnemonic with 128 bits of entropy. Pass the mnemonic along with an
optional passphrase and a HD path m/1/2/3
to derive the extended private and
public keys using the private derivation
$env.PATH = $env.PATH | prepend ("." | path expand)
let mnem = wallet mnemonic generate --bits 128
print $mnem
# area boring more trick public exist spray flame junk height denial fade
let key = $mnem | wallet hd path --passphrase "secret" --path "m/1/2/3"
| from yaml
print $key
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ 56b69966b1198121cccb9e6cdafeea6e72b98944b3c77dd28cb9a0dfd4e17246 │
# │ pub │ 046174a7849c1386ab1593e427939fb21ad18efe09b667b86250115b586e732b7e5bfab6721291002 │
# │ │ e3209a68aa659250d5dcfe6c6101f8085dae8f8e8b588b315 │
# │ pubc │ 036174a7849c1386ab1593e427939fb21ad18efe09b667b86250115b586e732b7e │
# │ code │ 6719d551409c263206ec17bef32babc88dc97e7b0bba5c484cee2c5a7fa722ee │
# │ depth │ 3 │
# │ index │ 3 │
# │ xprv │ xprv9yzG3jBuhbgwLHF4ybkwjngykRSC7bgW934mQ3FUiho5P4AW6Bcs1oxFnfijomRjPvpRrjNFisUXo │
# │ │ 4x3aek1pipoE6FjFSkABaUcPeDJkuA │
# │ xpub │ xpub6CycTEioXyFEYmKY5dHx6vdiJTGgX4QMWFzNCRf6H3L4FrVediw7ZcGjdxmyprQoyVdcAn3Dj1wBT │
# │ │ akA49K368r9xdrceaoWevPgD1ih33Z │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Decode the encoded private key xprv
of the extended private key. Note, that
the decoded private key is equal to the extended private key
let prv = $key.xprv | wallet hd decode | from yaml
print $prv
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ prv │ 56b69966b1198121cccb9e6cdafeea6e72b98944b3c77dd28cb9a0dfd4e17246 │
# │ pub │ 046174a7849c1386ab1593e427939fb21ad18efe09b667b86250115b586e732b7e5bfab6721291002 │
# │ │ e3209a68aa659250d5dcfe6c6101f8085dae8f8e8b588b315 │
# │ pubc │ 036174a7849c1386ab1593e427939fb21ad18efe09b667b86250115b586e732b7e │
# │ code │ 6719d551409c263206ec17bef32babc88dc97e7b0bba5c484cee2c5a7fa722ee │
# │ depth │ 3 │
# │ index │ 3 │
# │ xprv │ xprv9yzG3jBuhbgwLHF4ybkwjngykRSC7bgW934mQ3FUiho5P4AW6Bcs1oxFnfijomRjPvpRrjNFisUXo │
# │ │ 4x3aek1pipoE6FjFSkABaUcPeDJkuA │
# │ xpub │ xpub6CycTEioXyFEYmKY5dHx6vdiJTGgX4QMWFzNCRf6H3L4FrVediw7ZcGjdxmyprQoyVdcAn3Dj1wBT │
# │ │ akA49K368r9xdrceaoWevPgD1ih33Z │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯
Decode the encoded public key xpub
of the extended public key. Note, that
the decoded public key is equal to the extended public key
let pub = $key.xpub | wallet hd decode | from yaml
print $pub
# ╭───────┬───────────────────────────────────────────────────────────────────────────────────╮
# │ pub │ 046174a7849c1386ab1593e427939fb21ad18efe09b667b86250115b586e732b7e5bfab6721291002 │
# │ │ e3209a68aa659250d5dcfe6c6101f8085dae8f8e8b588b315 │
# │ pubc │ 036174a7849c1386ab1593e427939fb21ad18efe09b667b86250115b586e732b7e │
# │ code │ 6719d551409c263206ec17bef32babc88dc97e7b0bba5c484cee2c5a7fa722ee │
# │ depth │ 3 │
# │ index │ 3 │
# │ xpub │ xpub6CycTEioXyFEYmKY5dHx6vdiJTGgX4QMWFzNCRf6H3L4FrVediw7ZcGjdxmyprQoyVdcAn3Dj1wBT │
# │ │ akA49K368r9xdrceaoWevPgD1ih33Z │
# ╰───────┴───────────────────────────────────────────────────────────────────────────────────╯