Support HD keys using cryptocoinjs/hdkey

This commit is contained in:
Alex Beregszaszi 2016-03-23 02:25:44 +00:00
parent f72c9679c1
commit c6da9114f9
5 changed files with 152 additions and 3 deletions

View File

@ -17,7 +17,7 @@ Features not supported:
- signing transactions - signing transactions
- managing storage (neither in node.js or the browser) - managing storage (neither in node.js or the browser)
## API ## Wallet API
Constructors: Constructors:
@ -54,6 +54,27 @@ Instance methods:
All of the above instance methods return a Buffer or JSON. Use the `String` suffixed versions for a string output, such as `getPrivateKeyString()`. All of the above instance methods return a Buffer or JSON. Use the `String` suffixed versions for a string output, such as `getPrivateKeyString()`.
## HD Wallet API
To use BIP32 HD wallets, first include the `hdkey` submodule:
`var hdkey = require('ethereumjs-wallet/hdkey')`
Constructors:
* `fromMasterSeed(seed)` - create an instance based on a seed
* `formExtendedKey(key)` - create an instance based on a BIP32 extended private or public key
For the seed we suggest to use [bip39](https://npmjs.org/packages/bip39) to create one from a BIP39 mnemonic.
Instance methods:
* `privateExtendedKey()` - return a BIP32 extended private key
* `publicExtendedKey()` - return a BIP32 extended private key
* `derivePath(path)` - derive a node based on a path (e.g. m/44'/0'/0/1)
* `deriveChild(index)` - derive a node based on a child index
* `getWallet()` - return a `Wallet` instance as seen above
### Remarks about `toV3` ### Remarks about `toV3`
The `options` is an optional object hash, where all the serialization parameters can be fine tuned: The `options` is an optional object hash, where all the serialization parameters can be fine tuned:
@ -82,3 +103,9 @@ The following settings are favoured by the Go Ethereum implementation and we def
- `r`: `8` - `r`: `8`
- `p`: `1` - `p`: `1`
- `cipher`: `aes-128-ctr` - `cipher`: `aes-128-ctr`
## License
MIT License
Copyright (C) 2016 Alex Beregszaszi

48
hdkey.js Normal file
View File

@ -0,0 +1,48 @@
const HDKey = require('hdkey')
const Wallet = require('./index.js')
function EthereumHDKey () {
}
/*
* Horrible wrapping.
*/
function fromHDKey (hdkey) {
var ret = new EthereumHDKey()
ret._hdkey = hdkey
return ret
}
EthereumHDKey.fromMasterSeed = function (seedBuffer) {
return fromHDKey(HDKey.fromMasterSeed(seedBuffer))
}
EthereumHDKey.fromExtendedKey = function (base58key) {
return fromHDKey(HDKey.fromExtendedKey(base58key))
}
EthereumHDKey.prototype.privateExtendedKey = function () {
return this._hdkey.privateExtendedKey
}
EthereumHDKey.prototype.publicExtendedKey = function () {
return this._hdkey.publicExtendedKey
}
EthereumHDKey.prototype.derivePath = function (path) {
return fromHDKey(this._hdkey.derive(path))
}
EthereumHDKey.prototype.deriveChild = function (index) {
return fromHDKey(this._hdkey.deriveChild(index))
}
EthereumHDKey.prototype.getWallet = function () {
if (this._hdkey._privateKey) {
return Wallet.fromPrivateKey(this._hdkey._privateKey)
} else {
return Wallet.fromPublicKey(this._hdkey._publicKey)
}
}
module.exports = EthereumHDKey

View File

@ -6,7 +6,7 @@
"scripts": { "scripts": {
"lint": "standard", "lint": "standard",
"prepublish": "npm run lint && npm run test", "prepublish": "npm run lint && npm run test",
"test": "mocha ./test/index.js" "test": "mocha ./test/index.js && mocha ./test/hdkey.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -27,6 +27,7 @@
"aes-js": "^0.2.3", "aes-js": "^0.2.3",
"bs58check": "^1.0.8", "bs58check": "^1.0.8",
"ethereumjs-util": "^4.3.0", "ethereumjs-util": "^4.3.0",
"hdkey": "git+https://github.com/axic/hdkey#feature/secp256k1",
"scrypt.js": "^0.1.0", "scrypt.js": "^0.1.0",
"secp256k1": "^3.0.1", "secp256k1": "^3.0.1",
"uuid": "^2.0.1", "uuid": "^2.0.1",

74
test/hdkey.js Normal file
View File

@ -0,0 +1,74 @@
var assert = require('assert')
var HDKey = require('../hdkey.js')
// from BIP39 mnemonic: awake book subject inch gentle blur grant damage process float month clown
var fixtureseed = new Buffer('747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03', 'hex')
var fixturehd = HDKey.fromMasterSeed(fixtureseed)
describe('.fromMasterSeed()', function () {
it('should work', function () {
assert.doesNotThrow(function () {
HDKey.fromMasterSeed(fixtureseed)
})
})
})
describe('.privateExtendedKey()', function () {
it('should work', function () {
assert.equal(fixturehd.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
})
})
describe('.publicExtendedKey()', function () {
it('should work', function () {
assert.equal(fixturehd.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
})
})
describe('.fromExtendedKey()', function () {
it('should work with public', function () {
var hdnode = HDKey.fromExtendedKey('xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
assert.equal(hdnode.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
assert.throws(function () {
hdnode.privateExtendedKey()
})
})
it('should work with private', function () {
var hdnode = HDKey.fromExtendedKey('xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
assert.equal(hdnode.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
assert.equal(hdnode.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
})
})
describe('.deriveChild()', function () {
it('should work', function () {
var hdnode = fixturehd.deriveChild(1)
assert.equal(hdnode.privateExtendedKey(), 'xprv9vYSvrg3eR5FaKbQE4Ao2vHdyvfFL27aWMyH6X818mKWMsqqQZAN6HmRqYDGDPLArzaqbLExRsxFwtx2B2X2QKkC9uoKsiBNi22tLPKZHNS')
})
})
describe('.derivePath()', function () {
it('should work with m', function () {
var hdnode = fixturehd.derivePath('m')
assert.equal(hdnode.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
})
it('should work with m/44\'/0\'/0/1', function () {
var hdnode = fixturehd.derivePath('m/44\'/0\'/0/1')
assert.equal(hdnode.privateExtendedKey(), 'xprvA1ErCzsuXhpB8iDTsbmgpkA2P8ggu97hMZbAXTZCdGYeaUrDhyR8fEw47BNEgLExsWCVzFYuGyeDZJLiFJ9kwBzGojQ6NB718tjVJrVBSrG')
})
})
describe('.getWallet()', function () {
it('should work', function () {
assert.equal(fixturehd.getWallet().getPrivateKeyString(), '0x26cc9417b89cd77c4acdbe2e3cd286070a015d8e380f9cd1244ae103b7d89d81')
assert.equal(fixturehd.getWallet().getPublicKeyString(),
'0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1')
})
it('should work with public nodes', function () {
var hdnode = HDKey.fromExtendedKey('xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
assert.throws(function () {
hdnode.getWallet().getPrivateKeyString()
})
assert.equal(hdnode.getWallet().getPublicKeyString(), '0x030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d')
})
})

View File

@ -87,7 +87,6 @@ describe('.fromExtendedPublicKey()', function () {
}) })
}) })
describe('.generate()', function () { describe('.generate()', function () {
it('should generate an account', function () { it('should generate an account', function () {
assert.equal(Wallet.generate().getPrivateKey().length, 32) assert.equal(Wallet.generate().getPrivateKey().length, 32)