Merge pull request #28 from bitcoinjs/hd-testnet-support
HD testnet support (#27) + unify network names
This commit is contained in:
commit
b9de999434
9 changed files with 129 additions and 75 deletions
|
@ -2,6 +2,7 @@ var base58 = require('./base58');
|
|||
var Crypto = require('./crypto-js/crypto');
|
||||
var conv = require('./convert');
|
||||
var util = require('./util');
|
||||
var mainnet = require('./network').mainnet.addressVersion;
|
||||
|
||||
var Address = function (bytes, version) {
|
||||
if (!(this instanceof Address)) { return new Address(bytes, version); }
|
||||
|
@ -15,11 +16,11 @@ var Address = function (bytes, version) {
|
|||
: bytes.length <= 40 ? conv.hexToBytes(bytes)
|
||||
: util.error('Bad input');
|
||||
|
||||
this.version = version || this.hash.version || Address.address_types.prod;
|
||||
this.version = version || this.hash.version || mainnet;
|
||||
}
|
||||
else {
|
||||
this.hash = bytes;
|
||||
this.version = version || bytes.version || Address.address_types.prod;
|
||||
this.version = version || bytes.version || mainnet;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -29,7 +30,7 @@ var Address = function (bytes, version) {
|
|||
* Returns the address as a base58-encoded string in the standardized format.
|
||||
*/
|
||||
Address.prototype.toString = function () {
|
||||
return base58.checkEncode(this.hash.slice(0),this.version);
|
||||
return base58.checkEncode(this.hash.slice(0), this.version);
|
||||
};
|
||||
|
||||
Address.prototype.getHash = function () {
|
||||
|
@ -56,14 +57,4 @@ Address.decodeString = function (string) {
|
|||
return base58.checkDecode(string);
|
||||
};
|
||||
|
||||
Address.address_types = {
|
||||
prod: 0,
|
||||
testnet: 111
|
||||
};
|
||||
|
||||
Address.p2sh_types = {
|
||||
prod: 5,
|
||||
testnet: 196
|
||||
};
|
||||
|
||||
module.exports = Address;
|
||||
|
|
27
src/eckey.js
27
src/eckey.js
|
@ -7,6 +7,9 @@ var conv = require('./convert');
|
|||
var Address = require('./address');
|
||||
var ecdsa = require('./ecdsa');
|
||||
var ECPointFp = require('./jsbn/ec').ECPointFp;
|
||||
var Network = require('./network')
|
||||
var mainnet = Network.mainnet.addressVersion
|
||||
var testnet = Network.testnet.addressVersion
|
||||
|
||||
var ecparams = sec("secp256k1");
|
||||
|
||||
|
@ -18,7 +21,7 @@ var ECKey = function (input,compressed,version) {
|
|||
var n = ecparams.getN();
|
||||
this.priv = ecdsa.getBigRandom(n);
|
||||
this.compressed = compressed || false;
|
||||
this.version = version || Address.address_types.prod;
|
||||
this.version = version || mainnet;
|
||||
}
|
||||
else this.import(input,compressed,version)
|
||||
};
|
||||
|
@ -57,16 +60,16 @@ ECKey.prototype.import = function (input,compressed,version) {
|
|||
this.version =
|
||||
version !== undefined ? version
|
||||
: input instanceof ECKey ? input.version
|
||||
: input instanceof BigInteger ? Address.address_types.prod
|
||||
: util.isArray(input) ? Address.address_types.prod
|
||||
: input instanceof BigInteger ? mainnet
|
||||
: util.isArray(input) ? mainnet
|
||||
: typeof input != "string" ? null
|
||||
: input.length == 44 ? Address.address_types.prod
|
||||
: input.length == 51 && input[0] == '5' ? Address.address_types.prod
|
||||
: input.length == 51 && input[0] == '9' ? Address.address_types.testnet
|
||||
: input.length == 52 && has('LK',input[0]) ? Address.address_types.prod
|
||||
: input.length == 52 && input[0] == 'c' ? Address.address_types.testnet
|
||||
: input.length == 64 ? Address.address_types.prod
|
||||
: input.length == 65 ? Address.address_types.prod
|
||||
: input.length == 44 ? mainnet
|
||||
: input.length == 51 && input[0] == '5' ? mainnet
|
||||
: input.length == 51 && input[0] == '9' ? testnet
|
||||
: input.length == 52 && has('LK',input[0]) ? mainnet
|
||||
: input.length == 52 && input[0] == 'c' ? testnet
|
||||
: input.length == 64 ? mainnet
|
||||
: input.length == 65 ? mainnet
|
||||
: null
|
||||
};
|
||||
|
||||
|
@ -133,7 +136,7 @@ var ECPubKey = function(input,compressed,version) {
|
|||
var n = ecparams.getN();
|
||||
this.pub = ecparams.getG().multiply(ecdsa.getBigRandom(n))
|
||||
this.compressed = compressed || false;
|
||||
this.version = version || Address.address_types.prod;
|
||||
this.version = version || mainnet;
|
||||
}
|
||||
else this.import(input,compressed,version)
|
||||
}
|
||||
|
@ -158,7 +161,7 @@ ECPubKey.prototype.import = function(input,compressed,version) {
|
|||
version ? version
|
||||
: input instanceof ECPointFp ? input.version
|
||||
: input instanceof ECPubKey ? input.version
|
||||
: Address.address_types.prod
|
||||
: mainnet
|
||||
}
|
||||
|
||||
ECPubKey.prototype.add = function(key) {
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
var convert = require('./convert.js')
|
||||
, base58 = require('./base58.js')
|
||||
, assert = require('assert')
|
||||
, format = require('util').format
|
||||
, util = require('./util.js')
|
||||
, Crypto = require('./crypto-js/crypto.js')
|
||||
, ECKey = require('./eckey.js').ECKey
|
||||
, ECPubKey = require('./eckey.js').ECPubKey
|
||||
, Address = require('./address.js')
|
||||
var base58 = require('./base58.js')
|
||||
var assert = require('assert')
|
||||
var format = require('util').format
|
||||
var util = require('./util.js')
|
||||
var Crypto = require('./crypto-js/crypto.js')
|
||||
var ECKey = require('./eckey.js').ECKey
|
||||
var ECPubKey = require('./eckey.js').ECPubKey
|
||||
var Address = require('./address.js')
|
||||
var Network = require('./network')
|
||||
|
||||
var HDWallet = module.exports = function(seed, network) {
|
||||
if (seed === undefined) return
|
||||
|
||||
var I = Crypto.HMAC(Crypto.SHA512, seed, 'Bitcoin seed', { asBytes: true })
|
||||
this.chaincode = I.slice(32)
|
||||
this.priv = new ECKey(I.slice(0, 32).concat([1]), true)
|
||||
this.network = network || 'mainnet'
|
||||
if(!Network.hasOwnProperty(this.network)) {
|
||||
throw new Error("Unknown network: " + this.network)
|
||||
}
|
||||
|
||||
this.priv = new ECKey(I.slice(0, 32).concat([1]), true, this.getKeyVersion())
|
||||
this.pub = this.priv.getPub()
|
||||
this.network = network || 'Bitcoin'
|
||||
this.index = 0
|
||||
this.depth = 0
|
||||
}
|
||||
|
@ -23,11 +28,6 @@ var HDWallet = module.exports = function(seed, network) {
|
|||
HDWallet.HIGHEST_BIT = 0x80000000
|
||||
HDWallet.LENGTH = 78
|
||||
|
||||
HDWallet.VERSIONS = {
|
||||
Bitcoin: [0x0488B21E, 0x0488ADE4],
|
||||
BitcoinTest: [0x043587CF, 0x04358394]
|
||||
}
|
||||
|
||||
function arrayEqual(a, b) {
|
||||
return !(a < b || a > b)
|
||||
}
|
||||
|
@ -73,16 +73,17 @@ HDWallet.fromBytes = function(input) {
|
|||
// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private;
|
||||
// testnet: 0x043587CF public, 0x04358394 private)
|
||||
var versionBytes = input.slice(0, 4)
|
||||
, versionWord = util.bytesToWords(versionBytes)[0]
|
||||
, type
|
||||
var versionWord = util.bytesToWords(versionBytes)[0]
|
||||
var type
|
||||
|
||||
Object.keys(HDWallet.VERSIONS).forEach(function(name) {
|
||||
HDWallet.VERSIONS[name].forEach(function(word, i) {
|
||||
if (versionWord != word) return
|
||||
type = i ? 'private' : 'public'
|
||||
for(var name in Network) {
|
||||
var network = Network[name]
|
||||
for(var t in network.hdVersions) {
|
||||
if (versionWord != network.hdVersions[t]) continue
|
||||
type = t
|
||||
hd.network = name
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!hd.network) {
|
||||
throw new Error(format('Could not find version %s', convert.bytesToHex(versionBytes)))
|
||||
|
@ -105,11 +106,11 @@ HDWallet.fromBytes = function(input) {
|
|||
|
||||
// 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for
|
||||
// public keys, 0x00 + k for private keys)
|
||||
if (type == 'private') {
|
||||
hd.priv = new ECKey(input.slice(46, 78).concat([1]), true)
|
||||
if (type == 'priv') {
|
||||
hd.priv = new ECKey(input.slice(46, 78).concat([1]), true, hd.getKeyVersion())
|
||||
hd.pub = hd.priv.getPub()
|
||||
} else {
|
||||
hd.pub = new ECPubKey(input.slice(45, 78), true)
|
||||
hd.pub = new ECPubKey(input.slice(45, 78), true, hd.getKeyVersion())
|
||||
}
|
||||
|
||||
return hd
|
||||
|
@ -124,8 +125,7 @@ HDWallet.prototype.getFingerprint = function() {
|
|||
}
|
||||
|
||||
HDWallet.prototype.getBitcoinAddress = function() {
|
||||
var test = this.network.match(/Test$/)
|
||||
return new Address(util.sha256ripe160(this.pub.toBytes()), test ? 111 : 0)
|
||||
return new Address(util.sha256ripe160(this.pub.toBytes()), this.getKeyVersion())
|
||||
}
|
||||
|
||||
HDWallet.prototype.toBytes = function(priv) {
|
||||
|
@ -134,7 +134,8 @@ HDWallet.prototype.toBytes = function(priv) {
|
|||
// Version
|
||||
// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public,
|
||||
// 0x04358394 private)
|
||||
var vBytes = util.wordsToBytes([HDWallet.VERSIONS[this.network][priv ? 1 : 0]])
|
||||
var version = Network[this.network].hdVersions[priv ? 'priv' : 'pub']
|
||||
var vBytes = util.wordsToBytes([version])
|
||||
|
||||
buffer = buffer.concat(vBytes)
|
||||
assert.equal(buffer.length, 4)
|
||||
|
@ -213,10 +214,11 @@ HDWallet.prototype.derive = function(i) {
|
|||
// ki = IL + kpar (mod n).
|
||||
hd.priv = this.priv.add(new ECKey(IL.concat([1])))
|
||||
hd.priv.compressed = true
|
||||
hd.priv.version = this.getKeyVersion()
|
||||
hd.pub = hd.priv.getPub()
|
||||
} else {
|
||||
// Ki = (IL + kpar)*G = IL*G + Kpar
|
||||
hd.pub = this.pub.add(new ECKey(IL.concat([1])).getPub())
|
||||
hd.pub = this.pub.add(new ECKey(IL.concat([1]), true, this.getKeyVersion()).getPub())
|
||||
}
|
||||
|
||||
// ci = IR.
|
||||
|
@ -232,4 +234,8 @@ HDWallet.prototype.derivePrivate = function(index) {
|
|||
return this.derive(index + HDWallet.HIGHEST_BIT)
|
||||
}
|
||||
|
||||
HDWallet.prototype.getKeyVersion = function() {
|
||||
return Network[this.network].addressVersion
|
||||
}
|
||||
|
||||
HDWallet.prototype.toString = HDWallet.prototype.toBase58
|
||||
|
|
19
src/network.js
Normal file
19
src/network.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
mainnet: {
|
||||
addressVersion: 0,
|
||||
p2shVersion: 5,
|
||||
hdVersions: {
|
||||
pub: 0x0488B21E,
|
||||
priv: 0x0488ADE4
|
||||
}
|
||||
},
|
||||
testnet: {
|
||||
addressVersion: 111,
|
||||
p2shVersion: 196,
|
||||
hdVersions: {
|
||||
pub: 0x043587CF,
|
||||
priv: 0x04358394
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -18,7 +18,7 @@ var Wallet = function (seed, options) {
|
|||
if (!(this instanceof Wallet)) { return new Wallet(seed, options); }
|
||||
|
||||
var options = options || {}
|
||||
var network = options.network || 'Bitcoin'
|
||||
var network = options.network || 'mainnet'
|
||||
|
||||
// HD first-level child derivation method (i.e. public or private child key derivation)
|
||||
// NB: if not specified, defaults to private child derivation
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('Address', function() {
|
|||
}
|
||||
|
||||
validate('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa');
|
||||
// validate('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 'prod');
|
||||
// validate('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 'mainnet');
|
||||
validate('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef');
|
||||
// validate('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'testnet');
|
||||
|
||||
|
@ -29,7 +29,7 @@ describe('Address', function() {
|
|||
|
||||
// p2sh addresses
|
||||
validate('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt');
|
||||
// validate('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'prod');
|
||||
// validate('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'mainnet');
|
||||
validate('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7');
|
||||
// validate('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7', 'testnet');
|
||||
})
|
||||
|
@ -43,7 +43,7 @@ describe('Address', function() {
|
|||
invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhe');
|
||||
|
||||
// invalid('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 'testnet');
|
||||
// invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'prod');
|
||||
// invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'mainnet');
|
||||
|
||||
// invalid base58 string
|
||||
invalid('%%@');
|
||||
|
|
|
@ -5,6 +5,9 @@ var convert = require('../src/convert.js');
|
|||
var bytesToHex = convert.bytesToHex;
|
||||
var hexToBytes = convert.hexToBytes;
|
||||
var Address = require('../src/address');
|
||||
var Network = require('../src/network')
|
||||
var mainnet = Network.mainnet.addressVersion
|
||||
var testnet = Network.testnet.addressVersion
|
||||
|
||||
describe('ECKey', function() {
|
||||
describe('constructor (base58 private) on mainnet', function() {
|
||||
|
@ -16,7 +19,7 @@ describe('ECKey', function() {
|
|||
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.compressed, false);
|
||||
assert.equal(key.version, Address.address_types.prod);
|
||||
assert.equal(key.version, mainnet);
|
||||
})
|
||||
|
||||
it('parses base64', function() {
|
||||
|
@ -27,7 +30,7 @@ describe('ECKey', function() {
|
|||
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.compressed, false);
|
||||
assert.equal(key.version, Address.address_types.prod);
|
||||
assert.equal(key.version, mainnet);
|
||||
})
|
||||
|
||||
it('parses WIF', function() {
|
||||
|
@ -40,7 +43,7 @@ describe('ECKey', function() {
|
|||
assert.equal(key.compressed, false);
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.getBitcoinAddress().toString(), addr);
|
||||
assert.equal(key.version, Address.address_types.prod);
|
||||
assert.equal(key.version, mainnet);
|
||||
})
|
||||
|
||||
it('parses compressed WIF', function() {
|
||||
|
@ -52,7 +55,7 @@ describe('ECKey', function() {
|
|||
assert.equal(key.compressed, true);
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.getBitcoinAddress().toString(), addr);
|
||||
assert.equal(key.version, Address.address_types.prod);
|
||||
assert.equal(key.version, mainnet);
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -61,11 +64,11 @@ describe('ECKey', function() {
|
|||
var priv = 'ca48ec9783cf3ad0dfeff1fc254395a2e403cbbc666477b61b45e31d3b8ab458';
|
||||
var pub = '044b12d9d7c77db68388b6ff7c89046174c871546436806bcd80d07c28ea81199' +
|
||||
'283fbec990dad6fb98f93f712d50cb874dd717de6a184158d63886dda3090f566';
|
||||
var key = new ECKey(priv, false, Address.address_types.testnet);
|
||||
var key = new ECKey(priv, false, testnet);
|
||||
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.compressed, false);
|
||||
assert.equal(key.version, Address.address_types.testnet);
|
||||
assert.equal(key.version, testnet);
|
||||
assert.equal(key.toHex(), priv);
|
||||
})
|
||||
|
||||
|
@ -73,11 +76,11 @@ describe('ECKey', function() {
|
|||
var priv = 'VYdB+iv47y5FaUVIPdQInkgATrABeuD1lACUoM4x7tU=';
|
||||
var pub = '042f43c16c08849fed20a35bb7b1947bbf0923c52d613ee13b5c665a1e10d24b2' +
|
||||
'8be909a70f5f87c1adb79fbcd1b3f17d20aa91c04fc355112dba2ce9b1cbf013b';
|
||||
var key = new ECKey(priv, false, Address.address_types.testnet);
|
||||
var key = new ECKey(priv, false, testnet);
|
||||
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.compressed, false);
|
||||
assert.equal(key.version, Address.address_types.testnet);
|
||||
assert.equal(key.version, testnet);
|
||||
assert.equal(key.toBase64(), priv);
|
||||
})
|
||||
|
||||
|
@ -91,7 +94,7 @@ describe('ECKey', function() {
|
|||
assert.equal(key.compressed, false);
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.getBitcoinAddress().toString(), addr);
|
||||
assert.equal(key.version, Address.address_types.testnet);
|
||||
assert.equal(key.version, testnet);
|
||||
assert.equal(key.toBase58(), priv);
|
||||
})
|
||||
|
||||
|
@ -104,7 +107,7 @@ describe('ECKey', function() {
|
|||
assert.equal(key.compressed, true);
|
||||
assert.equal(key.getPub().toHex(), pub);
|
||||
assert.equal(key.getBitcoinAddress().toString(), addr);
|
||||
assert.equal(key.version, Address.address_types.testnet);
|
||||
assert.equal(key.version, testnet);
|
||||
assert.equal(key.toBase58(), priv);
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/* global describe, it */
|
||||
var HDWallet = require('../src/hdwallet.js')
|
||||
, assert = require('assert')
|
||||
, convert = require('../src/convert.js')
|
||||
var assert = require('assert')
|
||||
var convert = require('../src/convert.js')
|
||||
var Network = require('../src/network')
|
||||
var mainnet = Network.mainnet.addressVersion
|
||||
var testnet = Network.testnet.addressVersion
|
||||
|
||||
var b2h = convert.bytesToHex
|
||||
|
||||
|
@ -214,4 +217,32 @@ describe('HDWallet', function() {
|
|||
assert.equal(hd.toBase58(true), 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j')
|
||||
})
|
||||
})
|
||||
|
||||
describe('network types', function() {
|
||||
it('ensures that a mainnet Wallet has mainnet child keys (pub and priv)', function() {
|
||||
var wallet = new HDWallet("foobar", "mainnet")
|
||||
assert.equal(wallet.priv.version, mainnet)
|
||||
|
||||
var privChild = wallet.derivePrivate(0)
|
||||
assert.equal(privChild.priv.version, mainnet)
|
||||
|
||||
var pubChild = wallet.derive(0)
|
||||
assert.equal(pubChild.priv.version, mainnet)
|
||||
})
|
||||
|
||||
it('ensures that a testnet Wallet has testnet child keys (pub and priv)', function() {
|
||||
var wallet = new HDWallet("foobar", "testnet")
|
||||
assert.equal(wallet.priv.version, testnet)
|
||||
|
||||
var privChild = wallet.derivePrivate(0)
|
||||
assert.equal(privChild.priv.version, testnet)
|
||||
|
||||
var pubChild = wallet.derive(0)
|
||||
assert.equal(pubChild.priv.version, testnet)
|
||||
})
|
||||
|
||||
it('throws an excption when unknown network type is passed in', function() {
|
||||
assert.throws(function() { new HDWallet("foobar", "doge") })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,8 +10,8 @@ describe('Wallet', function() {
|
|||
wallet = new Wallet(seed)
|
||||
})
|
||||
|
||||
it('defaults to Bitcoin network', function() {
|
||||
assert.equal(wallet.getMasterKey().network, 'Bitcoin')
|
||||
it('defaults to Bitcoin mainnet', function() {
|
||||
assert.equal(wallet.getMasterKey().network, 'mainnet')
|
||||
})
|
||||
|
||||
it('defaults to private derivationMethod', function() {
|
||||
|
@ -22,15 +22,16 @@ describe('Wallet', function() {
|
|||
describe('constructor options', function() {
|
||||
var wallet;
|
||||
beforeEach(function() {
|
||||
wallet = new Wallet(seed, {network: 'Test', derivationMethod: 'public'})
|
||||
wallet = new Wallet(seed, {network: 'testnet', derivationMethod: 'public'})
|
||||
})
|
||||
|
||||
it('uses the network if specified', function() {
|
||||
assert.equal(wallet.getMasterKey().network, 'Test')
|
||||
assert.equal(wallet.getMasterKey().network, 'testnet')
|
||||
})
|
||||
|
||||
it('uses the derivationMethod if specified', function() {
|
||||
assert.equal(wallet.derivationMethod, 'public')
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue