Support HD keys using cryptocoinjs/hdkey
This commit is contained in:
parent
f72c9679c1
commit
c6da9114f9
29
README.md
29
README.md
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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",
|
||||||
|
|
|
@ -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')
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue