Skip to content

HD keys

HDPrivateKey and HDPublicKey implement BIP32 hierarchical deterministic keys. One seed yields an entire tree of keys, each addressed by a path such as m/44'/236'/0'/0/5. The guide’s HD keys page explains the scheme. The examples here use the BIP32 test vectors, so that you can check the outputs against the specification.

The seed is given as hex. In practice it will come from a mnemonic.

final master = HDPrivateKey.fromSeed('000102030405060708090a0b0c0d0e0f', NetworkType.MAIN);
master.xprivkey; // xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi
master.xpubkey; // xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8

deriveChildKey takes a path, in which an apostrophe marks a hardened step. deriveChildNumber(i) derives a single non-hardened step.

// An apostrophe marks a hardened step.
final child = master.deriveChildKey("m/0'/1/2'/2/1000000000");

Please note that deriveChildNumber accepts only non-hardened indexes. Passing 0x80000000 or above throws an IllegalArgumentException. Use a path ("m/0'") for hardened steps.

HDPrivateKey.fromXpriv restores a key from its xprv string. It should be noted that HD keys only derive. To sign a Transaction or a message, or to get an Address, we take the privateKey (an SVPrivateKey) of the derived node.

final account = HDPrivateKey.fromXpriv(
'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7');
// HD keys only derive. Transactions, messages and Addresses take an SVPrivateKey.
final SVPrivateKey signingKey = account.deriveChildKey('m/0/0').privateKey;
final address = signingKey.toAddress(networkType: NetworkType.MAIN);

An extended Public Key derives the public half of the tree. A server that holds only the account xpub can generate fresh receiving Addresses, but only the wallet holding the xprv can spend from them.

// We give a server the account xpub. It can derive receiving Addresses without any Private Key.
final String accountXpub = account.xpubkey;
final watchOnly = HDPublicKey.fromXpub(accountXpub);
final receiving = watchOnly.deriveChildKey('m/0/5').publicKey.toAddress(NetworkType.MAIN);
// The wallet holding the xprv derives the same Address, and can sign for it.
final sameAddress = account.deriveChildKey('m/0/5').privateKey.toAddress(networkType: NetworkType.MAIN);

Hardened children cannot be derived from an xpub:

try {
xpub.deriveChildKey("m/0'");
} on DerivationException {
// an xpub can only follow non-hardened steps
}

The network we pass to fromSeed selects the tprv/tpub prefixes, and the derived SVPrivateKey carries that same network.

final master = HDPrivateKey.fromSeed('000102030405060708090a0b0c0d0e0f', NetworkType.TEST);
master.xprivkey; // tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m
final key = master.deriveChildKey("m/0'").privateKey;
key.toWIF(); // a testnet WIF (starts with c)