9 changed files with 705 additions and 534 deletions
@ -0,0 +1,285 @@ |
|||
var assert = require('assert') |
|||
var base58 = require('./base58') |
|||
|
|||
var BigInteger = require('bigi') |
|||
var crypto = require('./crypto') |
|||
var ECKey = require('./eckey') |
|||
var ECPubKey = require('./ecpubkey') |
|||
var ECPointFp = require('./ec').ECPointFp |
|||
var networks = require('./networks') |
|||
|
|||
var sec = require('./sec') |
|||
var ecparams = sec("secp256k1") |
|||
|
|||
function findBIP32ParamsByVersion(version) { |
|||
for (var name in networks) { |
|||
var network = networks[name] |
|||
|
|||
for (var type in network.bip32) { |
|||
if (version != network.bip32[type]) continue |
|||
|
|||
return { |
|||
isPrivate: (type === 'private'), |
|||
network: network |
|||
} |
|||
} |
|||
} |
|||
|
|||
assert(false, 'Could not find version ' + version.toString(16)) |
|||
} |
|||
|
|||
function HDNode(K, chainCode, network) { |
|||
network = network || networks.bitcoin |
|||
|
|||
assert(Buffer.isBuffer(chainCode), 'Expected Buffer, got ' + chainCode) |
|||
assert(network.bip32, 'Unknown BIP32 constants for network') |
|||
|
|||
this.chainCode = chainCode |
|||
this.depth = 0 |
|||
this.index = 0 |
|||
this.network = network |
|||
|
|||
if (K instanceof BigInteger) { |
|||
this.privKey = new ECKey(K, true) |
|||
this.pubKey = this.privKey.pub |
|||
} else { |
|||
this.pubKey = new ECPubKey(K, true) |
|||
} |
|||
} |
|||
|
|||
HDNode.MASTER_SECRET = new Buffer('Bitcoin seed') |
|||
HDNode.HIGHEST_BIT = 0x80000000 |
|||
HDNode.LENGTH = 78 |
|||
|
|||
HDNode.fromSeedBuffer = function(seed, network) { |
|||
var I = crypto.HmacSHA512(seed, HDNode.MASTER_SECRET) |
|||
var IL = I.slice(0, 32) |
|||
var IR = I.slice(32) |
|||
|
|||
// In case IL is 0 or >= n, the master key is invalid
|
|||
// This is handled by `new ECKey` in the HDNode constructor
|
|||
var pIL = BigInteger.fromBuffer(IL) |
|||
|
|||
return new HDNode(pIL, IR, network) |
|||
} |
|||
|
|||
HDNode.fromSeedHex = function(hex, network) { |
|||
return HDNode.fromSeedBuffer(new Buffer(hex, 'hex'), network) |
|||
} |
|||
|
|||
HDNode.fromBase58 = function(string) { |
|||
var buffer = base58.decode(string) |
|||
|
|||
var payload = buffer.slice(0, -4) |
|||
var checksum = buffer.slice(-4) |
|||
|
|||
var newChecksum = crypto.hash256(payload).slice(0, 4) |
|||
assert.deepEqual(newChecksum, checksum, 'Invalid checksum') |
|||
|
|||
return HDNode.fromBuffer(payload) |
|||
} |
|||
|
|||
HDNode.fromBuffer = function(buffer) { |
|||
assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length') |
|||
|
|||
// 4 byte: version bytes
|
|||
var version = buffer.readUInt32BE(0) |
|||
var params = findBIP32ParamsByVersion(version) |
|||
|
|||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
|
|||
var depth = buffer.readUInt8(4) |
|||
|
|||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
|||
var parentFingerprint = buffer.readUInt32BE(5) |
|||
if (depth === 0) { |
|||
assert.strictEqual(parentFingerprint, 0x00000000, 'Invalid parent fingerprint') |
|||
} |
|||
|
|||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
|||
// This is encoded in MSB order. (0x00000000 if master key)
|
|||
var index = buffer.readUInt32BE(9) |
|||
assert(depth > 0 || index === 0, 'Invalid index') |
|||
|
|||
// 32 bytes: the chain code
|
|||
var chainCode = buffer.slice(13, 45) |
|||
var hd |
|||
|
|||
// 33 bytes: private key data (0x00 + k)
|
|||
if (params.isPrivate) { |
|||
assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key') |
|||
var data = buffer.slice(46, 78) |
|||
var D = BigInteger.fromBuffer(data) |
|||
hd = new HDNode(D, chainCode, params.network) |
|||
|
|||
// 33 bytes: public key data (0x02 + X or 0x03 + X)
|
|||
} else { |
|||
var data = buffer.slice(45, 78) |
|||
var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) |
|||
assert.equal(decode.compressed, true, 'Invalid public key') |
|||
|
|||
// Verify that the X coordinate in the public point corresponds to a point on the curve.
|
|||
// If not, the extended public key is invalid.
|
|||
decode.Q.validate() |
|||
|
|||
hd = new HDNode(decode.Q, chainCode, params.network) |
|||
} |
|||
|
|||
hd.depth = depth |
|||
hd.index = index |
|||
hd.parentFingerprint = parentFingerprint |
|||
|
|||
return hd |
|||
} |
|||
|
|||
HDNode.fromHex = function(hex) { |
|||
return HDNode.fromBuffer(new Buffer(hex, 'hex')) |
|||
} |
|||
|
|||
HDNode.prototype.getIdentifier = function() { |
|||
return crypto.hash160(this.pubKey.toBuffer()) |
|||
} |
|||
|
|||
HDNode.prototype.getFingerprint = function() { |
|||
return this.getIdentifier().slice(0, 4) |
|||
} |
|||
|
|||
HDNode.prototype.getAddress = function() { |
|||
return this.pubKey.getAddress(this.network.pubKeyHash) |
|||
} |
|||
|
|||
HDNode.prototype.toBase58 = function(isPrivate) { |
|||
var buffer = this.toBuffer(isPrivate) |
|||
var checksum = crypto.hash256(buffer).slice(0, 4) |
|||
|
|||
return base58.encode(Buffer.concat([ |
|||
buffer, |
|||
checksum |
|||
])) |
|||
} |
|||
|
|||
HDNode.prototype.toBuffer = function(isPrivate) { |
|||
if (isPrivate == undefined) isPrivate = !!this.privKey |
|||
|
|||
// Version
|
|||
var version = isPrivate ? this.network.bip32.private : this.network.bip32.public |
|||
var buffer = new Buffer(HDNode.LENGTH) |
|||
|
|||
// 4 bytes: version bytes
|
|||
buffer.writeUInt32BE(version, 0) |
|||
|
|||
// Depth
|
|||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
|
|||
buffer.writeUInt8(this.depth, 4) |
|||
|
|||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
|||
var fingerprint = (this.depth === 0) ? 0x00000000 : this.parentFingerprint |
|||
buffer.writeUInt32BE(fingerprint, 5) |
|||
|
|||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
|||
// This is encoded in Big endian. (0x00000000 if master key)
|
|||
buffer.writeUInt32BE(this.index, 9) |
|||
|
|||
// 32 bytes: the chain code
|
|||
this.chainCode.copy(buffer, 13) |
|||
|
|||
// 33 bytes: the public key or private key data
|
|||
if (isPrivate) { |
|||
assert(this.privKey, 'Missing private key') |
|||
|
|||
// 0x00 + k for private keys
|
|||
buffer.writeUInt8(0, 45) |
|||
this.privKey.D.toBuffer(32).copy(buffer, 46) |
|||
} else { |
|||
|
|||
// X9.62 encoding for public keys
|
|||
this.pubKey.toBuffer().copy(buffer, 45) |
|||
} |
|||
|
|||
return buffer |
|||
} |
|||
|
|||
HDNode.prototype.toHex = function(isPrivate) { |
|||
return this.toBuffer(isPrivate).toString('hex') |
|||
} |
|||
|
|||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
|
|||
HDNode.prototype.derive = function(index) { |
|||
var isHardened = index >= HDNode.HIGHEST_BIT |
|||
var indexBuffer = new Buffer(4) |
|||
indexBuffer.writeUInt32BE(index, 0) |
|||
|
|||
var data |
|||
|
|||
// Hardened child
|
|||
if (isHardened) { |
|||
assert(this.privKey, 'Could not derive hardened child key') |
|||
|
|||
// data = 0x00 || ser256(kpar) || ser32(index)
|
|||
data = Buffer.concat([ |
|||
this.privKey.D.toBuffer(33), |
|||
indexBuffer |
|||
]) |
|||
|
|||
// Normal child
|
|||
} else { |
|||
// data = serP(point(kpar)) || ser32(index)
|
|||
// = serP(Kpar) || ser32(index)
|
|||
data = Buffer.concat([ |
|||
this.pubKey.toBuffer(), |
|||
indexBuffer |
|||
]) |
|||
} |
|||
|
|||
var I = crypto.HmacSHA512(data, this.chainCode) |
|||
var IL = I.slice(0, 32) |
|||
var IR = I.slice(32) |
|||
|
|||
var pIL = BigInteger.fromBuffer(IL) |
|||
|
|||
// In case parse256(IL) >= n, proceed with the next value for i
|
|||
if (pIL.compareTo(ecparams.getN()) >= 0) { |
|||
return this.derive(index + 1) |
|||
} |
|||
|
|||
// Private parent key -> private child key
|
|||
var hd |
|||
if (this.privKey) { |
|||
// ki = parse256(IL) + kpar (mod n)
|
|||
var ki = pIL.add(this.privKey.D).mod(ecparams.getN()) |
|||
|
|||
// In case ki == 0, proceed with the next value for i
|
|||
if (ki.signum() === 0) { |
|||
return this.derive(index + 1) |
|||
} |
|||
|
|||
hd = new HDNode(ki, IR, this.network) |
|||
|
|||
// Public parent key -> public child key
|
|||
} else { |
|||
// Ki = point(parse256(IL)) + Kpar
|
|||
// = G*IL + Kpar
|
|||
var Ki = ecparams.getG().multiply(pIL).add(this.pubKey.Q) |
|||
|
|||
// In case Ki is the point at infinity, proceed with the next value for i
|
|||
if (Ki.isInfinity()) { |
|||
return this.derive(index + 1) |
|||
} |
|||
|
|||
hd = new HDNode(Ki, IR, this.network) |
|||
} |
|||
|
|||
hd.depth = this.depth + 1 |
|||
hd.index = index |
|||
hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) |
|||
|
|||
return hd |
|||
} |
|||
|
|||
HDNode.prototype.deriveHardened = function(index) { |
|||
// Only derives hardened private keys by default
|
|||
return this.derive(index + HDNode.HIGHEST_BIT) |
|||
} |
|||
|
|||
HDNode.prototype.toString = HDNode.prototype.toBase58 |
|||
|
|||
module.exports = HDNode |
@ -1,260 +0,0 @@ |
|||
var assert = require('assert') |
|||
var base58 = require('./base58') |
|||
var convert = require('./convert') |
|||
|
|||
var Address = require('./address') |
|||
var BigInteger = require('bigi') |
|||
var crypto = require('./crypto') |
|||
var ECKey = require('./eckey') |
|||
var ECPubKey = require('./ecpubkey') |
|||
var networks = require('./networks') |
|||
|
|||
var sec = require('./sec') |
|||
var ecparams = sec("secp256k1") |
|||
|
|||
function HDWallet(seed, network) { |
|||
if (seed == undefined) return; // FIXME: Boo, should be stricter
|
|||
|
|||
network = network || networks.bitcoin |
|||
assert(network.bip32, 'Unknown BIP32 constants for network') |
|||
|
|||
var I = crypto.HmacSHA512(seed, HDWallet.MASTER_SECRET) |
|||
var IL = I.slice(0, 32) |
|||
var IR = I.slice(32) |
|||
|
|||
// In case IL is 0 or >= n, the master key is invalid (handled by ECKey.fromBuffer)
|
|||
var pIL = BigInteger.fromBuffer(IL) |
|||
|
|||
this.network = network |
|||
this.priv = new ECKey(pIL, true) |
|||
this.pub = this.priv.pub |
|||
|
|||
this.chaincode = IR |
|||
this.depth = 0 |
|||
this.index = 0 |
|||
} |
|||
|
|||
HDWallet.MASTER_SECRET = new Buffer('Bitcoin seed') |
|||
HDWallet.HIGHEST_BIT = 0x80000000 |
|||
HDWallet.LENGTH = 78 |
|||
|
|||
HDWallet.fromSeedHex = function(hex, network) { |
|||
return new HDWallet(new Buffer(hex, 'hex'), network) |
|||
} |
|||
|
|||
HDWallet.fromBase58 = function(string) { |
|||
var buffer = base58.decode(string) |
|||
|
|||
var payload = buffer.slice(0, -4) |
|||
var checksum = buffer.slice(-4) |
|||
var newChecksum = crypto.hash256(payload).slice(0, 4) |
|||
|
|||
assert.deepEqual(newChecksum, checksum, 'Invalid checksum') |
|||
assert.equal(payload.length, HDWallet.LENGTH, 'Invalid BIP32 string') |
|||
|
|||
return HDWallet.fromBuffer(payload) |
|||
} |
|||
|
|||
HDWallet.fromBuffer = function(input) { |
|||
assert.strictEqual(input.length, HDWallet.LENGTH, 'Invalid buffer length') |
|||
|
|||
var hd = new HDWallet() |
|||
|
|||
// 4 byte: version bytes
|
|||
var version = input.readUInt32BE(0) |
|||
|
|||
var type |
|||
for (var name in networks) { |
|||
var network = networks[name] |
|||
|
|||
for (var t in network.bip32) { |
|||
if (version != network.bip32[t]) continue |
|||
|
|||
type = t |
|||
hd.network = network |
|||
} |
|||
} |
|||
|
|||
if (!hd.network) { |
|||
throw new Error('Could not find version ' + version.toString(16)) |
|||
} |
|||
|
|||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
|
|||
hd.depth = input.readUInt8(4) |
|||
|
|||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
|||
hd.parentFingerprint = input.readUInt32BE(5) |
|||
if (hd.depth === 0) { |
|||
assert.strictEqual(hd.parentFingerprint, 0x00000000, 'Invalid parent fingerprint') |
|||
} |
|||
|
|||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
|||
// This is encoded in MSB order. (0x00000000 if master key)
|
|||
hd.index = input.readUInt32BE(9) |
|||
assert(hd.depth > 0 || hd.index === 0, 'Invalid index') |
|||
|
|||
// 32 bytes: the chain code
|
|||
hd.chaincode = input.slice(13, 45) |
|||
|
|||
// 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for
|
|||
// public keys, 0x00 + k for private keys)
|
|||
if (type == 'priv') { |
|||
assert.equal(input.readUInt8(45), 0, 'Invalid private key') |
|||
var D = BigInteger.fromBuffer(input.slice(46, 78)) |
|||
|
|||
hd.priv = new ECKey(D, true) |
|||
hd.pub = hd.priv.pub |
|||
} else { |
|||
hd.pub = ECPubKey.fromBuffer(input.slice(45, 78), true) |
|||
} |
|||
|
|||
return hd |
|||
} |
|||
|
|||
HDWallet.prototype.getIdentifier = function() { |
|||
return crypto.hash160(this.pub.toBuffer()) |
|||
} |
|||
|
|||
HDWallet.prototype.getFingerprint = function() { |
|||
return this.getIdentifier().slice(0, 4) |
|||
} |
|||
|
|||
HDWallet.prototype.getAddress = function() { |
|||
return this.pub.getAddress(this.getKeyVersion()) |
|||
} |
|||
|
|||
HDWallet.prototype.toBuffer = function(priv) { |
|||
// Version
|
|||
var version = this.network.bip32[priv ? 'priv' : 'pub'] |
|||
var buffer = new Buffer(HDWallet.LENGTH) |
|||
|
|||
// 4 bytes: version bytes
|
|||
buffer.writeUInt32BE(version, 0) |
|||
|
|||
// Depth
|
|||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
|
|||
buffer.writeUInt8(this.depth, 4) |
|||
|
|||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
|||
var fingerprint = this.depth ? this.parentFingerprint : 0x00000000 |
|||
buffer.writeUInt32BE(fingerprint, 5) |
|||
|
|||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
|||
// This is encoded in Big endian. (0x00000000 if master key)
|
|||
buffer.writeUInt32BE(this.index, 9) |
|||
|
|||
// 32 bytes: the chain code
|
|||
this.chaincode.copy(buffer, 13) |
|||
|
|||
// 33 bytes: the public key or private key data
|
|||
if (priv) { |
|||
assert(this.priv, 'Missing private key') |
|||
|
|||
// 0x00 + k for private keys
|
|||
buffer.writeUInt8(0, 45) |
|||
this.priv.D.toBuffer(32).copy(buffer, 46) |
|||
} else { |
|||
|
|||
// X9.62 encoding for public keys
|
|||
this.pub.toBuffer().copy(buffer, 45) |
|||
} |
|||
|
|||
return buffer |
|||
} |
|||
HDWallet.prototype.toHex = function(priv) { |
|||
return this.toBuffer(priv).toString('hex') |
|||
} |
|||
|
|||
HDWallet.prototype.toBase58 = function(priv) { |
|||
var buffer = this.toBuffer(priv) |
|||
var checksum = crypto.hash256(buffer).slice(0, 4) |
|||
|
|||
return base58.encode(Buffer.concat([ |
|||
buffer, |
|||
checksum |
|||
])) |
|||
} |
|||
|
|||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
|
|||
HDWallet.prototype.derive = function(index) { |
|||
var isHardened = index >= HDWallet.HIGHEST_BIT |
|||
var indexBuffer = new Buffer(4) |
|||
indexBuffer.writeUInt32BE(index, 0) |
|||
|
|||
var data |
|||
|
|||
// Hardened child
|
|||
if (isHardened) { |
|||
assert(this.priv, 'Could not derive hardened child key') |
|||
|
|||
// data = 0x00 || ser256(kpar) || ser32(index)
|
|||
data = Buffer.concat([ |
|||
this.priv.D.toBuffer(33), |
|||
indexBuffer |
|||
]) |
|||
|
|||
// Normal child
|
|||
} else { |
|||
// data = serP(point(kpar)) || ser32(index)
|
|||
// = serP(Kpar) || ser32(index)
|
|||
data = Buffer.concat([ |
|||
this.pub.toBuffer(), |
|||
indexBuffer |
|||
]) |
|||
} |
|||
|
|||
var I = crypto.HmacSHA512(data, this.chaincode) |
|||
var IL = I.slice(0, 32) |
|||
var IR = I.slice(32) |
|||
|
|||
var hd = new HDWallet() |
|||
var pIL = BigInteger.fromBuffer(IL) |
|||
|
|||
// Private parent key -> private child key
|
|||
if (this.priv) { |
|||
// ki = parse256(IL) + kpar (mod n)
|
|||
var ki = pIL.add(this.priv.D).mod(ecparams.getN()) |
|||
|
|||
// In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i
|
|||
if (pIL.compareTo(ecparams.getN()) >= 0 || ki.signum() === 0) { |
|||
return this.derive(index + 1) |
|||
} |
|||
|
|||
hd.priv = new ECKey(ki, true) |
|||
hd.pub = hd.priv.pub |
|||
|
|||
// Public parent key -> public child key
|
|||
} else { |
|||
// Ki = point(parse256(IL)) + Kpar
|
|||
// = G*IL + Kpar
|
|||
var Ki = ecparams.getG().multiply(pIL).add(this.pub.Q) |
|||
|
|||
// In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i
|
|||
if (pIL.compareTo(ecparams.getN()) >= 0 || Ki.isInfinity()) { |
|||
return this.derive(index + 1) |
|||
} |
|||
|
|||
hd.pub = new ECPubKey(Ki, true) |
|||
} |
|||
|
|||
hd.chaincode = IR |
|||
hd.depth = this.depth + 1 |
|||
hd.network = this.network |
|||
hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) |
|||
hd.index = index |
|||
|
|||
return hd |
|||
} |
|||
|
|||
HDWallet.prototype.derivePrivate = function(index) { |
|||
// Only derives hardened private keys by default
|
|||
return this.derive(index + HDWallet.HIGHEST_BIT) |
|||
} |
|||
|
|||
HDWallet.prototype.getKeyVersion = function() { |
|||
return this.network.pubKeyHash |
|||
} |
|||
|
|||
HDWallet.prototype.toString = HDWallet.prototype.toBase58 |
|||
|
|||
module.exports = HDWallet |
@ -0,0 +1,287 @@ |
|||
var assert = require('assert') |
|||
var networks = require('../src/networks') |
|||
var sec = require('../src/sec') |
|||
var ecparams = sec("secp256k1") |
|||
|
|||
var BigInteger = require('bigi') |
|||
var HDNode = require('../src/hdnode') |
|||
|
|||
var fixtures = require('./fixtures/hdnode.json') |
|||
|
|||
describe('HDNode', function() { |
|||
describe('Constructor', function() { |
|||
var D = BigInteger.ONE |
|||
var Q = ecparams.getG().multiply(D) |
|||
var chainCode = new Buffer(32) |
|||
chainCode.fill(1) |
|||
|
|||
it('calculates the publicKey from a BigInteger', function() { |
|||
var hd = new HDNode(D, chainCode) |
|||
|
|||
assert(hd.pubKey.Q.equals(Q)) |
|||
}) |
|||
|
|||
it('only uses compressed points', function() { |
|||
var hd = new HDNode(Q, chainCode) |
|||
var hdP = new HDNode(D, chainCode) |
|||
|
|||
assert.strictEqual(hd.pubKey.compressed, true) |
|||
assert.strictEqual(hdP.pubKey.compressed, true) |
|||
}) |
|||
|
|||
it('has a default depth/index of 0', function() { |
|||
var hd = new HDNode(Q, chainCode) |
|||
|
|||
assert.strictEqual(hd.depth, 0) |
|||
assert.strictEqual(hd.index, 0) |
|||
}) |
|||
|
|||
it('defaults to the bitcoin network', function() { |
|||
var hd = new HDNode(Q, chainCode) |
|||
|
|||
assert.equal(hd.network, networks.bitcoin) |
|||
}) |
|||
|
|||
it('supports alternative networks', function() { |
|||
var hd = new HDNode(Q, chainCode, networks.testnet) |
|||
|
|||
assert.equal(hd.network, networks.testnet) |
|||
}) |
|||
|
|||
it('throws an exception when an unknown network is given', function() { |
|||
assert.throws(function() { |
|||
new HDNode(D, chainCode, {}) |
|||
}, /Unknown BIP32 constants for network/) |
|||
}) |
|||
}) |
|||
|
|||
describe('fromSeed*', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
it('calculates privKey and chainCode for ' + f.master.fingerprint, function() { |
|||
var hd = HDNode.fromSeedHex(f.master.seed) |
|||
|
|||
assert.equal(hd.privKey.toWIF(), f.master.wif) |
|||
assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('toBase58', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
it('exports ' + f.master.base58 + ' (public) correctly', function() { |
|||
var hd = HDNode.fromSeedHex(f.master.seed) |
|||
|
|||
assert.equal(hd.toBase58(false), f.master.base58) |
|||
}) |
|||
}) |
|||
|
|||
fixtures.valid.forEach(function(f) { |
|||
it('exports ' + f.master.base58Priv + ' (private) correctly', function() { |
|||
var hd = HDNode.fromSeedHex(f.master.seed) |
|||
|
|||
assert.equal(hd.toBase58(true), f.master.base58Priv) |
|||
}) |
|||
}) |
|||
|
|||
it('fails when there is no private key', function() { |
|||
var hd = HDNode.fromBase58(fixtures.valid[0].master.base58) |
|||
|
|||
assert.throws(function() { |
|||
hd.toBase58(true) |
|||
}, /Missing private key/) |
|||
}) |
|||
}) |
|||
|
|||
describe('fromBase58', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
it('imports ' + f.master.base58 + ' (public) correctly', function() { |
|||
var hd = HDNode.fromBase58(f.master.base58) |
|||
|
|||
assert.equal(hd.toBase58(), f.master.base58) |
|||
}) |
|||
}) |
|||
|
|||
fixtures.valid.forEach(function(f) { |
|||
it('imports ' + f.master.base58Priv + ' (private) correctly', function() { |
|||
var hd = HDNode.fromBase58(f.master.base58Priv) |
|||
|
|||
assert.equal(hd.toBase58(), f.master.base58Priv) |
|||
}) |
|||
}) |
|||
|
|||
fixtures.invalid.fromBase58.forEach(function(f) { |
|||
it('throws on ' + f.string, function() { |
|||
assert.throws(function() { |
|||
HDNode.fromBase58(f.string) |
|||
}, new RegExp(f.exception)) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('fromBuffer/fromHex', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
it('imports ' + f.master.hex + ' (public) correctly', function() { |
|||
var hd = HDNode.fromHex(f.master.hex) |
|||
|
|||
assert.equal(hd.toBuffer().toString('hex'), f.master.hex) |
|||
}) |
|||
}) |
|||
|
|||
fixtures.valid.forEach(function(f) { |
|||
it('imports ' + f.master.hexPriv + ' (private) correctly', function() { |
|||
var hd = HDNode.fromHex(f.master.hexPriv) |
|||
|
|||
assert.equal(hd.toBuffer().toString('hex'), f.master.hexPriv) |
|||
}) |
|||
}) |
|||
|
|||
fixtures.invalid.fromBuffer.forEach(function(f) { |
|||
it('throws on ' + f.string, function() { |
|||
assert.throws(function() { |
|||
HDNode.fromHex(f.hex) |
|||
}, new RegExp(f.exception)) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('toBuffer/toHex', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
it('exports ' + f.master.hex + ' (public) correctly', function() { |
|||
var hd = HDNode.fromSeedHex(f.master.seed) |
|||
|
|||
assert.equal(hd.toHex(false), f.master.hex) |
|||
}) |
|||
}) |
|||
|
|||
fixtures.valid.forEach(function(f) { |
|||
it('exports ' + f.master.hexPriv + ' (private) correctly', function() { |
|||
var hd = HDNode.fromSeedHex(f.master.seed) |
|||
|
|||
assert.equal(hd.toHex(true), f.master.hexPriv) |
|||
}) |
|||
}) |
|||
|
|||
it('fails when there is no private key', function() { |
|||
var hd = HDNode.fromHex(fixtures.valid[0].master.hex) |
|||
|
|||
assert.throws(function() { |
|||
hd.toHex(true) |
|||
}, /Missing private key/) |
|||
}) |
|||
}) |
|||
|
|||
describe('getIdentifier', function() { |
|||
var f = fixtures.valid[0] |
|||
|
|||
it('returns the identifier for ' + f.master.fingerprint, function() { |
|||
var hd = HDNode.fromBase58(f.master.base58) |
|||
|
|||
assert.equal(hd.getIdentifier().toString('hex'), f.master.identifier) |
|||
}) |
|||
}) |
|||
|
|||
describe('getFingerprint', function() { |
|||
var f = fixtures.valid[0] |
|||
|
|||
it('returns the fingerprint for ' + f.master.fingerprint, function() { |
|||
var hd = HDNode.fromBase58(f.master.base58) |
|||
|
|||
assert.equal(hd.getFingerprint().toString('hex'), f.master.fingerprint) |
|||
}) |
|||
}) |
|||
|
|||
describe('getAddress', function() { |
|||
var f = fixtures.valid[0] |
|||
|
|||
it('returns the Address (pubHash) for ' + f.master.fingerprint, function() { |
|||
var hd = HDNode.fromBase58(f.master.base58) |
|||
|
|||
assert.equal(hd.getAddress().toString(), f.master.address) |
|||
}) |
|||
|
|||
it('supports alternative networks', function() { |
|||
var hd = HDNode.fromBase58(f.master.base58) |
|||
hd.network = networks.testnet |
|||
|
|||
assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) |
|||
}) |
|||
}) |
|||
|
|||
describe('derive', function() { |
|||
function verifyVector(hd, v, depth) { |
|||
assert.equal(hd.privKey.toWIF(), v.wif) |
|||
assert.equal(hd.pubKey.toHex(), v.pubKey) |
|||
assert.equal(hd.chainCode.toString('hex'), v.chainCode) |
|||
assert.equal(hd.depth, depth || 0) |
|||
|
|||
if (v.hardened) { |
|||
assert.equal(hd.index, v.m + HDNode.HIGHEST_BIT) |
|||
} else { |
|||
assert.equal(hd.index, v.m) |
|||
} |
|||
} |
|||
|
|||
fixtures.valid.forEach(function(f, j) { |
|||
var hd = HDNode.fromSeedHex(f.master.seed) |
|||
|
|||
// FIXME: test data is only testing Private -> private for now
|
|||
f.children.forEach(function(c, i) { |
|||
it(c.description + ' from ' + f.master.fingerprint, function() { |
|||
if (c.hardened) { |
|||
hd = hd.deriveHardened(c.m) |
|||
|
|||
} else { |
|||
hd = hd.derive(c.m) |
|||
} |
|||
|
|||
verifyVector(hd, c, i + 1) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
it('works for Private -> public (neutered)', function() { |
|||
var f = fixtures.valid[1] |
|||
var c = f.children[0] |
|||
|
|||
var parentNode = HDNode.fromBase58(f.master.base58Priv) |
|||
var child = parentNode.derive(c.m) |
|||
|
|||
// FIXME: N(CKDpriv((kpar, cpar), i)), could be done better...
|
|||
var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter
|
|||
assert.equal(childNeutered.toBase58(), c.base58) |
|||
}) |
|||
|
|||
it('works for Private -> public (neutered, hardened)', function() { |
|||
var f = fixtures.valid[0] |
|||
var c = f.children[0] |
|||
|
|||
var parentNode = HDNode.fromBase58(f.master.base58Priv) |
|||
var child = parentNode.deriveHardened(c.m) |
|||
|
|||
// FIXME: N(CKDpriv((kpar, cpar), i)), could be done better...
|
|||
var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter
|
|||
assert.equal(childNeutered.toBase58(), c.base58) |
|||
}) |
|||
|
|||
it('works for Public -> public', function() { |
|||
var f = fixtures.valid[1] |
|||
var c = f.children[0] |
|||
|
|||
var parentNode = HDNode.fromBase58(f.master.base58) |
|||
var child = parentNode.derive(c.m) |
|||
|
|||
assert.equal(child.toBase58(), c.base58) |
|||
}) |
|||
|
|||
it('throws on Public -> public (hardened)', function() { |
|||
var f = fixtures.valid[0] |
|||
var c = f.children[0] |
|||
|
|||
var parentNode = HDNode.fromBase58(f.master.base58) |
|||
|
|||
assert.throws(function() { |
|||
parentNode.deriveHardened(c.m) |
|||
}, /Could not derive hardened child key/) |
|||
}) |
|||
}) |
|||
}) |
@ -1,174 +0,0 @@ |
|||
var assert = require('assert') |
|||
var networks = require('../src/networks') |
|||
|
|||
var HDWallet = require('../src/hdwallet') |
|||
var fixtures = require('./fixtures/hdwallet.json') |
|||
|
|||
function b2h(buf) { |
|||
assert(Buffer.isBuffer(buf)) |
|||
return buf.toString('hex') |
|||
} |
|||
|
|||
describe('HDWallet', function() { |
|||
describe('toBase58', function() { |
|||
it('reproduces input', function() { |
|||
var input = 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5' |
|||
var output = HDWallet.fromBase58(input).toBase58(false) |
|||
assert.equal(output, input) |
|||
|
|||
input = 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334' |
|||
output = HDWallet.fromBase58(input).toBase58(true) |
|||
assert.equal(output, input) |
|||
}) |
|||
|
|||
it('fails with priv=true when theres no private key', function() { |
|||
var hd = HDWallet.fromBase58('xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon') |
|||
try { |
|||
hd.toBase58(true) |
|||
} catch(e) { |
|||
assert(e.message.match(/private key/i)) |
|||
return |
|||
} |
|||
assert.fail() |
|||
}) |
|||
}) |
|||
|
|||
describe('fromBase58', function() { |
|||
fixtures.invalid.fromBase58.forEach(function(f) { |
|||
it('throws on ' + f.string, function() { |
|||
assert.throws(function() { |
|||
HDWallet.fromBase58(f.string) |
|||
}, new RegExp(f.exception)) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('constructor & seed deserialization', function() { |
|||
var expectedPrivateKey = '0fd71c652e847ba7ea7956e3cf3fc0a0985871846b1b2c23b9c6a29a38cee860' |
|||
var seed = new Buffer([ |
|||
99, 114, 97, 122, 121, 32, 104, 111, 114, 115, 101, 32, 98, |
|||
97, 116, 116, 101, 114, 121, 32, 115, 116, 97, 112, 108, 101 |
|||
]) |
|||
|
|||
it('creates from binary seed', function() { |
|||
var hd = new HDWallet(seed) |
|||
|
|||
assert.equal(hd.priv.D.toHex(), expectedPrivateKey) |
|||
assert(hd.pub) |
|||
}) |
|||
|
|||
describe('fromSeedHex', function() { |
|||
it('creates from hex seed', function() { |
|||
var hd = HDWallet.fromSeedHex(seed.toString('hex')) |
|||
|
|||
assert.equal(hd.priv.D.toHex(), expectedPrivateKey) |
|||
assert(hd.pub) |
|||
}) |
|||
}) |
|||
|
|||
describe('fromBuffer', function() { |
|||
it('fails for invalid parent fingerprint', function() { |
|||
var buffer = new HDWallet(seed).toBuffer() |
|||
buffer.writeUInt8(0x00, 4) |
|||
buffer.writeUInt32BE(0xFFFFFFFF, 5) |
|||
assert.throws(function() { HDWallet.fromBuffer(buffer) }, /Invalid parent fingerprint/) |
|||
}) |
|||
|
|||
it('fails for invalid index', function() { |
|||
var buffer = new HDWallet(seed).toBuffer() |
|||
buffer.writeUInt32BE(0xFFFFFFFF, 9) |
|||
assert.throws(function() { HDWallet.fromBuffer(buffer) }, /Invalid index/) |
|||
}) |
|||
|
|||
it('fails for an invalid network type', function() { |
|||
var network = { bip32: { priv: 0x11111111, pub: 0x22222222 } } |
|||
var buffer = new HDWallet(seed, network).toBuffer() |
|||
|
|||
assert.throws(function() { |
|||
HDWallet.fromBuffer(buffer) |
|||
}, /Could not find version 22222222/) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('Test vectors', function() { |
|||
function verifyVector(hd, v) { |
|||
assert.equal(b2h(hd.getIdentifier()), v.identifier) |
|||
assert.equal(b2h(hd.getFingerprint()), v.fingerprint) |
|||
assert.equal(hd.getAddress().toString(), v.address) |
|||
assert.equal(hd.priv.toWIF(), v.wif) |
|||
assert.equal(hd.pub.toHex(), v.pubKey) |
|||
assert.equal(b2h(hd.chaincode), v.chaincode) |
|||
assert.equal(hd.toHex(false), v.hex) |
|||
assert.equal(hd.toHex(true), v.hexPriv) |
|||
assert.equal(hd.toBase58(false), v.base58) |
|||
assert.equal(hd.toBase58(true), v.base58Priv) |
|||
} |
|||
|
|||
it('matches the test vectors', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
var hd = HDWallet.fromSeedHex(f.master.seed) |
|||
verifyVector(hd, f.master) |
|||
|
|||
f.children.forEach(function(c) { |
|||
// FIXME: c.description could be shown
|
|||
if (c.mPriv != undefined) { |
|||
hd = hd.derivePrivate(c.mPriv) |
|||
} else { |
|||
hd = hd.derive(c.m) |
|||
} |
|||
|
|||
verifyVector(hd, c) |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('derive', function() { |
|||
describe('m/0', function() { |
|||
var wallet = HDWallet.fromBase58('xpub6CxuB8ifZCMXeS3KbyNkYvrsJEHqxedCSiUhrNwH1nKtb8hcJpxDbDxkdoVCTR2bQ1G8hY4UMv85gef9SEpgFFUftBjt37FUSZxVx4AU9Qh').derive(0) |
|||
|
|||
it('derives the correct public key', function() { |
|||
assert.equal(wallet.pub.toHex(), '02df843e6ae2017e0772d0584f76f56b8f2f5181a3045c7a7740a9d86dc7c80ce7') |
|||
}) |
|||
|
|||
it('derives the correct depth', function() { |
|||
assert.equal(wallet.depth, 4) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('network types', function() { |
|||
var seed |
|||
|
|||
beforeEach(function() { |
|||
seed = new Buffer('foobar') |
|||
}) |
|||
|
|||
it('ensure that a bitcoin wallet is the default', function() { |
|||
var wallet = new HDWallet(seed) |
|||
|
|||
assert.equal(wallet.network, networks.bitcoin) |
|||
}) |
|||
|
|||
it('ensures that a bitcoin Wallet generates bitcoin addresses', function() { |
|||
var wallet = new HDWallet(seed) |
|||
var address = wallet.getAddress().toString() |
|||
|
|||
assert.equal(address, '17SnB9hyGwJPoKpLb9eVPHjsujyEuBpMAA') |
|||
}) |
|||
|
|||
it('ensures that a testnet Wallet generates testnet addresses', function() { |
|||
var wallet = new HDWallet(seed, networks.testnet) |
|||
var address = wallet.getAddress().toString() |
|||
|
|||
assert.equal(address, 'mmxjUCnx5xjeaSHxJicsDCxCmjZwq8KTbv') |
|||
}) |
|||
|
|||
it('throws an exception when unknown network type is passed in', function() { |
|||
assert.throws(function() { |
|||
new HDWallet(seed, {}) |
|||
}, /Unknown BIP32 constants for network/) |
|||
}) |
|||
}) |
|||
}) |
Loading…
Reference in new issue