"Keypair" is a more explanatory name, and also should be less confused with other kinds of keys (particularly "cipher keys", which are the keys used in symmetric block ciphers, especially AES).
354 lines
9.7 KiB
JavaScript
354 lines
9.7 KiB
JavaScript
var base58 = require('./base58');
|
|
var Hash = require('./hash');
|
|
var Keypair = require('./keypair');
|
|
var Pubkey = require('./pubkey');
|
|
var Privkey = require('./privkey');
|
|
var Point = require('./point');
|
|
var Random = require('./random');
|
|
var bn = require('./bn');
|
|
var constants = require('./constants');
|
|
|
|
var BIP32 = function BIP32() {
|
|
if (!(this instanceof BIP32))
|
|
return new BIP32();
|
|
}
|
|
|
|
BIP32.prototype.fromRandom = function(networkstr) {
|
|
if (!networkstr)
|
|
networkstr = 'mainnet';
|
|
this.version = constants[networkstr].bip32privkey;
|
|
this.depth = 0x00;
|
|
this.parentFingerprint = new Buffer([0, 0, 0, 0]);
|
|
this.childIndex = new Buffer([0, 0, 0, 0]);
|
|
this.chainCode = Random.getRandomBuffer(32);
|
|
this.key = (new Keypair()).fromRandom();
|
|
this.hasPrivateKey = true;
|
|
this.pubKeyHash = Hash.sha256ripemd160(this.key.pubkey.toBuffer());
|
|
this.buildExtendedPublicKey();
|
|
this.buildExtendedPrivateKey();
|
|
};
|
|
|
|
BIP32.prototype.fromString = function(str) {
|
|
var decoded = base58.decode(str);
|
|
if (decoded.length != 82)
|
|
throw new Error('gcNot enough data, expected 82 and received ' + decoded.length);
|
|
var checksum = decoded.slice(78, 82);
|
|
var bytes = decoded.slice(0, 78);
|
|
|
|
var hash = Hash.sha256sha256(bytes);
|
|
|
|
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
|
|
throw new Error('gcInvalid checksum');
|
|
|
|
if (bytes !== undefined && bytes !== null)
|
|
this.initFromBytes(bytes);
|
|
|
|
return this;
|
|
};
|
|
|
|
BIP32.prototype.fromSeed = function(bytes, networkstr) {
|
|
if (!networkstr)
|
|
networkstr = 'mainnet';
|
|
|
|
if (!Buffer.isBuffer(bytes))
|
|
throw new Error('gcbytes must be a buffer');
|
|
if (bytes.length < 128 / 8)
|
|
throw new Error('gcNeed more than 128 bytes of entropy');
|
|
if (bytes.length > 512 / 8)
|
|
throw new Error('gcMore than 512 bytes of entropy is nonstandard');
|
|
var hash = Hash.sha512hmac(bytes, new Buffer('Bitcoin seed'));
|
|
|
|
this.depth = 0x00;
|
|
this.parentFingerprint = new Buffer([0, 0, 0, 0]);
|
|
this.childIndex = new Buffer([0, 0, 0, 0]);
|
|
this.chainCode = hash.slice(32, 64);
|
|
this.version = constants[networkstr].bip32privkey;
|
|
this.key = new Keypair();
|
|
this.key.privkey = new Privkey({bn: bn.fromBuffer(hash.slice(0, 32))});
|
|
this.key.privkey2pubkey();
|
|
this.hasPrivateKey = true;
|
|
this.pubKeyHash = Hash.sha256ripemd160(this.key.pubkey.toBuffer());
|
|
|
|
this.buildExtendedPublicKey();
|
|
this.buildExtendedPrivateKey();
|
|
|
|
return this;
|
|
};
|
|
|
|
BIP32.prototype.initFromBytes = function(bytes) {
|
|
// Both pub and private extended keys are 78 bytes
|
|
if (bytes.length != 78)
|
|
throw new Error('gcnot enough data');
|
|
|
|
this.version = u32(bytes.slice(0, 4));
|
|
this.depth = u8(bytes.slice(4, 5));
|
|
this.parentFingerprint = bytes.slice(5, 9);
|
|
this.childIndex = u32(bytes.slice(9, 13));
|
|
this.chainCode = bytes.slice(13, 45);
|
|
|
|
var keyBytes = bytes.slice(45, 78);
|
|
|
|
var isPrivate =
|
|
(this.version == constants.mainnet.bip32privkey ||
|
|
this.version == constants.testnet.bip32privkey);
|
|
|
|
var isPublic =
|
|
(this.version == constants.mainnet.bip32pubkey ||
|
|
this.version == constants.testnet.bip32pubkey);
|
|
|
|
if (isPrivate && keyBytes[0] == 0) {
|
|
this.key = new Keypair();
|
|
this.key.privkey = new Privkey({bn: bn.fromBuffer(keyBytes.slice(1, 33))});
|
|
this.key.privkey2pubkey();
|
|
this.pubKeyHash = Hash.sha256ripemd160(this.key.pubkey.toBuffer());
|
|
this.hasPrivateKey = true;
|
|
} else if (isPublic && (keyBytes[0] == 0x02 || keyBytes[0] == 0x03)) {
|
|
this.key = new Keypair();
|
|
this.key.pubkey = (new Pubkey()).fromDER(keyBytes);
|
|
this.pubKeyHash = Hash.sha256ripemd160(this.key.pubkey.toBuffer());
|
|
this.hasPrivateKey = false;
|
|
} else {
|
|
throw new Error('gcInvalid key');
|
|
}
|
|
|
|
this.buildExtendedPublicKey();
|
|
this.buildExtendedPrivateKey();
|
|
}
|
|
|
|
BIP32.prototype.buildExtendedPublicKey = function() {
|
|
this.extendedPublicKey = new Buffer([]);
|
|
|
|
var v = null;
|
|
switch (this.version) {
|
|
case constants.mainnet.bip32pubkey:
|
|
case constants.mainnet.bip32privkey:
|
|
v = constants.mainnet.bip32pubkey;
|
|
break;
|
|
case constants.testnet.bip32pubkey:
|
|
case constants.testnet.bip32privkey:
|
|
v = constants.testnet.bip32pubkey;
|
|
break;
|
|
default:
|
|
throw new Error('gcUnknown version');
|
|
}
|
|
|
|
// Version
|
|
this.extendedPublicKey = Buffer.concat([
|
|
new Buffer([v >> 24]),
|
|
new Buffer([(v >> 16) & 0xff]),
|
|
new Buffer([(v >> 8) & 0xff]),
|
|
new Buffer([v & 0xff]),
|
|
new Buffer([this.depth]),
|
|
this.parentFingerprint,
|
|
new Buffer([this.childIndex >>> 24]),
|
|
new Buffer([(this.childIndex >>> 16) & 0xff]),
|
|
new Buffer([(this.childIndex >>> 8) & 0xff]),
|
|
new Buffer([this.childIndex & 0xff]),
|
|
this.chainCode,
|
|
this.key.pubkey.toBuffer()
|
|
]);
|
|
}
|
|
|
|
BIP32.prototype.extendedPublicKeyString = function(format) {
|
|
if (format === undefined || format === 'base58') {
|
|
if (!Buffer.isBuffer(this.extendedPublicKey))
|
|
console.log('extendedPublicKey: ' + this.extendedPublicKey);
|
|
var hash = Hash.sha256sha256(this.extendedPublicKey);
|
|
var checksum = hash.slice(0, 4);
|
|
var data = Buffer.concat([this.extendedPublicKey, checksum]);
|
|
return base58.encode(data);
|
|
} else if (format === 'hex') {
|
|
return this.extendedPublicKey.toString('hex');;
|
|
} else {
|
|
throw new Error('gcbad format');
|
|
}
|
|
}
|
|
|
|
BIP32.prototype.buildExtendedPrivateKey = function() {
|
|
if (!this.hasPrivateKey) return;
|
|
this.extendedPrivateKey = new Buffer([]);
|
|
|
|
var v = this.version;
|
|
|
|
this.extendedPrivateKey = Buffer.concat([
|
|
new Buffer([v >> 24]),
|
|
new Buffer([(v >> 16) & 0xff]),
|
|
new Buffer([(v >> 8) & 0xff]),
|
|
new Buffer([v & 0xff]),
|
|
new Buffer([this.depth]),
|
|
this.parentFingerprint,
|
|
new Buffer([this.childIndex >>> 24]),
|
|
new Buffer([(this.childIndex >>> 16) & 0xff]),
|
|
new Buffer([(this.childIndex >>> 8) & 0xff]),
|
|
new Buffer([this.childIndex & 0xff]),
|
|
this.chainCode,
|
|
new Buffer([0]),
|
|
this.key.privkey.bn.toBuffer({size: 32})
|
|
]);
|
|
}
|
|
|
|
BIP32.prototype.extendedPrivateKeyString = function(format) {
|
|
if (format === undefined || format === 'base58') {
|
|
var hash = Hash.sha256sha256(this.extendedPrivateKey);
|
|
var checksum = hash.slice(0, 4);
|
|
var data = Buffer.concat([this.extendedPrivateKey, checksum]);
|
|
return base58.encode(data);
|
|
} else if (format === 'hex') {
|
|
return this.extendedPrivateKey.toString('hex');
|
|
} else {
|
|
throw new Error('gcbad format');
|
|
}
|
|
}
|
|
|
|
|
|
BIP32.prototype.derive = function(path) {
|
|
var e = path.split('/');
|
|
|
|
// Special cases:
|
|
if (path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'')
|
|
return this;
|
|
|
|
var bip32 = this;
|
|
for (var i in e) {
|
|
var c = e[i];
|
|
|
|
if (i == 0) {
|
|
if (c != 'm') throw new Error('gcinvalid path');
|
|
continue;
|
|
}
|
|
|
|
if (parseInt(c.replace("'", "")).toString() !== c.replace("'", ""))
|
|
throw new Error('gcinvalid path');
|
|
|
|
var usePrivate = (c.length > 1) && (c[c.length - 1] == '\'');
|
|
var childIndex = parseInt(usePrivate ? c.slice(0, c.length - 1) : c) & 0x7fffffff;
|
|
|
|
if (usePrivate)
|
|
childIndex += 0x80000000;
|
|
|
|
bip32 = bip32.deriveChild(childIndex);
|
|
}
|
|
|
|
return bip32;
|
|
}
|
|
|
|
BIP32.prototype.deriveChild = function(i) {
|
|
if (typeof i !== 'number')
|
|
throw new Error('gci must be a number');
|
|
|
|
var ib = [];
|
|
ib.push((i >> 24) & 0xff);
|
|
ib.push((i >> 16) & 0xff);
|
|
ib.push((i >> 8) & 0xff);
|
|
ib.push(i & 0xff);
|
|
ib = new Buffer(ib);
|
|
|
|
var usePrivate = (i & 0x80000000) != 0;
|
|
|
|
var isPrivate =
|
|
(this.version == constants.mainnet.bip32privkey ||
|
|
this.version == constants.testnet.bip32privkey);
|
|
|
|
if (usePrivate && (!this.hasPrivateKey || !isPrivate))
|
|
throw new Error('gcCannot do private key derivation without private key');
|
|
|
|
var ret = null;
|
|
if (this.hasPrivateKey) {
|
|
var data = null;
|
|
|
|
if (usePrivate) {
|
|
data = Buffer.concat([new Buffer([0]), this.key.privkey.bn.toBuffer({size: 32}), ib]);
|
|
} else {
|
|
data = Buffer.concat([this.key.pubkey.toBuffer({size: 32}), ib]);
|
|
}
|
|
|
|
var hash = Hash.sha512hmac(data, this.chainCode);
|
|
var il = bn.fromBuffer(hash.slice(0, 32), {size: 32});
|
|
var ir = hash.slice(32, 64);
|
|
|
|
// ki = IL + kpar (mod n).
|
|
var k = il.add(this.key.privkey.bn).mod(Point.getN());
|
|
|
|
ret = new BIP32();
|
|
ret.chainCode = ir;
|
|
|
|
ret.key = new Keypair();
|
|
ret.key.privkey = new Privkey({bn: k});
|
|
ret.key.privkey2pubkey();
|
|
ret.hasPrivateKey = true;
|
|
|
|
} else {
|
|
var data = Buffer.concat([this.key.pubkey.toBuffer(), ib]);
|
|
var hash = Hash.sha512hmac(data, this.chainCode);
|
|
var il = bn(hash.slice(0, 32));
|
|
var ir = hash.slice(32, 64);
|
|
|
|
// Ki = (IL + kpar)*G = IL*G + Kpar
|
|
var ilG = Point.getG().mul(il);
|
|
var Kpar = this.key.pubkey.point;
|
|
var Ki = ilG.add(Kpar);
|
|
var newpub = new Pubkey();
|
|
newpub.point = Ki;
|
|
|
|
ret = new BIP32();
|
|
ret.chainCode = ir;
|
|
|
|
var key = new Keypair();
|
|
key.pubkey = newpub;
|
|
ret.key = key;
|
|
ret.hasPrivateKey = false;
|
|
}
|
|
|
|
ret.childIndex = i;
|
|
ret.parentFingerprint = this.pubKeyHash.slice(0, 4);
|
|
ret.version = this.version;
|
|
ret.depth = this.depth + 1;
|
|
|
|
ret.pubKeyHash = Hash.sha256ripemd160(ret.key.pubkey.toBuffer());
|
|
|
|
ret.buildExtendedPublicKey();
|
|
ret.buildExtendedPrivateKey();
|
|
|
|
return ret;
|
|
}
|
|
|
|
BIP32.prototype.toString = function() {
|
|
var isPrivate =
|
|
(this.version == constants.mainnet.bip32privkey ||
|
|
this.version == constants.testnet.bip32privkey);
|
|
|
|
if (isPrivate)
|
|
return this.extendedPrivateKeyString();
|
|
else
|
|
return this.extendedPublicKeyString();
|
|
};
|
|
|
|
function uint(f, size) {
|
|
if (f.length < size)
|
|
throw new Error('not enough data');
|
|
var n = 0;
|
|
for (var i = 0; i < size; i++) {
|
|
n *= 256;
|
|
n += f[i];
|
|
}
|
|
return n;
|
|
}
|
|
|
|
function u8(f) {
|
|
return uint(f, 1);
|
|
}
|
|
|
|
function u16(f) {
|
|
return uint(f, 2);
|
|
}
|
|
|
|
function u32(f) {
|
|
return uint(f, 4);
|
|
}
|
|
|
|
function u64(f) {
|
|
return uint(f, 8);
|
|
}
|
|
|
|
module.exports = BIP32;
|