...had to use jsSHA package to do SHA512 in the browser. Unfortunately it is quite slow compared to node.
404 lines
14 KiB
JavaScript
404 lines
14 KiB
JavaScript
var imports = require('soop').imports();
|
|
var base58 = imports.base58 || require('base58-native').base58;
|
|
var coinUtil = imports.coinUtil || require('./util/util');
|
|
var Key = imports.Key || require('./Key');
|
|
var bignum = imports.bignum || require('bignum');
|
|
var crypto = require('crypto');
|
|
|
|
var BITCOIN_MAINNET_PUBLIC = 0x0488b21e;
|
|
var BITCOIN_MAINNET_PRIVATE = 0x0488ade4;
|
|
var BITCOIN_TESTNET_PUBLIC = 0x043587cf;
|
|
var BITCOIN_TESTNET_PRIVATE = 0x04358394;
|
|
var DOGECOIN_MAINNET_PUBLIC = 0x02facafd;
|
|
var DOGECOIN_MAINNET_PRIVATE = 0x02fac398;
|
|
var DOGECOIN_TESTNET_PUBLIC = 0x0432a9a8;
|
|
var DOGECOIN_TESTNET_PRIVATE = 0x0432a243;
|
|
var LITECOIN_MAINNET_PUBLIC = 0x019da462;
|
|
var LITECOIN_MAINNET_PRIVATE = 0x019d9cfe;
|
|
var LITECOIN_TESTNET_PUBLIC = 0x0436f6e1;
|
|
var LITECOIN_TESTNET_PRIVATE = 0x0436ef7d;
|
|
var secp256k1_n = new bignum("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
|
|
var secp256k1_G = new bignum("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); //x coordinate
|
|
|
|
var BIP32 = function(bytes) {
|
|
// decode base58
|
|
if (typeof bytes === "string") {
|
|
var decoded = base58.decode(bytes);
|
|
if (decoded.length != 82)
|
|
throw new Error("Not enough data");
|
|
var checksum = decoded.slice(78, 82);
|
|
bytes = decoded.slice(0, 78);
|
|
|
|
var hash = coinUtil.sha256(coinUtil.sha256(bytes));
|
|
|
|
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) {
|
|
throw new Error("Invalid checksum");
|
|
}
|
|
}
|
|
|
|
if (bytes !== undefined)
|
|
this.init_from_bytes(bytes);
|
|
}
|
|
|
|
BIP32.prototype.init_from_bytes = function(bytes) {
|
|
// Both pub and private extended keys are 78 bytes
|
|
if(bytes.length != 78) throw new Error("not enough data");
|
|
|
|
this.version = u32(bytes.slice(0, 4));
|
|
this.depth = u8(bytes.slice(4, 5));
|
|
this.parent_fingerprint = bytes.slice(5, 9);
|
|
this.child_index = u32(bytes.slice(9, 13));
|
|
this.chain_code = bytes.slice(13, 45);
|
|
|
|
var key_bytes = bytes.slice(45, 78);
|
|
|
|
var is_private =
|
|
(this.version == BITCOIN_MAINNET_PRIVATE ||
|
|
this.version == BITCOIN_TESTNET_PRIVATE ||
|
|
this.version == DOGECOIN_MAINNET_PRIVATE ||
|
|
this.version == DOGECOIN_TESTNET_PRIVATE ||
|
|
this.version == LITECOIN_MAINNET_PRIVATE ||
|
|
this.version == LITECOIN_TESTNET_PRIVATE );
|
|
|
|
var is_public =
|
|
(this.version == BITCOIN_MAINNET_PUBLIC ||
|
|
this.version == BITCOIN_TESTNET_PUBLIC ||
|
|
this.version == DOGECOIN_MAINNET_PUBLIC ||
|
|
this.version == DOGECOIN_TESTNET_PUBLIC ||
|
|
this.version == LITECOIN_MAINNET_PUBLIC ||
|
|
this.version == LITECOIN_TESTNET_PUBLIC );
|
|
|
|
if (is_private && key_bytes[0] == 0) {
|
|
/*
|
|
this.eckey = new Bitcoin.ECKey(key_bytes.slice(1, 33));
|
|
this.eckey.setCompressed(true);
|
|
|
|
var ecparams = getSECCurveByName("secp256k1");
|
|
var pt = ecparams.getG().multiply(this.eckey.priv);
|
|
this.eckey.pub = pt;
|
|
this.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(this.eckey.pub.getEncoded(true));
|
|
this.has_private_key = true;
|
|
*/
|
|
this.eckey = new Key();
|
|
this.eckey.private = key_bytes.slice(1, 33);
|
|
this.eckey.compressed = true;
|
|
this.eckey.regenerateSync();
|
|
this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); //not compressed ... seems to conflict with below
|
|
this.has_private_key = true;
|
|
} else if (is_public && (key_bytes[0] == 0x02 || key_bytes[0] == 0x03)) {
|
|
/*
|
|
this.eckey = new Bitcoin.ECKey();
|
|
this.eckey.pub = decompress_pubkey(key_bytes);
|
|
this.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(this.eckey.pub.getEncoded(true));
|
|
//TODO: why compute hash of uncompressed, then compress again?
|
|
this.eckey.setCompressed(true);
|
|
this.has_private_key = false;
|
|
*/
|
|
this.eckey = new Key();
|
|
this.eckey.public = key_bytes; //assume compressed
|
|
this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); //not compressed ... seems to conflict with above
|
|
this.has_private_key = false;
|
|
} else {
|
|
throw new Error("Invalid key");
|
|
}
|
|
|
|
this.build_extended_public_key();
|
|
this.build_extended_private_key();
|
|
}
|
|
|
|
BIP32.prototype.build_extended_public_key = function() {
|
|
this.extended_public_key = new Buffer([]);
|
|
|
|
var v = null;
|
|
switch(this.version) {
|
|
case BITCOIN_MAINNET_PUBLIC:
|
|
case BITCOIN_MAINNET_PRIVATE:
|
|
v = BITCOIN_MAINNET_PUBLIC;
|
|
break;
|
|
case BITCOIN_TESTNET_PUBLIC:
|
|
case BITCOIN_TESTNET_PRIVATE:
|
|
v = BITCOIN_TESTNET_PUBLIC;
|
|
break;
|
|
case DOGECOIN_MAINNET_PUBLIC:
|
|
case DOGECOIN_MAINNET_PRIVATE:
|
|
v = DOGECOIN_MAINNET_PUBLIC;
|
|
break;
|
|
case DOGECOIN_TESTNET_PUBLIC:
|
|
case DOGECOIN_TESTNET_PRIVATE:
|
|
v = DOGECOIN_TESTNET_PUBLIC;
|
|
break;
|
|
case LITECOIN_MAINNET_PUBLIC:
|
|
case LITECOIN_MAINNET_PRIVATE:
|
|
v = LITECOIN_MAINNET_PUBLIC;
|
|
break;
|
|
case LITECOIN_TESTNET_PUBLIC:
|
|
case LITECOIN_TESTNET_PRIVATE:
|
|
v = LITECOIN_TESTNET_PUBLIC;
|
|
break;
|
|
default:
|
|
throw new Error("Unknown version");
|
|
}
|
|
|
|
// Version
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([v >> 24])]);
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([(v >> 16) & 0xff])]);
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([(v >> 8) & 0xff])]);
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([v & 0xff])]);
|
|
|
|
// Depth
|
|
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([this.depth])]);
|
|
|
|
// Parent fingerprint
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, this.parent_fingerprint]);
|
|
|
|
// Child index
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([this.child_index >>> 24])]);
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([(this.child_index >>> 16) & 0xff])]);
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([(this.child_index >>> 8) & 0xff])]);
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([this.child_index & 0xff])]);
|
|
|
|
// Chain code
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, this.chain_code]);
|
|
|
|
// Public key
|
|
this.extended_public_key = Buffer.concat([this.extended_public_key, this.eckey.public]);
|
|
}
|
|
|
|
BIP32.prototype.extended_public_key_string = function(format) {
|
|
if (format === undefined || format === "base58") {
|
|
var hash = coinUtil.sha256(coinUtil.sha256(this.extended_public_key));
|
|
var checksum = hash.slice(0, 4);
|
|
var data = Buffer.concat([this.extended_public_key, checksum]);
|
|
return base58.encode(data);
|
|
} else if (format === "hex") {
|
|
return this.extended_public_key.toString('hex');;
|
|
} else {
|
|
throw new Error("bad format");
|
|
}
|
|
}
|
|
|
|
BIP32.prototype.build_extended_private_key = function() {
|
|
if (!this.has_private_key) return;
|
|
this.extended_private_key = new Buffer([]);
|
|
|
|
var v = this.version;
|
|
|
|
// Version
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([v >> 24])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([(v >> 16) & 0xff])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([(v >> 8) & 0xff])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([v & 0xff])]);
|
|
|
|
// Depth
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([this.depth])]);
|
|
|
|
// Parent fingerprint
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, this.parent_fingerprint]);
|
|
|
|
// Child index
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([this.child_index >>> 24])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([(this.child_index >>> 16) & 0xff])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([(this.child_index >>> 8) & 0xff])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([this.child_index & 0xff])]);
|
|
|
|
// Chain code
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, this.chain_code]);
|
|
|
|
// Private key
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([0])]);
|
|
this.extended_private_key = Buffer.concat([this.extended_private_key, this.eckey.private]);
|
|
}
|
|
|
|
BIP32.prototype.extended_private_key_string = function(format) {
|
|
if (format === undefined || format === "base58") {
|
|
var hash = coinUtil.sha256(coinUtil.sha256(this.extended_private_key));
|
|
var checksum = hash.slice(0, 4);
|
|
var data = Buffer.concat([this.extended_private_key, checksum]);
|
|
return base58.encode(data);
|
|
} else if (format === "hex") {
|
|
return this.extended_private_key.toString('hex');
|
|
} else {
|
|
throw new Error("bad 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("invalid path");
|
|
continue;
|
|
}
|
|
|
|
var use_private = (c.length > 1) && (c[c.length-1] == '\'');
|
|
var child_index = parseInt(use_private ? c.slice(0, c.length - 1) : c) & 0x7fffffff;
|
|
|
|
if (use_private)
|
|
child_index += 0x80000000;
|
|
|
|
bip32 = bip32.derive_child(child_index);
|
|
}
|
|
|
|
return bip32;
|
|
}
|
|
|
|
BIP32.prototype.derive_child = function(i) {
|
|
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 use_private = (i & 0x80000000) != 0;
|
|
//var ecparams = getSECCurveByName("secp256k1");
|
|
|
|
var is_private =
|
|
(this.version == BITCOIN_MAINNET_PRIVATE ||
|
|
this.version == BITCOIN_TESTNET_PRIVATE ||
|
|
this.version == DOGECOIN_MAINNET_PRIVATE ||
|
|
this.version == DOGECOIN_TESTNET_PRIVATE ||
|
|
this.version == LITECOIN_MAINNET_PRIVATE ||
|
|
this.version == LITECOIN_TESTNET_PRIVATE);
|
|
|
|
if (use_private && (!this.has_private_key || !is_private))
|
|
throw new Error("Cannot do private key derivation without private key");
|
|
|
|
var ret = null;
|
|
if (this.has_private_key) {
|
|
var data = null;
|
|
|
|
if (use_private) {
|
|
data = Buffer.concat([new Buffer([0]), this.eckey.private, ib]);
|
|
} else {
|
|
data = Buffer.concat([this.eckey.public, ib]);
|
|
}
|
|
|
|
/*
|
|
var j = new jsSHA(Crypto.util.bytesToHex(data), 'HEX');
|
|
var hash = j.getHMAC(Crypto.util.bytesToHex(this.chain_code), "HEX", "SHA-512", "HEX");
|
|
var il = new BigInteger(hash.slice(0, 64), 16);
|
|
var ir = Crypto.util.hexToBytes(hash.slice(64, 128));
|
|
*/
|
|
var hash = coinUtil.sha512hmac(data, this.chain_code);
|
|
var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32});
|
|
var ir = hash.slice(32, 64);
|
|
|
|
// ki = IL + kpar (mod n).
|
|
var priv = bignum.fromBuffer(this.eckey.private, {size: 32});
|
|
var k = il.add(priv).mod(secp256k1_n);
|
|
|
|
ret = new BIP32();
|
|
ret.chain_code = ir;
|
|
|
|
ret.eckey = new Key();
|
|
ret.eckey.private = k.toBuffer({size: 32});
|
|
ret.eckey.regenerateSync();
|
|
ret.has_private_key = true;
|
|
|
|
} else {
|
|
/*
|
|
var data = this.eckey.public.getEncoded(true).concat(ib);
|
|
var data = Buffer.concat([this.eckey.public, new Buffer(ib]);
|
|
var j = new jsSHA(Crypto.util.bytesToHex(data), 'HEX');
|
|
var hash = j.getHMAC(Crypto.util.bytesToHex(this.chain_code), "HEX", "SHA-512", "HEX");
|
|
var il = new BigInteger(hash.slice(0, 64), 16);
|
|
var ir = Crypto.util.hexToBytes(hash.slice(64, 128));
|
|
*/
|
|
var data = Buffer.concat([this.eckey.public, ib]);
|
|
var hash = coinUtil.sha512hmac(data, this.chain_code);
|
|
var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32});
|
|
var ir = hash.slice(32, 64);
|
|
|
|
// Ki = (IL + kpar)*G = IL*G + Kpar
|
|
//var k = ecparams.getG().multiply(il).add(this.eckey.pub);
|
|
var pub = new bignum(this.eckey.public, {size: 32});
|
|
var k = secp256k1_G.mul(il).add(pub);
|
|
|
|
//compressed pubkey must start with 0x02 just like compressed G
|
|
var kbuf = Buffer.concat([new Buffer(0x02), k.toBuffer({size: 32})]);
|
|
|
|
ret = new BIP32();
|
|
ret.chain_code = new Buffer(ir);
|
|
|
|
ret.eckey = new Key();
|
|
ret.eckey.public = kbuf;
|
|
ret.has_private_key = false;
|
|
}
|
|
|
|
ret.child_index = i;
|
|
ret.parent_fingerprint = this.pubKeyHash.slice(0,4);
|
|
ret.version = this.version;
|
|
ret.depth = this.depth + 1;
|
|
|
|
ret.eckey.compressed = true;
|
|
ret.pubKeyHash = coinUtil.sha256ripe160(ret.eckey.public);
|
|
|
|
ret.build_extended_public_key();
|
|
ret.build_extended_private_key();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
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);}
|
|
|
|
/*
|
|
//This function is not actually necessary
|
|
|
|
function decompress_pubkey(key_bytes) {
|
|
//TODO: Fix this whole function
|
|
var y_bit = u8(key_bytes.slice(0, 1)) & 0x01;
|
|
var ecparams = getSECCurveByName("secp256k1");
|
|
|
|
// build X
|
|
var x = BigInteger.ZERO.clone();
|
|
x.fromString(Crypto.util.bytesToHex(key_bytes.slice(1, 33)), 16);
|
|
|
|
// get curve
|
|
var curve = ecparams.getCurve();
|
|
var a = curve.getA().toBigInteger();
|
|
var b = curve.getB().toBigInteger();
|
|
var p = curve.getQ();
|
|
|
|
// compute y^2 = x^3 + a*x + b
|
|
var tmp = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
|
|
|
|
// compute modular square root of y (mod p)
|
|
var y = tmp.modSqrt(p);
|
|
|
|
// flip sign if we need to
|
|
if ((y[0] & 0x01) != y_bit) {
|
|
y = y.multiply(new BigInteger("-1")).mod(p);
|
|
}
|
|
|
|
return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y));
|
|
}
|
|
*/
|
|
|
|
module.exports = require('soop')(BIP32);
|