├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .min-wd ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib └── hdkey.js ├── package.json └── test ├── fixtures └── hdkey.json ├── hdkey.test.js └── mocha.opts /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | on: 3 | push: 4 | branches: [ "master" ] 5 | pull_request: 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [10.x, 12.x, 14.x, 16.x, 18.x, latest] 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - run: npm install 19 | - run: npm test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ -------------------------------------------------------------------------------- /.min-wd: -------------------------------------------------------------------------------- 1 | { 2 | "hostname" : "localhost", 3 | "port" : 4444, 4 | "browsers" : [{ 5 | "name" : "chrome" 6 | }] 7 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .travis.yml 3 | .min-wd 4 | Makefile -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.1.0 / 2023-01-18 2 | ------------------ 3 | 4 | - Add `skipVerification` option to `HDKey.fromExtendedKey()` to allow skipping verification logic for performance ([#53](https://github.com/cryptocoinjs/hdkey/pull/53)) 5 | - Use `ripemd160` package; as Node v18+ `crypto.createHash()` does not support ripemd160 ([#51](https://github.com/cryptocoinjs/hdkey/pull/51)) 6 | - Fix `.sign()` method in some environments with polyfilled `Buffer` implementations ([#50](https://github.com/cryptocoinjs/hdkey/pull/50)) 7 | - Performance improvements ([#52](https://github.com/cryptocoinjs/hdkey/pull/52)) 8 | 9 | 2.0.1 / 2020-05-30 10 | ------------------ 11 | 12 | - Bugfix: prevent mutating buffers passed in ([#39](https://github.com/cryptocoinjs/hdkey/pull/39)) 13 | 14 | 2.0.0 / 2020-05-29 15 | ------------------ 16 | 17 | - **BREAKING:** Require Node.js v10+ ([#38](https://github.com/cryptocoinjs/hdkey/pull/38)) 18 | - Upgrade `secp256k1` dependency; to use N-API ([#32](https://github.com/cryptocoinjs/hdkey/pull/32)) 19 | 20 | 1.1.2 / 2020-04-16 21 | ------------------ 22 | 23 | - Fix extremely rare types bug ([#33](https://github.com/cryptocoinjs/hdkey/pull/33)) 24 | - Use `bs58check` dependency instead of `coinstring` ([#30](https://github.com/cryptocoinjs/hdkey/pull/30)) 25 | - Don't publish test files ([#27](https://github.com/cryptocoinjs/hdkey/issues/27), [#34](https://github.com/cryptocoinjs/hdkey/pull/34)) 26 | 27 | 1.1.1 / 2019-02-09 28 | ------------------ 29 | 30 | - Fix Electron v4 support. No changes to external API. ([#26](https://github.com/cryptocoinjs/hdkey/pull/26)) 31 | 32 | 1.1.0 / 2018-08-14 33 | ------------------ 34 | 35 | - Add `wipePrivateData()` method ([#22](https://github.com/cryptocoinjs/hdkey/pull/22)) 36 | - Add missing LICENSE file ([#21](https://github.com/cryptocoinjs/hdkey/pull/21)) 37 | 38 | 1.0.0 / 2018-05-24 39 | ------------------ 40 | 41 | - drop support for all Node.js versions 4 and earlier 42 | - fix `derive()` path validation ([#20](https://github.com/cryptocoinjs/hdkey/pull/20)) 43 | 44 | 0.8.0 / 2018-02-06 45 | ------------------ 46 | - add `sign()` and `verify()` 47 | - upgrade to `safe-buffer` 48 | 49 | 0.7.1 / 2016-05-26 50 | ------------------ 51 | - fix bug when `privateKey` is `null`, `privateExtendedKey` should not throw, and return `null` [#7][#7] 52 | 53 | 0.7.0 / 2016-03-22 54 | ------------------ 55 | - upgrade from `ecurve` to `secp256k1`. [#5][#5] 56 | 57 | 0.6.0 / 2015-07-02 58 | ------------------ 59 | - **breaking** (same day though, haha). Changed `publicExtendedKey`/`privateExtendedKey` in `JSON` methods to `xpub`/`xpriv` 60 | - export `HARDENED_OFFSET` 61 | 62 | 0.5.0 / 2015-07-02 63 | ------------------ 64 | - JavaScript Standard Style 65 | - fix rare condition for BIP32 consistency: [#1][#1] 66 | - added `toJSON()/fromJSON()` 67 | 68 | 0.4.0 / 2014-09-24 69 | ------------------ 70 | - dropped `sha512` dependency and upgraded to crypto-browserify that supports sha512 71 | 72 | 0.3.1 / 2014-07-11 73 | ------------------ 74 | - removed superfluous code `this._privateKeyBigInteger` 75 | 76 | 0.3.0 / 2014-06-29 77 | ------------------ 78 | - bugfix: if private key was less than 32 bytes, pad out to 32 bytes with leading zeros (this happens in derive) 79 | - changed behavior of `privateExtendedKey()` and `publicExtendedKey()` to return base 58 encoded `string` instead of `Buffer` 80 | - changed behavior of `fromExtendedKey()` from accepting a type of `Buffer` bytes to base58 `string` 81 | 82 | 0.2.0 / 2014-06-25 83 | ------------------ 84 | - upgraded `"ecurve": "^0.8.0"` to `"ecurve": "^1.0.0"` 85 | - added functionality to derive public to public child keys 86 | 87 | 0.1.0 / 2014-06-16 88 | ------------------ 89 | - removed semicolons per http://cryptocoinjs.com/about/contributing/#semicolons 90 | - removed `ECKey` dep 91 | - added `ecurve` dep 92 | - removed `terst` dev dep for `assert` 93 | - added method `fromMasterSeed(seedBuffer, [versions])` 94 | - changed constructor from `new HDKey(masterSeed, [versions])` to `new HDKey([versions])` 95 | - added properties: `privateKey` and `publicKey` 96 | - removed method `getIdentifier()`, added property `identifier` 97 | - removed method `getFingerprint()`, added property `fingerprint` 98 | - renamed `private` to `privateExtendedKey` 99 | - renamed `public` to `publicExtendedKey` 100 | - added method `fromExtendedKey()` 101 | 102 | 0.0.1 / 2014-05-29 103 | ------------------ 104 | - initial release 105 | 106 | 107 | [#7]: https://github.com/cryptocoinjs/hdkey/issues/7 "privateExtendedKey error handling" 108 | [#6]: https://github.com/cryptocoinjs/hdkey/pull/6 "hdkey: use bippath for BIP32 path parsing and validation" 109 | [#5]: https://github.com/cryptocoinjs/hdkey/pull/5 "hdkey: use the secp256k1 package for crypto" 110 | [#4]: https://github.com/cryptocoinjs/hdkey/issues/4 "Is this library still maintained?" 111 | [#3]: https://github.com/cryptocoinjs/hdkey/pull/3 "Update hdkey.js" 112 | [#2]: https://github.com/cryptocoinjs/hdkey/pull/2 "Update hdkey.js" 113 | [#1]: https://github.com/cryptocoinjs/hdkey/issues/1 "rare condition needed for bip consistency" 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 cryptocoinjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hdkey 2 | ===== 3 | 4 | [![NPM Package](https://img.shields.io/npm/v/hdkey.svg?style=flat-square)](https://www.npmjs.org/package/hdkey) 5 | [![build status](https://secure.travis-ci.org/cryptocoinjs/hdkey.svg)](http://travis-ci.org/cryptocoinjs/hdkey) 6 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 7 | 8 | A JavaScript component for [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)(hierarchical deterministic keys). 9 | 10 | 11 | Installation 12 | ------------ 13 | 14 | ```bash 15 | npm i --save hdkey 16 | ``` 17 | 18 | 19 | Usage 20 | ----- 21 | 22 | **example:** 23 | 24 | ```js 25 | var HDKey = require('hdkey') 26 | var seed = 'a0c42a9c3ac6abf2ba6a9946ae83af18f51bf1c9fa7dacc4c92513cc4dd015834341c775dcd4c0fac73547c5662d81a9e9361a0aac604a73a321bd9103bce8af' 27 | var hdkey = HDKey.fromMasterSeed(Buffer.from(seed, 'hex')) 28 | console.log(hdkey.privateExtendedKey) 29 | // => 'xprv9s21ZrQH143K2SKJK9EYRW3Vsg8tWVHRS54hAJasj1eGsQXeWDHLeuu5hpLHRbeKedDJM4Wj9wHHMmuhPF8dQ3bzyup6R7qmMQ1i1FtzNEW' 30 | console.log(hdkey.publicExtendedKey) 31 | // => 'xpub661MyMwAqRbcEvPmRAmYndzERhyNux1GoHzHxgzVHMBFkCro3kbbCiDZZ5XabZDyXPj5mH3hktvkjhhUdCQxie5e1g4t2GuAWNbPmsSfDp2' 32 | ``` 33 | 34 | 35 | ### `HDKey.fromMasterSeed(seedBuffer[, versions])` 36 | 37 | Creates an `hdkey` object from a master seed buffer. Accepts an optional `versions` object. 38 | 39 | ```js 40 | var seed = 'a0c42a9c3ac6abf2ba6a9946ae83af18f51bf1c9fa7dacc4c92513cc4dd015834341c775dcd4c0fac73547c5662d81a9e9361a0aac604a73a321bd9103bce8af' 41 | var hdkey = HDKey.fromMasterSeed(Buffer.from(seed, 'hex')) 42 | ``` 43 | 44 | ### `HDKey.fromExtendedKey(extendedKey[, versions, skipVerification])` 45 | 46 | Creates an `hdkey` object from a `xprv` or `xpub` extended key string. Accepts an optional `versions` object & an optional `skipVerification` boolean. If `skipVerification` is set to true, then the provided public key's x (and y if uncompressed) coordinate will not will be verified to be on the curve. 47 | 48 | ```js 49 | var key = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j' 50 | var hdkey = HDKey.fromExtendedKey(key) 51 | ``` 52 | 53 | **or** 54 | 55 | ```js 56 | var key = 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt' 57 | var hdkey = HDKey.fromExtendedKey(key) 58 | ``` 59 | 60 | ### `HDKey.fromJSON(obj)` 61 | 62 | Creates an `hdkey` object from an object created via `hdkey.toJSON()`. 63 | 64 | --- 65 | 66 | ### `hdkey.derive(path)` 67 | 68 | Derives the `hdkey` at `path` from the current `hdkey`. 69 | 70 | ```js 71 | var seed = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542' 72 | var hdkey = HDKey.fromMasterSeed(Buffer.from(seed, 'hex')) 73 | var childkey = hdkey.derive("m/0/2147483647'/1") 74 | 75 | console.log(childkey.privateExtendedKey) 76 | // -> "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" 77 | console.log(childkey.publicExtendedKey) 78 | // -> "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon" 79 | ``` 80 | 81 | Newer, "hardened" derivation paths look like this: 82 | 83 | ```js 84 | // as defined by BIP-44 85 | var childkey = hdkey.derive("m/44'/0'/0'/0/0"); 86 | ``` 87 | 88 | ### `hdkey.sign(hash)` 89 | 90 | Signs the buffer `hash` with the private key using `secp256k1` and returns the signature as a buffer. 91 | 92 | ### `hdkey.verify(hash, signature)` 93 | 94 | Verifies that the `signature` is valid for `hash` and the `hdkey`'s public key using `secp256k1`. Returns `true` for valid, `false` for invalid. Throws if the `hash` or `signature` is the wrong length. 95 | 96 | ### `hdkey.wipePrivateData()` 97 | 98 | Wipes all record of the private key from the `hdkey` instance. After calling this method, the instance will behave as if it was created via `HDKey.fromExtendedKey(xpub)`. 99 | 100 | ### `hdkey.toJSON()` 101 | 102 | Serializes the `hdkey` to an object that can be `JSON.stringify()`ed. 103 | 104 | ```js 105 | var seed = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542' 106 | var hdkey = HDKey.fromMasterSeed(Buffer.from(seed, 'hex')) 107 | 108 | console.log(hdkey.toJSON()) 109 | // -> { 110 | // xpriv: 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U', 111 | // xpub: 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB' 112 | // } 113 | ``` 114 | 115 | ### `hdkey.privateKey` 116 | 117 | Getter/Setter of the `hdkey`'s private key, stored as a buffer. 118 | 119 | ### `hdkey.publicKey` 120 | 121 | Getter/Setter of the `hdkey`'s public key, stored as a buffer. 122 | 123 | ### `hdkey.privateExtendedKey` 124 | 125 | Getter/Setter of the `hdkey`'s `xprv`, stored as a string. 126 | 127 | ### `hdkey.publicExtendedKey` 128 | 129 | Getter/Setter of the `hdkey`'s `xpub`, stored as a string. 130 | 131 | References 132 | ---------- 133 | - https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/hdnode.js 134 | - http://bip32.org/ 135 | - http://blog.richardkiss.com/?p=313 136 | - https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 137 | - http://bitcoinmagazine.com/8396/deterministic-wallets-advantages-flaw/ 138 | 139 | 140 | License 141 | ------- 142 | 143 | MIT 144 | -------------------------------------------------------------------------------- /lib/hdkey.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var Buffer = require('safe-buffer').Buffer 3 | var crypto = require('crypto') 4 | var bs58check = require('bs58check') 5 | var RIPEMD160 = require('ripemd160') 6 | var secp256k1 = require('secp256k1') 7 | 8 | var MASTER_SECRET = Buffer.from('Bitcoin seed', 'utf8') 9 | var HARDENED_OFFSET = 0x80000000 10 | var LEN = 78 11 | 12 | // Bitcoin hardcoded by default, can use package `coininfo` for others 13 | var BITCOIN_VERSIONS = {private: 0x0488ADE4, public: 0x0488B21E} 14 | 15 | function HDKey (versions) { 16 | this.versions = versions || BITCOIN_VERSIONS 17 | this.depth = 0 18 | this.index = 0 19 | this._privateKey = null 20 | this._publicKey = null 21 | this.chainCode = null 22 | this._fingerprint = 0 23 | this.parentFingerprint = 0 24 | } 25 | 26 | Object.defineProperty(HDKey.prototype, 'fingerprint', { get: function () { return this._fingerprint } }) 27 | Object.defineProperty(HDKey.prototype, 'identifier', { get: function () { return this._identifier } }) 28 | Object.defineProperty(HDKey.prototype, 'pubKeyHash', { get: function () { return this.identifier } }) 29 | 30 | Object.defineProperty(HDKey.prototype, 'privateKey', { 31 | get: function () { 32 | return this._privateKey 33 | }, 34 | set: function (value) { 35 | assert.equal(value.length, 32, 'Private key must be 32 bytes.') 36 | assert(secp256k1.privateKeyVerify(value) === true, 'Invalid private key') 37 | 38 | this._privateKey = value 39 | this._publicKey = Buffer.from(secp256k1.publicKeyCreate(value, true)) 40 | this._identifier = hash160(this.publicKey) 41 | this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0) 42 | } 43 | }) 44 | 45 | function setPublicKey (hdkey, publicKey) { 46 | hdkey._publicKey = Buffer.from(publicKey) 47 | hdkey._identifier = hash160(publicKey) 48 | hdkey._fingerprint = hdkey._identifier.slice(0, 4).readUInt32BE(0) 49 | hdkey._privateKey = null 50 | } 51 | 52 | Object.defineProperty(HDKey.prototype, 'publicKey', { 53 | get: function () { 54 | return this._publicKey 55 | }, 56 | set: function (value) { 57 | assert(value.length === 33 || value.length === 65, 'Public key must be 33 or 65 bytes.') 58 | assert(secp256k1.publicKeyVerify(value) === true, 'Invalid public key') 59 | // force compressed point (performs public key verification) 60 | const publicKey = (value.length === 65) ? secp256k1.publicKeyConvert(value, true) : value 61 | setPublicKey(this, publicKey) 62 | } 63 | }) 64 | 65 | Object.defineProperty(HDKey.prototype, 'privateExtendedKey', { 66 | get: function () { 67 | if (this._privateKey) return bs58check.encode(serialize(this, this.versions.private, Buffer.concat([Buffer.alloc(1, 0), this.privateKey]))) 68 | else return null 69 | } 70 | }) 71 | 72 | Object.defineProperty(HDKey.prototype, 'publicExtendedKey', { 73 | get: function () { 74 | return bs58check.encode(serialize(this, this.versions.public, this.publicKey)) 75 | } 76 | }) 77 | 78 | HDKey.prototype.derive = function (path) { 79 | if (path === 'm' || path === 'M' || path === "m'" || path === "M'") { 80 | return this 81 | } 82 | 83 | var entries = path.split('/') 84 | var hdkey = this 85 | entries.forEach(function (c, i) { 86 | if (i === 0) { 87 | assert(/^[mM]{1}/.test(c), 'Path must start with "m" or "M"') 88 | return 89 | } 90 | 91 | var hardened = (c.length > 1) && (c[c.length - 1] === "'") 92 | var childIndex = parseInt(c, 10) // & (HARDENED_OFFSET - 1) 93 | assert(childIndex < HARDENED_OFFSET, 'Invalid index') 94 | if (hardened) childIndex += HARDENED_OFFSET 95 | 96 | hdkey = hdkey.deriveChild(childIndex) 97 | }) 98 | 99 | return hdkey 100 | } 101 | 102 | HDKey.prototype.deriveChild = function (index) { 103 | var isHardened = index >= HARDENED_OFFSET 104 | var indexBuffer = Buffer.allocUnsafe(4) 105 | indexBuffer.writeUInt32BE(index, 0) 106 | 107 | var data 108 | 109 | if (isHardened) { // Hardened child 110 | assert(this.privateKey, 'Could not derive hardened child key') 111 | 112 | var pk = this.privateKey 113 | var zb = Buffer.alloc(1, 0) 114 | pk = Buffer.concat([zb, pk]) 115 | 116 | // data = 0x00 || ser256(kpar) || ser32(index) 117 | data = Buffer.concat([pk, indexBuffer]) 118 | } else { // Normal child 119 | // data = serP(point(kpar)) || ser32(index) 120 | // = serP(Kpar) || ser32(index) 121 | data = Buffer.concat([this.publicKey, indexBuffer]) 122 | } 123 | 124 | var I = crypto.createHmac('sha512', this.chainCode).update(data).digest() 125 | var IL = I.slice(0, 32) 126 | var IR = I.slice(32) 127 | 128 | var hd = new HDKey(this.versions) 129 | 130 | // Private parent key -> private child key 131 | if (this.privateKey) { 132 | // ki = parse256(IL) + kpar (mod n) 133 | try { 134 | hd.privateKey = Buffer.from(secp256k1.privateKeyTweakAdd(Buffer.from(this.privateKey), IL)) 135 | // throw if IL >= n || (privateKey + IL) === 0 136 | } catch (err) { 137 | // In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i 138 | return this.deriveChild(index + 1) 139 | } 140 | // Public parent key -> public child key 141 | } else { 142 | // Ki = point(parse256(IL)) + Kpar 143 | // = G*IL + Kpar 144 | try { 145 | hd.publicKey = Buffer.from(secp256k1.publicKeyTweakAdd(Buffer.from(this.publicKey), IL, true)) 146 | // throw if IL >= n || (g**IL + publicKey) is infinity 147 | } catch (err) { 148 | // In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i 149 | return this.deriveChild(index + 1) 150 | } 151 | } 152 | 153 | hd.chainCode = IR 154 | hd.depth = this.depth + 1 155 | hd.parentFingerprint = this.fingerprint// .readUInt32BE(0) 156 | hd.index = index 157 | 158 | return hd 159 | } 160 | 161 | HDKey.prototype.sign = function (hash) { 162 | return Buffer.from(secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(this.privateKey)).signature) 163 | } 164 | 165 | HDKey.prototype.verify = function (hash, signature) { 166 | return secp256k1.ecdsaVerify( 167 | Uint8Array.from(signature), 168 | Uint8Array.from(hash), 169 | Uint8Array.from(this.publicKey) 170 | ) 171 | } 172 | 173 | HDKey.prototype.wipePrivateData = function () { 174 | if (this._privateKey) crypto.randomBytes(this._privateKey.length).copy(this._privateKey) 175 | this._privateKey = null 176 | return this 177 | } 178 | 179 | HDKey.prototype.toJSON = function () { 180 | return { 181 | xpriv: this.privateExtendedKey, 182 | xpub: this.publicExtendedKey 183 | } 184 | } 185 | 186 | HDKey.fromMasterSeed = function (seedBuffer, versions) { 187 | var I = crypto.createHmac('sha512', MASTER_SECRET).update(seedBuffer).digest() 188 | var IL = I.slice(0, 32) 189 | var IR = I.slice(32) 190 | 191 | var hdkey = new HDKey(versions) 192 | hdkey.chainCode = IR 193 | hdkey.privateKey = IL 194 | 195 | return hdkey 196 | } 197 | 198 | HDKey.fromExtendedKey = function (base58key, versions, skipVerification) { 199 | // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) 200 | versions = versions || BITCOIN_VERSIONS 201 | skipVerification = skipVerification || false 202 | var hdkey = new HDKey(versions) 203 | 204 | var keyBuffer = bs58check.decode(base58key) 205 | 206 | var version = keyBuffer.readUInt32BE(0) 207 | assert(version === versions.private || version === versions.public, 'Version mismatch: does not match private or public') 208 | 209 | hdkey.depth = keyBuffer.readUInt8(4) 210 | hdkey.parentFingerprint = keyBuffer.readUInt32BE(5) 211 | hdkey.index = keyBuffer.readUInt32BE(9) 212 | hdkey.chainCode = keyBuffer.slice(13, 45) 213 | 214 | var key = keyBuffer.slice(45) 215 | if (key.readUInt8(0) === 0) { // private 216 | assert(version === versions.private, 'Version mismatch: version does not match private') 217 | hdkey.privateKey = key.slice(1) // cut off first 0x0 byte 218 | } else { 219 | assert(version === versions.public, 'Version mismatch: version does not match public') 220 | if (skipVerification) { 221 | setPublicKey(hdkey, key) 222 | } else { 223 | hdkey.publicKey = key 224 | } 225 | } 226 | 227 | return hdkey 228 | } 229 | 230 | HDKey.fromJSON = function (obj) { 231 | return HDKey.fromExtendedKey(obj.xpriv) 232 | } 233 | 234 | function serialize (hdkey, version, key) { 235 | // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) 236 | var buffer = Buffer.allocUnsafe(LEN) 237 | 238 | buffer.writeUInt32BE(version, 0) 239 | buffer.writeUInt8(hdkey.depth, 4) 240 | 241 | var fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000 242 | buffer.writeUInt32BE(fingerprint, 5) 243 | buffer.writeUInt32BE(hdkey.index, 9) 244 | 245 | hdkey.chainCode.copy(buffer, 13) 246 | key.copy(buffer, 45) 247 | 248 | return buffer 249 | } 250 | 251 | function hash160 (buf) { 252 | var sha = crypto.createHash('sha256').update(buf).digest() 253 | return new RIPEMD160().update(sha).digest() 254 | } 255 | 256 | HDKey.HARDENED_OFFSET = HARDENED_OFFSET 257 | module.exports = HDKey 258 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hdkey", 3 | "version": "2.1.0", 4 | "description": "Bitcoin BIP32 hierarchical deterministic keys", 5 | "main": "lib/hdkey.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/cryptocoinjs/hdkey" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "bitcoin", 13 | "bip32", 14 | "bip", 15 | "key", 16 | "hierarchical", 17 | "deterministic", 18 | "crypto" 19 | ], 20 | "bugs": { 21 | "url": "https://github.com/cryptocoinjs/hdkey/issues" 22 | }, 23 | "homepage": "https://github.com/cryptocoinjs/hdkey", 24 | "files": [], 25 | "devDependencies": { 26 | "bigi": "^1.1.0", 27 | "coveralls": "^3.0.4", 28 | "ecurve": "^1.0.0", 29 | "istanbul": "^0.4.5", 30 | "mocha": "^6.1.4", 31 | "mocha-lcov-reporter": "0.0.1", 32 | "mochify": "^6.3.0", 33 | "secure-random": "^1.0.0", 34 | "standard": "^7.1.1" 35 | }, 36 | "dependencies": { 37 | "bs58check": "^2.1.2", 38 | "ripemd160": "^2.0.2", 39 | "safe-buffer": "^5.1.1", 40 | "secp256k1": "^4.0.0" 41 | }, 42 | "scripts": { 43 | "lint": "standard", 44 | "browser-test": "mochify --wd -R spec", 45 | "test": "standard && mocha", 46 | "unit": "mocha", 47 | "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter list test/*.js", 48 | "coveralls": "npm run-script coverage && node ./node_modules/.bin/coveralls < coverage/lcov.info" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/fixtures/hdkey.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid": [ 3 | { 4 | "seed": "000102030405060708090a0b0c0d0e0f", 5 | "path": "m", 6 | "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", 7 | "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" 8 | }, 9 | { 10 | "seed": "000102030405060708090a0b0c0d0e0f", 11 | "path": "m/0'", 12 | "public": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", 13 | "private": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" 14 | }, 15 | { 16 | "seed": "000102030405060708090a0b0c0d0e0f", 17 | "path": "m/0'/1", 18 | "public": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", 19 | "private": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" 20 | }, 21 | { 22 | "seed": "000102030405060708090a0b0c0d0e0f", 23 | "path": "m/0'/1/2'", 24 | "public": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", 25 | "private": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" 26 | }, 27 | { 28 | "seed": "000102030405060708090a0b0c0d0e0f", 29 | "path": "m/0'/1/2'/2", 30 | "public": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", 31 | "private": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" 32 | }, 33 | { 34 | "seed": "000102030405060708090a0b0c0d0e0f", 35 | "path": "m/0'/1/2'/2/1000000000", 36 | "public": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", 37 | "private": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" 38 | }, 39 | { 40 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 41 | "path": "m", 42 | "public": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", 43 | "private": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" 44 | }, 45 | { 46 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 47 | "path": "m/0", 48 | "public": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", 49 | "private": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" 50 | }, 51 | { 52 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 53 | "path": "m/0/2147483647'", 54 | "public": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", 55 | "private": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" 56 | }, 57 | { 58 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 59 | "path": "m/0/2147483647'/1", 60 | "public": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", 61 | "private": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" 62 | }, 63 | { 64 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 65 | "path": "m/0/2147483647'/1/2147483646'", 66 | "public": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", 67 | "private": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" 68 | }, 69 | { 70 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 71 | "path": "m/0/2147483647'/1/2147483646'/2", 72 | "public": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", 73 | "private": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" 74 | } 75 | ], 76 | "invalid": [ 77 | 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /test/hdkey.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var BigInteger = require('bigi') 3 | var Buffer = require('safe-buffer').Buffer 4 | var ecurve = require('ecurve') 5 | var secureRandom = require('secure-random') 6 | var curve = ecurve.getCurveByName('secp256k1') 7 | var HDKey = require('../') 8 | var fixtures = require('./fixtures/hdkey') 9 | 10 | // trinity: mocha 11 | /* global describe it */ 12 | 13 | describe('hdkey', function () { 14 | describe('+ fromMasterSeed', function () { 15 | fixtures.valid.forEach(function (f) { 16 | it('should properly derive the chain path: ' + f.path, function () { 17 | var hdkey = HDKey.fromMasterSeed(Buffer.from(f.seed, 'hex')) 18 | var childkey = hdkey.derive(f.path) 19 | 20 | assert.equal(childkey.privateExtendedKey, f.private) 21 | assert.equal(childkey.publicExtendedKey, f.public) 22 | }) 23 | 24 | describe('> ' + f.path + ' toJSON() / fromJSON()', function () { 25 | it('should return an object read for JSON serialization', function () { 26 | var hdkey = HDKey.fromMasterSeed(Buffer.from(f.seed, 'hex')) 27 | var childkey = hdkey.derive(f.path) 28 | 29 | var obj = { 30 | xpriv: f.private, 31 | xpub: f.public 32 | } 33 | 34 | assert.deepEqual(childkey.toJSON(), obj) 35 | 36 | var newKey = HDKey.fromJSON(obj) 37 | assert.strictEqual(newKey.privateExtendedKey, f.private) 38 | assert.strictEqual(newKey.publicExtendedKey, f.public) 39 | }) 40 | }) 41 | }) 42 | }) 43 | 44 | describe('- privateKey', function () { 45 | it('should throw an error if incorrect key size', function () { 46 | var hdkey = new HDKey() 47 | assert.throws(function () { 48 | hdkey.privateKey = Buffer.from([1, 2, 3, 4]) 49 | }, /key must be 32/) 50 | }) 51 | }) 52 | 53 | describe('- publicKey', function () { 54 | it('should throw an error if incorrect key size', function () { 55 | assert.throws(function () { 56 | var hdkey = new HDKey() 57 | hdkey.publicKey = Buffer.from([1, 2, 3, 4]) 58 | }, /key must be 33 or 65/) 59 | }) 60 | 61 | it('should not throw if key is 33 bytes (compressed)', function () { 62 | var priv = secureRandom.randomBuffer(32) 63 | var pub = curve.G.multiply(BigInteger.fromBuffer(priv)).getEncoded(true) 64 | assert.equal(pub.length, 33) 65 | var hdkey = new HDKey() 66 | hdkey.publicKey = pub 67 | }) 68 | 69 | it('should not throw if key is 65 bytes (not compressed)', function () { 70 | var priv = secureRandom.randomBuffer(32) 71 | var pub = curve.G.multiply(BigInteger.fromBuffer(priv)).getEncoded(false) 72 | assert.equal(pub.length, 65) 73 | var hdkey = new HDKey() 74 | hdkey.publicKey = pub 75 | }) 76 | }) 77 | 78 | describe('+ fromExtendedKey()', function () { 79 | describe('> when private', function () { 80 | it('should parse it', function () { 81 | // m/0/2147483647'/1/2147483646'/2 82 | var key = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j' 83 | var hdkey = HDKey.fromExtendedKey(key) 84 | assert.equal(hdkey.versions.private, 0x0488ade4) 85 | assert.equal(hdkey.versions.public, 0x0488b21e) 86 | assert.equal(hdkey.depth, 5) 87 | assert.equal(hdkey.parentFingerprint, 0x31a507b8) 88 | assert.equal(hdkey.index, 2) 89 | assert.equal(hdkey.chainCode.toString('hex'), '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271') 90 | assert.equal(hdkey.privateKey.toString('hex'), 'bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23') 91 | assert.equal(hdkey.publicKey.toString('hex'), '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c') 92 | assert.equal(hdkey.identifier.toString('hex'), '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220') 93 | }) 94 | }) 95 | 96 | describe('> when public', function () { 97 | it('should parse it', function () { 98 | // m/0/2147483647'/1/2147483646'/2 99 | var key = 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt' 100 | var hdkey = HDKey.fromExtendedKey(key) 101 | assert.equal(hdkey.versions.private, 0x0488ade4) 102 | assert.equal(hdkey.versions.public, 0x0488b21e) 103 | assert.equal(hdkey.depth, 5) 104 | assert.equal(hdkey.parentFingerprint, 0x31a507b8) 105 | assert.equal(hdkey.index, 2) 106 | assert.equal(hdkey.chainCode.toString('hex'), '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271') 107 | assert.equal(hdkey.privateKey, null) 108 | assert.equal(hdkey.publicKey.toString('hex'), '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c') 109 | assert.equal(hdkey.identifier.toString('hex'), '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220') 110 | }) 111 | 112 | it('should parse it without verification', function () { 113 | // m/0/2147483647'/1/2147483646'/2 114 | var key = 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt' 115 | var hdkey = HDKey.fromExtendedKey(key, null, false) 116 | assert.equal(hdkey.versions.private, 0x0488ade4) 117 | assert.equal(hdkey.versions.public, 0x0488b21e) 118 | assert.equal(hdkey.depth, 5) 119 | assert.equal(hdkey.parentFingerprint, 0x31a507b8) 120 | assert.equal(hdkey.index, 2) 121 | assert.equal(hdkey.chainCode.toString('hex'), '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271') 122 | assert.equal(hdkey.privateKey, null) 123 | assert.equal(hdkey.publicKey.toString('hex'), '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c') 124 | assert.equal(hdkey.identifier.toString('hex'), '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220') 125 | }) 126 | }) 127 | }) 128 | 129 | describe('> when signing', function () { 130 | it('should work', function () { 131 | var key = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j' 132 | var hdkey = HDKey.fromExtendedKey(key) 133 | 134 | var ma = Buffer.alloc(32, 0) 135 | var mb = Buffer.alloc(32, 8) 136 | var a = hdkey.sign(ma) 137 | var b = hdkey.sign(mb) 138 | assert.equal(a.toString('hex'), '6ba4e554457ce5c1f1d7dbd10459465e39219eb9084ee23270688cbe0d49b52b7905d5beb28492be439a3250e9359e0390f844321b65f1a88ce07960dd85da06') 139 | assert.equal(b.toString('hex'), 'dfae85d39b73c9d143403ce472f7c4c8a5032c13d9546030044050e7d39355e47a532e5c0ae2a25392d97f5e55ab1288ef1e08d5c034bad3b0956fbbab73b381') 140 | assert.equal(hdkey.verify(ma, a), true) 141 | assert.equal(hdkey.verify(mb, b), true) 142 | assert.equal(hdkey.verify(Buffer.alloc(32), Buffer.alloc(64)), false) 143 | assert.equal(hdkey.verify(ma, b), false) 144 | assert.equal(hdkey.verify(mb, a), false) 145 | 146 | assert.throws(function () { 147 | hdkey.verify(Buffer.alloc(99), a) 148 | }, /message.*length/) 149 | assert.throws(function () { 150 | hdkey.verify(ma, Buffer.alloc(99)) 151 | }, /signature.*length/) 152 | }) 153 | }) 154 | 155 | describe('> when deriving public key', function () { 156 | it('should work', function () { 157 | var key = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8' 158 | var hdkey = HDKey.fromExtendedKey(key) 159 | 160 | var path = 'm/3353535/2223/0/99424/4/33' 161 | var derivedHDKey = hdkey.derive(path) 162 | 163 | var expected = 'xpub6JdKdVJtdx6sC3nh87pDvnGhotXuU5Kz6Qy7Piy84vUAwWSYShsUGULE8u6gCivTHgz7cCKJHiXaaMeieB4YnoFVAsNgHHKXJ2mN6jCMbH1' 164 | assert.equal(derivedHDKey.publicExtendedKey, expected) 165 | }) 166 | }) 167 | 168 | describe('> when private key integer is less than 32 bytes', function () { 169 | it('should work', function () { 170 | var seed = '000102030405060708090a0b0c0d0e0f' 171 | var masterKey = HDKey.fromMasterSeed(Buffer.from(seed, 'hex')) 172 | 173 | var newKey = masterKey.derive("m/44'/6'/4'") 174 | var expected = 'xprv9ymoag6W7cR6KBcJzhCM6qqTrb3rRVVwXKzwNqp1tDWcwierEv3BA9if3ARHMhMPh9u2jNoutcgpUBLMfq3kADDo7LzfoCnhhXMRGX3PXDx' 175 | assert.equal(newKey.privateExtendedKey, expected) 176 | }) 177 | }) 178 | 179 | describe('HARDENED_OFFSET', function () { 180 | it('should be set', function () { 181 | assert(HDKey.HARDENED_OFFSET) 182 | }) 183 | }) 184 | 185 | describe('> when private key has leading zeros', function () { 186 | it('will include leading zeros when hashing to derive child', function () { 187 | var key = 'xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr' 188 | var hdkey = HDKey.fromExtendedKey(key) 189 | assert.equal(hdkey.privateKey.toString('hex'), '00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd') 190 | var derived = hdkey.derive("m/44'/0'/0'/0/0'") 191 | assert.equal(derived.privateKey.toString('hex'), '3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb') 192 | }) 193 | }) 194 | 195 | describe('> when private key is null', function () { 196 | it('privateExtendedKey should return null and not throw', function () { 197 | var seed = '000102030405060708090a0b0c0d0e0f' 198 | var masterKey = HDKey.fromMasterSeed(Buffer.from(seed, 'hex')) 199 | 200 | assert.ok(masterKey.privateExtendedKey, 'xpriv is truthy') 201 | masterKey._privateKey = null 202 | 203 | assert.doesNotThrow(function () { 204 | masterKey.privateExtendedKey 205 | }) 206 | 207 | assert.ok(!masterKey.privateExtendedKey, 'xpriv is falsy') 208 | }) 209 | }) 210 | 211 | describe(' - when the path given to derive contains only the master extended key', function () { 212 | const hdKeyInstance = HDKey.fromMasterSeed(Buffer.from(fixtures.valid[0].seed, 'hex')) 213 | 214 | it('should return the same hdkey instance', function () { 215 | assert.equal(hdKeyInstance.derive('m'), hdKeyInstance) 216 | assert.equal(hdKeyInstance.derive('M'), hdKeyInstance) 217 | assert.equal(hdKeyInstance.derive("m'"), hdKeyInstance) 218 | assert.equal(hdKeyInstance.derive("M'"), hdKeyInstance) 219 | }) 220 | }) 221 | 222 | describe(' - when the path given to derive does not begin with master extended key', function () { 223 | it('should throw an error', function () { 224 | assert.throws(function () { 225 | HDKey.prototype.derive('123') 226 | }, /Path must start with "m" or "M"/) 227 | }) 228 | }) 229 | 230 | describe('- after wipePrivateData()', function () { 231 | it('should not have private data', function () { 232 | const hdkey = HDKey.fromMasterSeed(Buffer.from(fixtures.valid[6].seed, 'hex')).wipePrivateData() 233 | assert.equal(hdkey.privateKey, null) 234 | assert.equal(hdkey.privateExtendedKey, null) 235 | assert.throws(() => hdkey.sign(Buffer.alloc(32)), "shouldn't be able to sign") 236 | const childKey = hdkey.derive('m/0') 237 | assert.equal(childKey.publicExtendedKey, fixtures.valid[7].public) 238 | assert.equal(childKey.privateKey, null) 239 | assert.equal(childKey.privateExtendedKey, null) 240 | }) 241 | 242 | it('should have correct data', function () { 243 | // m/0/2147483647'/1/2147483646'/2 244 | const key = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j' 245 | const hdkey = HDKey.fromExtendedKey(key).wipePrivateData() 246 | assert.equal(hdkey.versions.private, 0x0488ade4) 247 | assert.equal(hdkey.versions.public, 0x0488b21e) 248 | assert.equal(hdkey.depth, 5) 249 | assert.equal(hdkey.parentFingerprint, 0x31a507b8) 250 | assert.equal(hdkey.index, 2) 251 | assert.equal(hdkey.chainCode.toString('hex'), '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271') 252 | assert.equal(hdkey.publicKey.toString('hex'), '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c') 253 | assert.equal(hdkey.identifier.toString('hex'), '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220') 254 | }) 255 | 256 | it('should be able to verify signatures', function () { 257 | const fullKey = HDKey.fromMasterSeed(fixtures.valid[0].seed) 258 | // using JSON methods to clone before mutating 259 | const wipedKey = HDKey.fromJSON(fullKey.toJSON()).wipePrivateData() 260 | 261 | const hash = Buffer.alloc(32, 8) 262 | assert.ok(wipedKey.verify(hash, fullKey.sign(hash))) 263 | }) 264 | 265 | it('should not throw if called on hdkey without private data', function () { 266 | const hdkey = HDKey.fromExtendedKey(fixtures.valid[0].public) 267 | assert.doesNotThrow(() => hdkey.wipePrivateData()) 268 | assert.equal(hdkey.publicExtendedKey, fixtures.valid[0].public) 269 | }) 270 | }) 271 | 272 | describe('Deriving a child key does not mutate the internal state', function () { 273 | it('should not mutate it when deriving with a private key', function () { 274 | const hdkey = HDKey.fromExtendedKey(fixtures.valid[0].private) 275 | const path = 'm/123' 276 | const privateKeyBefore = hdkey.privateKey.toString('hex') 277 | 278 | const child = hdkey.derive(path) 279 | assert.equal(hdkey.privateKey.toString('hex'), privateKeyBefore) 280 | 281 | const child2 = hdkey.derive(path) 282 | assert.equal(hdkey.privateKey.toString('hex'), privateKeyBefore) 283 | 284 | const child3 = hdkey.derive(path) 285 | assert.equal(hdkey.privateKey.toString('hex'), privateKeyBefore) 286 | 287 | assert.equal(child.privateKey.toString('hex'), child2.privateKey.toString('hex')) 288 | assert.equal(child2.privateKey.toString('hex'), child3.privateKey.toString('hex')) 289 | }) 290 | 291 | it('should not mutate it when deriving without a private key', function () { 292 | const hdkey = HDKey.fromExtendedKey(fixtures.valid[0].private) 293 | const path = 'm/123/123/123' 294 | hdkey.wipePrivateData() 295 | 296 | const publicKeyBefore = hdkey.publicKey.toString('hex') 297 | 298 | const child = hdkey.derive(path) 299 | assert.equal(hdkey.publicKey.toString('hex'), publicKeyBefore) 300 | 301 | const child2 = hdkey.derive(path) 302 | assert.equal(hdkey.publicKey.toString('hex'), publicKeyBefore) 303 | 304 | const child3 = hdkey.derive(path) 305 | assert.equal(hdkey.publicKey.toString('hex'), publicKeyBefore) 306 | 307 | assert.equal(child.publicKey.toString('hex'), child2.publicKey.toString('hex')) 308 | assert.equal(child2.publicKey.toString('hex'), child3.publicKey.toString('hex')) 309 | }) 310 | }) 311 | }) 312 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --ui bdd --------------------------------------------------------------------------------