Skip to content

JavaScript SDK (Browser, Node.js, & Bundler compatible) for Dash hierarchical deterministic keys (BIP32)

License

Notifications You must be signed in to change notification settings

dashhive/DashHD.js

Repository files navigation

Manage HD Keys from HD Wallet Seed and Extended (xprv, xpub) Key Paths.
(compatible with the Hierarchical Deterministic Keys (BIP-44) and BIP-32 specs)

A fully-functional, production-ready reference implementation of Dash HD - suitable for learning DASH specs and protocols, and porting to other languages.

HD Wallet Seed:  ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069
                 (the canonical _Zeed_ seed, from the canonical _Zoomonic_)
  HD XKey Path:  m/44'/5'/0'/0

          XPrv:  xprvA2L7qar7dyJNhxnE47gK5J6cc1oEHQuAk8WrZLnLeHTtnkeyP4w6
                 Eo6Tt65trtdkTRtx8opazGnLbpWrkhzNaL6ZsgG3sQmc2yS8AxoMjfZ
          XPub:  xpub6FKUF6P1ULrfvSrhA9DKSS3MA3digsd27MSTMjBxCczsfYz7vcFL
                 nbQwjP9CsAfEJsnD4UwtbU43iZaibv4vnzQNZmQAVcufN4r3pva8kTz


   HD Key Path:  m/44'/5'/0'/0/0

           WIF:  XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK
       Address:  XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9
  HD XKey Path:  m/44'/5'/1'/1

          XPrv:  xprvA2ACWaqwADRtbkLsM6oQHzeWtqZVviBmKMKNRBFcwKGGRBgWHNeo
                 ZSKzduFMFkqvNsV5LaqRT9XRibzgSAweAAsfMF35PWy6beK3aL1BwTU
          XPub:  xpub6F9Yv6NpzazBpERLT8LQf8bFSsPzLAucgaEyDZfEVeoFHz1epuy4
                 7EeUVCRTNVToM1zgFZMxiGs2AFc9cNqZE2UVwJod2zPkG7W4ZGRuwJJ


   HD Key Path:  m/44'/5'/1'/1/1

           WIF:  XF9murLtNpJaZXbwMxqJ6BhigEtu9NxfBCJDBokCJcqFkYkz3itz
       Address:  XueHW2ELMxoXzXcaHMxmwVWhcADE1W5s8c

Table of Contents

Install

Notes:

  • secp256k1 is not a listed dependency in package.json for this -
    because there are many decent implementations to choose from.
    HOWEVER, DashKeys will use @dashincubator/secp256k1@1.x
    by default, it if is installed.
    See DashKeys if you prefer to use another implementation.
  • DashPhrase is recommended, but not strictly required.
    (you can work with bare seeds without a word list)

Node, Bun, & Bundlers

npm install --save @dashincubator/secp256k1@1.x
npm install --save dashhd@3.x
npm install --save dashphrase
let DashHd = require("dashhd");
let DashPhrase = require("dashphrase");

Browser

<script src="https://unpkg.com/@dashincubator/secp256k1@1.7.1-5/secp256k1.js"></script>
<script src="https://unpkg.com/dashkeys@1.x/dashkeys.js"></script>
<script src="https://unpkg.com/dashhd@3.x/dashhd.js"></script>
let DashHd = window.DashHd;
let DashPhrase = window.DashPhrase;

Usage Overview

Already know what you're doing?

Clueless? Go to the QuickStart or Tutorial section.

  1. Generate a Wallet

    let walletKey = await DashHd.fromSeed(seedBytes);
    • As a one-off, you can directly generate an Address Key by HD Path

      let hdpath = `m/44'/5'/0'/0/0`;
      let key = await DashHd.derivePath(walletKey, hdpath);
      
      let wif = await DashHd.toWif(key.privateKey);
      let address = await DashHd.toAddr(key.publicKey);
  2. Generate an Account

    let accountIndex = 0;
    let accountKey = await walletKey.deriveAccount(accountIndex);
  3. Generate an XPrv or XPub X Key (Extended Private or Public Key)

    // generally used for as a 'receive' address (as opposed to 'change' address)
    let use = DashHd.RECEIVE;
    let xprvKey = await accountKey.deriveXKey(use);
  4. (Optional) Generate XPrv and XPubs

    let xprv = await DashHd.toXPrv(xprvKey);
    let xpub = await DashHd.toXPub(xprvKey);
  5. Generate an Address Key

    let index = 0;
    let addressKey = await xprvKey.deriveAddress(index);
  6. Generate WIF & Address

    let wif = await DashHd.toWif(key.privateKey);
    let address = await DashHd.toAddr(key.publicKey);

Production QuickStart

However, production code will look more like this:

  1. Get a Seed from the user's Recovery Phrase and Secret Salt

    let wordList = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong";
    let secretSalt = "TREZOR";
    
    // Derive a Wallet Seed
    let seedBytes = await DashPhrase.toSeed(wordList, secretSalt);
  2. Derive a Wallet, Account, and X Key, if possible.
    (reject the Recovery Phrase or Seed if Account index 0 is not valid)

    let accountIndex = 0;
    let xprvKey;
    
    try {
      let walletKey = await DashHd.fromSeed(seedBytes); // from step 1
    
      let accountKey = await walletKey.deriveAccount(accountIndex);
    
      void (await accountKey.deriveXKey(DashHd.CHANGE));
      xprvKey = await accountKey.deriveXKey(DashHd.RECEIVE);
    } catch (e) {
      window.alert(
        "Error: The recovery phrase can't generate a valid 1st account!",
      );
    }

    Note: For multi-Accounts apps, just mark failed indexes as invalid.

  3. Derive a batch of Address keys (20 is the typical number)

    let keys = [];
    let previousIndex = 0;
    let use = DashHd.RECEIVE;
    
    let last = previousIndex + 20;
    for (let i = previousIndex; i < last; i += 1) {
      let key;
      try {
        key = await xprvKey.deriveAddress(i); // xprvKey from step 2
      } catch (e) {
        // to make up for skipping on error
        last += 1;
        continue;
      }
    
      let wif = await DashHd.toWif(key.privateKey);
      let address = await DashHd.toAddr(key.publicKey);
      let hdpath = `m/44'/5'/${accountIndex}'/${use}/${i}`; // accountIndex from step 2
      keys.push({
        index: i,
        hdpath: hdpath, // useful for multi-account indexing
        address: address, // XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9
        wif: wif, // XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK
      });
    }

    Note: it may be useful to indicate the path of the

API

  • IMPORTANT
    • all derivations can fail
  • DashHd
    Constants
        MAINNET, TESTNET
        HARDENED, PUBLIC
        RECEIVE, CHANGE
    async fromSeed(seedBytes, opts)   // depth-0 hdkey (Wallet)
    async fromXKey(xprv||xpub, opts)  // depth-4 hdkey (XKey)
    async toPublic(xKey)
    async wipePrivateData(xKey)
    async toWif(privBytes, opts)
    async toAddr(pubBytes, opts)
    async toXPrv(xprvKey, opts)
    async toXPub(xKey, opts)
  • DashHd (BIP-32)
    create(hdkey)                      // depth-n HDKey (any)
    async derivePath(
            hdkey,
            hdpath,
          )
    async deriveChild(                 // (mostly for debugging)
            hdkey,
            index,
            isHardened,
          )
  • HD Key Types
    Wallet Key
      async deriveAccount(accountIndex)
    Account Key
      async deriveXKey(use = 0)
    X (Extended) Key
      async deriveAddress(keyIndex)
    Address Key
      { privateKey, publicKey }
    HD Key (Base Type)
      { versions, depth, parentFingerprint,
        index, chainCode, privateKey, publicKey }

IMPORTANT

All derive methods can fail (throw an error).

This is highly-unlikely, but normal (because maths).

It's recommended that:

  1. Before accepting a seed as valid, derive these HD Paths:
    m/44'/5'/0'/0/0
    m/44'/5'/0'/0/1
    m/44'/5'/0'/1/0
    m/44'/5'/0'/1/1
    
    If derivation fails, reject the seed.
  2. When creating a new Account Index, always derive both Use Indexes:
    m/44'/5'/n'/0
    m/44'/5'/n'/1
    
    If either the Account Index or Use Index derivation fails,
    mark the whole Account Index as invalid.
  3. If a key derivation ever fails, simply mark that key as invalid.
    m/44'/5'/0'/0/n
    

Most likely you will never personally encounter this failure.

However, when your software is generating millions and billions of keys across thousands and millions of accounts, eventually one of the key expansion hashes will just so happen to hit Infinity or Zero on the curve.

Some libraries abstract this away by automatically incrementing to the next index on failure, but since the occurrence is so rare, and therefore will inevitably lead to highly confusing and difficult to track bugs (including address re-use, a privacy concern), we do not.

DashHd

This is the top-level export, and these methods are specific to the (modern & current) BIP-44 standard for HD Keys for:

  • Wallet Key
  • Account Key
  • X (Extended) Key (XPrv or XPub)
  • Address Key (WIF or Address)

Constants

MAINNET, TESTNET

The version byte that gets base58check encoded to a human-friendly prefix.

//                          "xprv"              "xpub"
DashHd.MAINNET = { private: 0x0488ade4, public: 0x0488b21e };
//                          "tprv"              "tpub"
DashHd.TESTNET = { private: 0x0488ade4, public: 0x0488b21e };

HARDENED, PUBLIC

Whether the HD Key should be derived as Hardened or Public (sharable).

DashHd.HARDENED = true;
DashHd.PUBLIC = false;

RECEIVE, CHANGE

The intended use of the key - as a receiving address for external funds, or an internal change address.

DashHd.RECEIVE = 0;
DashHd.CHANGE = 1;

fromSeed(seedBytes, opts)

Generate a Wallet (depth-0 HD Key)
with a default HD Path prefix of m/44'/5'.

let words = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong";
let secret = "TREZOR";
let seedBytes = await DashPhrase.toSeed(words, secret);
let walletKey = await DashHd.fromSeed(seedBytes, options);
// { deriveAccount,
//   versions,
//   depth: 0, parentFingerprint: 0, index: 0,
//   chainCode, privateKey, publicKey }

Options

{
    purpose: 44, // BIP-44 (default)
    coinType: 5, // DASH (default)
    versions: DashHd.MAINNET, // default
}

fromXKey(xprv || xpub, opts)

Parses a Base58Check-encoded xprv or xpub (Extended Private or Public Key) into an X Key (depth-4 HD Key).

Only the depth is known. The prefix is not recoverable, but can be assumed to be something like m/44'/5'/0'/0.

let xprv =
  "xprvA2L7qar7dyJNhxnE47gK5J6cc1oEHQuAk8WrZLnLeHTtnkeyP4w6Eo6Tt65trtdkTRtx8opazGnLbpWrkhzNaL6ZsgG3sQmc2yS8AxoMjfZ";
let xkey = await DashHd.fromXKey(xprvOrXPub, options);
// { deriveAddress,
//   versions: v, depth: n, parentFingerprint: p, index: 0,
//   chainCode: c, privateKey: privOrNull, publicKey: pubBytes }

Options

{
    bip32: false, // allow arbitrary depth (Wallet, Purpose, Coin, Account, etc)
    normalizePublicKey: false, // validate pubkey by re-ploting X,Y on curve
    versions: DashHd.MAINNET, // must match the given version
}

toPublic(xkey)

Creates a copy of the HD Key with privateKey set to null.

let xpubKey = await DashHd.toPublic(xprvKey);

wipePrivateData(xkey)

Performs an in-place secure erase of the private key memory.

await DashHd.wipePrivateData(xprvKey);

toWif(privBytes, opts)

Wrapper around DashKeys.encodeKey(keyBytes, options) to Base58Check-encode a Private Key as in WIF (Wallet Import Format).

let addressIndex = 0;
let key = await xprvKey.deriveAddress(addressIndex);
let wif = await DashHd.toWif(key.privateKey);
// "XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK"

Options

{
  version: "cc", // (default) mainnet DASH Private Key
}

toAddr(pubBytes, opts)

Wrapper around DashKeys.encodeKey(keyBytes, options) to Base58Check-encode a Public Key as an Address.

let addressIndex = 0;
let addressKey = await xkey.deriveAddress(addressIndex);
let addr = await DashHd.toAddr(key.publicKey);
// "XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9"

Options

{
  version: "4c", // (default) mainnet DASH PubKeyHash
}

toXPrv(hdkey)

Encode an HD Key to eXtended Private Key format.
(Base58Check with xprv magic version bytes)

Wrapper around DashKeys.encodeKey(keyBytes, options) to Base58Check-encode an X Key as an XPrv.

  1. Get your XKey (XPrv Key) by one of

    • Derive via HD Path directly from a Wallet Key
    // `m/${purpose}'/${coinType}'/${accountIndex}'/${use}`
    let hdpath = `m/44'/5'/0'/0`;
    let xprvKey = await DashHd.derivePath(walletKey, hdpath);
    • Derived from Account Key
    let use = DashHd.RECEIVE;
    let xprvKey = await accountKey.deriveXKey(use);
    • Decode an Extended Private Key (xprv)
    let xprvKey = await DashHd.fromXKey(xprv);
  2. Encode to Base58Check xprv

    let xprv = await DashHd.toXPrv(xprvKey);
    // "xprvA2L7qar7dyJNhxnE47gK5J6cc1oEHQuAk8WrZLnLeHTtnkeyP4w6Eo6Tt65trtdkTRtx8opazGnLbpWrkhzNaL6ZsgG3sQmc2yS8AxoMjfZ"

toXPub(hdkey)

Encode an HD Key to eXtended Public Key format.
(Base58Check with xprv magic version bytes)

Wrapper around DashKeys.encodeKey(keyBytes, options) to Base58Check-encode an X Key as an XPub.

  1. Get your XKey (XPub Key) by one of

    • Derive via HD Path directly from a Wallet Key
    // `m/${purpose}'/${coinType}'/${accountIndex}'/${use}`
    let hdpath = `m/44'/5'/0'/0`;
    let xprvKey = await DashHd.derivePath(walletKey, hdpath);
    • Derived from Account Key
    let use = DashHd.RECEIVE;
    let xprvKey = await accountKey.deriveXKey(use);
    • Decode an Extended Private Key (xprv)
    let xpubKey = await DashHd.fromXKey(xprvOrXPub);
  2. Encode to Base58Check xprv

    let xpub = await DashHd.toXPub(xprvKeyOrXPubKey);
    // "xpub6FKUF6P1ULrfvSrhA9DKSS3MA3digsd27MSTMjBxCczsfYz7vcFLnbQwjP9CsAfEJsnD4UwtbU43iZaibv4vnzQNZmQAVcufN4r3pva8kTz"

DashHd (BIP-32)

These are the more rudimentary functions which can be used for either style of HD Keys:

  • BIP-44 / BIP-43 (Wallet, Account, XKey, Key)
  • or BIP-32 (generic HD Keys)

These do not enforce BIP-44 compliance, so they can be useful for testing and debugging.

create(hdkey)

Returns an HD Key with any missing values set to their default.

let hdkey = DashHd.create({
  chainCode: chainBytes,
  privateKey: privOrNull,
  publicKey: pubBytes,
});

HD Key

{
  versions: DashHd.MAINNET,
  depth: 0,
  parentFingerprint: 0,    // should be set if depth is set
  index: 0,
  chainCode: chainBytes,   // required
  privateKey: privOrNull,  // required (or null)
  publicKey: pubBytes,     // required
}

deriveChild(hdkey, index, isHardened)

Derives one additional depth of an HD Key, by index value and hardness.

let hdkey = await DashHd.fromXKey(xpub, { bip32: true });
let UNHARDENED = false;
let nextIndex = 42;
let nextHdkey = await DashHd.deriveChild(hdkey, nextIndex, UNHARDENED);

derivePath(hdkey, hdpath)

Iterates over an HD Path, calling deriveChild(hdkey, index, isHardened) for each index.

Can derive any valid BIP-32 path, at any depth.
(even if nonsensical - such as a hardened key after an unhardened one)

let hdkey = await DashHd.fromSeed(seedBytes);
let childKey = await DashHd.derivePath(hdkey, `m/0'/0/1'/1/0`);
// { versions, depth, parentFingerprint,
//   index, chainCode, privateKey, publicKey }

This is the same as

let childKey = hdkey;
childKey = await DashHd.deriveChild(childKey, 0, true);
childKey = await DashHd.deriveChild(childKey, 0, false);
childKey = await DashHd.deriveChild(childKey, 1, true);
childKey = await DashHd.deriveChild(childKey, 1, false);
childKey = await DashHd.deriveChild(childKey, 0, false);
// { versions, depth, parentFingerprint,
//   index, chainCode, privateKey, publicKey }

Key Types

A BIP-44 HD Path has 5 depths, each with their own HD Key Type:

//0         1        2           3      4        5
`m/${purpose}'/${coin}'/${account}'/${use}/${index}`;
//m        44'       5'          a'    0|1       i
  • Depth 0: Wallet (Root) Key - calculated from a Seed
  • Depth 1: Purpose Key - predefined as index 44' for BIP-44
  • Depth 2: Coin Key - predefined as index 5' for DASH (1' for testnet)
    (this is also sometimes referred to as the "Wallet")
  • Depth 3: Account Key - derived by account index
  • Depth 4: X Key is for sharing, derived by Use (Receiving or Change)
  • Depth 5: Address Key is for paying and getting paid (WIF or Address)
  • Depth 6+: not defined by BIP-44, application specific

Keys can be derived

  • by instance (recommended: faster, maintains state):

    let walletKey = await DashHd.fromSeed(seedBytes);
    
    let accountIndex = 0;
    let accountKey = await walletKey.deriveAccount(accountIndex);
    
    let use = DashHD.RECEIVE;
    let xkey = await accountKey.deriveXKey(use);
    
    let addressIndex = 0;
    let key = await xkey.deriveAddress(addressIndex);
  • by path (slow if used in for loops):

    let walletKey = await DashHd.fromSeed(seedBytes);
    
    let hdpath = `m/${purpose}'/${coin}'/${account}'/${use}/${index}`;
    let key = await DashHd.derivePath(walletKey, hdpath);

The benefit of deriving by instance is that when you need to derive in a loop, such as multiple keys at the same depth - e.g. Addresses from the same X Key, or Accounts from the same Wallet - you're not deriving from the Wallet Root each time.

Since the key derivation process is intentionally somewhat slow, it's best to loop at the lowest level that you can. For example:

// ...
let xkey = await accountKey.deriveXKey(use);

for (let addressIndex = 0; addressIndex < 100; addressIndex += 1) {
  let key = await xkey.deriveAddress(addressIndex);
  // ...
}

Wallet (depth 0)

A root HD Key with a deriveAccount(accountIndex) method.

Can also derive BIP-32 HD Keys.

From a Seed:

let words = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong";
let secret = "TREZOR";
let seedBytes = await DashPhrase.toSeed(words, secret);

let walletKey = await DashHd.fromSeed(seedBytes, {});

To a different Coin Type or non-BIP-44 scheme:

let purpose = 44; // depth 1 is always 44 for DASH
let coinType = 0; // depth 2 is always 0 for DASH
let accountIndex = 0;
let accountKey = await DashHd.derivePath(
  walletKey,
  `m/${purpose}'/${coinType}'/${accountIndex}'`,
);

Purpose (depth 1)

Always 44', as in BIP-44 for DASH (and most other cryptocurrencies)

Purpose (depth 3)

Always 5', which is a magic number specifying DASH.

Except 1' for testnet, as per https://github.com/satoshilabs/slips/blob/master/slip-0044.md.

See fromSeed(seedBytes) for options to change purpose, coinType, and versions.

Account (depth 3)

A hardened HD Key with a deriveXKey(use) method.

From a Wallet:

let accountIndex = 0;
let accountKey = walletKey.deriveAccount(accountIndex);

let use = DashHd.RECEIVE;
let xkey = await accountKey.deriveXKey(use);

From an HD Path:

let accountIndex = 0;
let accountKey = await DashHd.derivePath(
  walletKey,
  `m/44'/5'/${accountIndex}'`,
);

let use = DashHd.RECEIVE;
let xkey = await accountKey.deriveXKey(use);

XKey (XPrv or XPub) (depth 4)

An HD Key with a deriveAddress(addressIndex) method.

Represents a "public" (i.e. non-hardened) XPrv or XPub.

Share an XPub with a contact so that they can derive additional addresses to pay you repeatedly without sacrificing privacy, and without needing interaction from you to creat a new address each time.

Share an XPrv with an application so that you can load the XPub side and it can use the XPrv side to make payments on your behalf.

From an Account:

let use = DashHd.RECEIVE;
let xkey = await accountKey.deriveXKey(use);

let addressIndex = 0;
let key = await xkey.deriveAddress(addressIndex);
// { ...,
//  privateKey: privBytes, publicKey: pubBytes }

From an xprv:

let xkey = await accountKey.fromXKey(xprv);

let addressIndex = 0;
let key = await xkey.deriveAddress(addressIndex);
// { ...,
//  privateKey: privBytes, publicKey: pubBytes }

From an xpub:

let xkey = await accountKey.fromXKey(xprv);

let addressIndex = 0;
let key = await xkey.deriveAddress(addressIndex);
// { ...,
//  privateKey: null, publicKey: pubBytes }

Key (depth 5)

The base HD Key type, but with no additional methods.

A fully-derived BIP-44 Address ("public" a.k.a. non-hardened).

{ ...,
  privateKey: privBytes, publicKey: pubBytes }

From a Wallet:

let accountIndex = 0;
let accountKey = await walletKey.deriveAccount(accountIndex);

let use = DashHd.RECEIVE;
let xkey = await accountKey.deriveXKey(use);

let addressIndex = 0;
let key = await xkey.deriveAddress(addressIndex);

From an XPrv:

let xkey = await accountKey.fromXKey(xprv);

let addressIndex = 0;
let key = await xkey.deriveAddress(addressIndex);
// { ...,
//   privateKey: privBytes, publicKey: pubBytes }

From an XPub:

let xkey = await accountKey.fromXKey(xpub);

let addressIndex = 0;
let key = await xkey.deriveAddress(addressIndex);
// { ...,
//   privateKey: null, publicKey: pubBytes }

HDKey

A generic HD Key at any depth.

{
  versions: DashHd.MAINNET,
  depth: 0,
  parentFingerprint: 0,    // should be set if depth is non-zero
  index: 0,
  chainCode: chainBytes,   // required
  privateKey: privOrNull,  // required (or null)
  publicKey: pubBytes,     // required
}

The same structure as Key, but documented separately - because BIP-32 doesn't define semantic differences between HD Keys at different depths (other than the root).

Walkthrough

This will focus on the most typical use case, where you intend to generate multiple addresses as part of your application's lifecycle.

Part 1: Recovery Phrase, Secret, Seed

The Recovery Phrase (or Seed), is the Wallet.

  • A Wallet is derived from a Seed.
  • A Seed is typically derived from a Recovery Phrase and Secret Salt.

From a code perspective:

  1. If your user doesn't supply a Recovery Phrase, you can generate one:
    let targetBitEntropy = 128;
    let wordList = await DashPhrase.generate(targetBitEntropy);
    // "cat swing flag economy stadium alone churn speed unique patch report train"
  2. Typically the Secret Salt is left as an empty string:
    let secretSalt = "";
  3. HOWEVER, for this demo we'll use the Zoomonic and Zecret
    (these values are specified by BIP-39's test suite for demos, debugging, etc)
    let wordList = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong";
    let secretSalt = "TREZOR";
  4. Key Expansion derives the Seed from the Recovery Phrase + Secret
    (FYI: it's a specific configuration of PBKDF2 under the hood)
    let seedBytes = await DashPhrase.toSeed(wordList, secretSalt);

Prompt the user to make a backup of their Recovery Phrase.
(or their Seed, if you're not implementing Recovery Phrases)

It's common to print this out and put it in a safe.

If the Recovery Phrase is lost, the Wallet (and all money) is unrecoverable.

Part 2: The Wallet Derivation

As mentioned in the API section, it is, in fact, possible for any derivation to fail.

It's highly unlikely that you'll ever encounter it, but it should be handled nonetheless.

  1. Generate a Wallet Key
    (uses an HMAC-style Key Expansion, defined in BIP-32 )
    let walletKey;
    try {
      walletKey = await DashHd.fromSeed(seedBytes);
    } catch (e) {
      window.alert(
        "the recovery phrase (or seed) could not be used to derive keys",
      );
    }
  2. Notify the user and retry a different Recovery Phrase on failure.

Part 2a: HD Path Derivation

As a one-off, HD Path Derivation can be very convenient:

Note: this approach would be 5x slower for deriving multiple keys because each key will derive from the Root Wallet Key each time.

  1. Define the target HD Path indexes to Depth 4 (Use a.k.a. X Key)

    let accountIndex = 0;
    let use = DashHd.RECEIVE;
    let addressIndex = 0;
    let maxTries = 3;
    let hdPartial = `m/44'/5'/${accountIndex}'/${use}`;
  2. Derive the Address Key (Depth 5)

    let key;
    for (let i = addressIndex; i < maxTries; i += 1) {
      try {
        let hdpath = `${hdPartial}/${addressIndex}`;
        key = await DashHd.derivePath(wallet, hdpath); // as defined above
        break;
      } catch (e) {
        // ignore
      }
    }
    
    if (!key) {
      // more than 1 failure in a row would indicate
      // the accountIndex or use index could not be derived
      // (and hence no addressIndex will ever derive)
      throw new Error(
        `'${hdPartial}': 'account' or 'use' index cannot be derived`,
      );
    }
  3. Mark the Account index as invalid and advance to the next on failure.
    (or fail hard if it's the first account)

Part 2b: Wallet, Account, X Key

This is the more typical and efficient use - for when you intend to generate multiple addresses as part of your application's lifecycle.

  1. Derive an Account Key and X Key
    (reject the seed if the account at index 0 fails)

    let accountIndex = 0;
    let accountKey;
    
    let use = DashHd.RECEIVE; // 0 = receive, 1 = change
    let xkey;
    
    while (!accountKey) {
      try {
        accountKey = await walletKey.deriveAccount(accountIndex);
        xkey = await accountKey.deriveXKey(use);
        break;
      } catch (e) {
        accountIndex += 1;
        // if your app handles multiple accounts, just try the next
      }
    }

    Note: technically you could advance the Use index,
    but that's more complicated than just advancing to the next account

  2. (optional) Encode the X Key as XPrv or XPub for sharing

    let xprv = await DashHd.toXPrv(xkey); // "xprv......"
    let xpub = await DashHd.toXPub(xkey); // "xpub......"

Part 3: Address Key

This is final piece, which you use for making and receiving payments.

  1. Derive an Address Key

    let index = 0;
    let maxTries = 3;
    let last = index + maxTries;
    let key;
    
    for (let i = index; i < last; i += 1) {
      try {
        key = await xkey.deriveAddress(index);
      } catch (e) {
        // you may wish to mark the index as failed
      }
    }
  2. Encode the Address Key as WIF or Address for use or sharing

    let wif = DashKeys.toWif(keys.privateKey); // "X....."
    let addr = DashKeys.toAddr(keys.publicKey); // "X..."

Glossary

See also Dash Tools Glossary.

Base2048

Also: Base2048, BIP39, BIP-0039

Rather than a bank of 2, 16, 32, 58, 62, or 64 characters,
you can encode data using a bank of whole words.
If you use 2048 words, each word represents 11 bits.
12 words represent 131 bits of information.
Any extra bits are used for checksumming the data. \

See HD Recovery Phrase.

Base58Check

The encoding format used for sharing XPrv and XPub Keys (X Keys).
(among other things, such as WIF and Address)

xprvA2L7qar7dyJNhxnE47gK5J6cc1oEHQuAk8WrZLnLeHTtnkeyP4w6Eo6Tt65trtdkTRtx8opazGnLbpWrkhzNaL6ZsgG3sQmc2yS8AxoMjfZ
xpub6FKUF6P1ULrfvSrhA9DKSS3MA3digsd27MSTMjBxCczsfYz7vcFLnbQwjP9CsAfEJsnD4UwtbU43iZaibv4vnzQNZmQAVcufN4r3pva8kTz

BIP

Also: BIPs

Bitcoin Improvement Proposal(s).
Specification Drafts / RFCs (Request For Comments).

BIP-32

See HD Keys.

BIP-39

Also: Base2048, BIP39, BIP-0039

BIP for HD Recovery Phrase.

BIP-43

BIP for the Purpose index of the HD Path.

`m/${purpose}'`;

This is the basis of [BIP-44][#bip-44] defining HD Paths as m/44'/.

See HD Keys.

BIP-44

See HD Path.

Curve

Related to parameters of Elliptic Curve (ECDSA) cryptography / algorithms.

A single X value produces two Y values on a curve (rather than 1 on a line).

In rare instances, an X value may produce no points on the curve.

Derive (by Path)

To split an HD Path by / and then iterate to derive each index (Child) in turn.

Cannot be reversed.

See [derivePath(hdkey, hdpath)][#derivePath-hdkey-hdpath].

Derived Child

A key directly derived from another key by an HD Path index.
(typically referring to a single index of the path, not the whole)

See [deriveChild(hdkey, index, isHardened)][#derivePath-hdkey-index-ishardened].

Key Expansion

An algorithm that creates a larger (byte-size) output than its input.
Typically uses hashing algos: HMAC, SHA-2, SHA-3, etc.
May combine multiple algos together.
Usually intentionally slow.
May run a high number of "Rounds" in succession.
(typically hundreds or thousands).

Recovery Phrase to Seed (BIP-39) uses PBKDF2.
HD Keys (BIP-44) use HMAC and Secp256k1 Tweaking for each index.

See also:

  • DashPhrase.toSeed(wordList)
  • DashHd.fromSeed(seedBytes)
  • DashHd.deriveChild(hdkey, index, isHardened)

HD Account

An HD Key derived at m/44'/5'/n' (Depth 3) of the HD Path.

See API: Key Types.

HD Address Key

Also: Key, HD Private Key, WIF, Address

An HD Key at final depth m/44'/5'/0'/0/0 (Depth 5) of an HD Path.
Can be encoded as WIF or Address for making or receiving payments.

See also API: Key Types.

HD Keys

Also: Hierarchical Deterministic Keys, BIP-32, BIP-44

Any of generic or purpose-specific keys derived deterministically form a seed.

See more at API: Key Types (code above) and HD Path.

HD Recovery Phrase

Also: Recovery Phrase, Mnemonic for Generating Hierarchical Deterministic Keys, HD Wallet, BIP-39

12 words used to derive an HD Seed.
(11¾ for entropy, ¼ for checksum)

Ex: zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong

Not used directly in this library, but...
it is how the HD Seeds used here are typically generated.

See DashPhrase.js.

HD Path

The path that defines an HD Key - typically of the BIP-44 variety:

  • a Root, ex: m (depth 0, the Wallet Key, straight from the seed)
  • an Coin Key, ex: m/44'/5' (depth 2)
  • an Account, ex: m/44'/5'/0' (depth 3)
  • an X Key (XPrv or XPub), ex: m/44'/5'/0'/0 (depth 4, a.k.a. Use)
  • an Address Key, ex: m/44'/5'/0'/0/0 (depth 5, the end)
  • ' is used for "hardened" (parent) key segments,
    but not for "public" (shareable) child key segments

In general:

let hdpath = `m/${purpose}'/${coinType}'/${account}'/${use}/${index}`;

For DASH:

let hdpath = `m/44'/5'/${account}'/${use}/${index}`;

See also API: Key Types (code above).

HD Wallet

Either the Root Key at m (Depth 0), directly from the Seed,
or the Coin Key at m/44'/5' (Depth 2), of the HD Path.
Sometimes also used to mean HD Account at m/44'/5'/n'.

Here we typically use it to mean the Root Key.
(because we're focus on DASH more so than other coins)

See also API: Key Types.

HD X Key

Also: XKey, XPrv, XPub, Use Key, Use Index, Extended Key.

An HD Key derived at m/44'/5'/0'/n (Depth 4), of the HD Path.

Here we typically use it to mean the Root Key.
(because we're focus on DASH more so than other coins)

See also API: Key Types.

Root Seed

Also: Master Seed, Seed, HD Seed

Either:

  • 64 random bytes
  • a 64-byte hash derived from a Recovery Phrase

Cannot be reversed.

Root Key

Also: HD Wallet, Master Key, HD Master

An HD Key of m (Depth 0), as derived directly from the Seed.

See also API: Key Types.

Secp256k1

A specific set of parameters "the curve" used by most cryptocurrencies.

See Curve.

Test Vectors

The well-known values used for testing, demos, debugging, and development:

XPrv

Also: Extended Private Key, XPriv, X Prv, X Priv

Specifically the Base58Check-encoded form of an HD Key at Depth 4.
(the X Key, a.k.a. Use Key, including the Private Key)_
Can be used to derive any number of WIFs and Addresses.

xprvA2L7qar7dyJNhxnE47gK5J6cc1oEHQuAk8WrZLnLeHTtnkeyP4w6Eo6Tt65trtdkTRtx8opazGnLbpWrkhzNaL6ZsgG3sQmc2yS8AxoMjfZ

See HD X Key.

XPub

Also: Extended Pubilc Key, X Pub

Specifically the Base58Check-encoded form of an HD Key.
(just the public key) Can be used to derive any number of receiving Addresses.

xpub6FKUF6P1ULrfvSrhA9DKSS3MA3digsd27MSTMjBxCczsfYz7vcFLnbQwjP9CsAfEJsnD4UwtbU43iZaibv4vnzQNZmQAVcufN4r3pva8kTz

See XPrv, HD X Key.

Zecret

The Secret Salt used for the BIP-32 Test Vectors.

TREZOR
let secretSalt = "TREZOR";

Comes from the fact that the company Trezor (a hardware wallet) was involved in creating the reference implementation and Test Vectors.

Zeed

The canonical Seed (generated from the Zoomonic salted with "TREZOR"),
to be used in documentation, examples, and test fixtures.

ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069

Zoomonic

Recovery Phrase (Mnemonic) :  zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong
Secret (Salt Password)     :  TREZOR
Seed                       :  ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069

References

License

Copyright © 2023 Dash Incubator
Copyright © 2023 AJ ONeal
Copyright © 2018-2022 cryptocoinjs

MIT License

About

JavaScript SDK (Browser, Node.js, & Bundler compatible) for Dash hierarchical deterministic keys (BIP32)

Resources

License

Stars

Watchers

Forks

Packages

No packages published