diff --git a/BIP32.js b/BIP32.js index 4b42e31d7..7b70d0b96 100644 --- a/BIP32.js +++ b/BIP32.js @@ -1,349 +1,347 @@ -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 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 Point = imports.Point || require('./Point'); +var bignum = imports.bignum || require('bignum'); +var crypto = require('crypto'); +var networks = require('./networks'); + +var secp256k1_n = new bignum("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); +var secp256k1_Gx = new bignum("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); var BIP32 = function(bytes) { - // decode base58 - if( typeof bytes === "string" ) { - var decoded = Bitcoin.Base58.decode(bytes); - if( decoded.length != 82 ) throw new Error("Not enough data"); - var checksum = decoded.slice(78, 82); - bytes = decoded.slice(0, 78); + if (bytes == 'mainnet' || bytes == 'livenet') + this.version = networks['livenet'].bip32private; + else if (bytes == 'testnet') + this.version = networks['testnet'].bip32private; - var hash = Crypto.SHA256( Crypto.SHA256( bytes, { asBytes: true } ), { asBytes: true } ); + if (bytes == 'mainnet' || bytes == 'livenet' || bytes == 'testnet') { + this.depth = 0x00; + this.parentFingerprint = new Buffer([0, 0, 0, 0]); + this.childIndex = new Buffer([0, 0, 0, 0]); + this.chainCode = Key.generateSync().private; + this.eckey = Key.generateSync(); + this.hasPrivateKey = true; + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.buildExtendedPublicKey(); + this.buildExtendedPrivateKey(); + return; + } + + // 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); - if( hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3] ) { - throw new Error("Invalid checksum"); - } + 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); + if (bytes !== undefined) + this.initFromBytes(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"); +BIP32.seed = function(bytes, network) { + if (!network) + return false; - 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); + if (!Buffer.isBuffer(bytes)) + bytes = new Buffer(bytes, 'hex'); //if not buffer, assume hex + if (bytes.length < 128/8) + return false; //need more entropy + var hash = coinUtil.sha512hmac(bytes, new Buffer("Bitcoin seed")); - 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 bip32 = new BIP32(); + bip32.depth = 0x00; + bip32.parentFingerprint = new Buffer([0, 0, 0, 0]); + bip32.childIndex = new Buffer([0, 0, 0, 0]); + bip32.chainCode = hash.slice(32, 64); + bip32.version = networks[network].bip32private; + bip32.eckey = new Key(); + bip32.eckey.private = hash.slice(0, 32); + bip32.eckey.regenerateSync(); + bip32.hasPrivateKey = true; + bip32.pubKeyHash = coinUtil.sha256ripe160(bip32.eckey.public); - 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 ); + bip32.buildExtendedPublicKey(); + bip32.buildExtendedPrivateKey(); - if( is_private && key_bytes[0] == 0 ) { - this.eckey = new Bitcoin.ECKey(key_bytes.slice(1, 33)); - this.eckey.setCompressed(true); + return bip32; +}; - 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; - } 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)); - this.eckey.setCompressed(true); - this.has_private_key = false; - } else { - throw new Error("Invalid key"); - } +BIP32.prototype.initFromBytes = function(bytes) { + // Both pub and private extended keys are 78 bytes + if(bytes.length != 78) throw new Error("not enough data"); - this.build_extended_public_key(); - this.build_extended_private_key(); + 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 == networks['livenet'].bip32private || + this.version == networks['testnet'].bip32private ); + + var isPublic = + (this.version == networks['livenet'].bip32public || + this.version == networks['testnet'].bip32public ); + + if (isPrivate && keyBytes[0] == 0) { + this.eckey = new Key(); + this.eckey.private = keyBytes.slice(1, 33); + this.eckey.compressed = true; + this.eckey.regenerateSync(); + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.hasPrivateKey = true; + } else if (isPublic && (keyBytes[0] == 0x02 || keyBytes[0] == 0x03)) { + this.eckey = new Key(); + this.eckey.public = keyBytes; + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.hasPrivateKey = false; + } else { + throw new Error("Invalid key"); + } + + this.buildExtendedPublicKey(); + this.buildExtendedPrivateKey(); } -BIP32.prototype.build_extended_public_key = function() { - this.extended_public_key = []; +BIP32.prototype.buildExtendedPublicKey = function() { + this.extendedPublicKey = 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"); - } + var v = null; + switch(this.version) { + case networks['livenet'].bip32public: + case networks['livenet'].bip32private: + v = networks['livenet'].bip32public; + break; + case networks['testnet'].bip32public: + case networks['testnet'].bip32private: + v = networks['testnet'].bip32public; + break; + default: + throw new Error("Unknown version"); + } - // Version - this.extended_public_key.push(v >> 24); - this.extended_public_key.push((v >> 16) & 0xff); - this.extended_public_key.push((v >> 8) & 0xff); - this.extended_public_key.push(v & 0xff); + // Version + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([v >> 24])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([(v >> 16) & 0xff])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([(v >> 8) & 0xff])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([v & 0xff])]); - // Depth - this.extended_public_key.push(this.depth); + // Depth + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([this.depth])]); - // Parent fingerprint - this.extended_public_key = this.extended_public_key.concat(this.parent_fingerprint); + // Parent fingerprint + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, this.parentFingerprint]); - // Child index - this.extended_public_key.push(this.child_index >>> 24); - this.extended_public_key.push((this.child_index >>> 16) & 0xff); - this.extended_public_key.push((this.child_index >>> 8) & 0xff); - this.extended_public_key.push(this.child_index & 0xff); + // Child index + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([this.childIndex >>> 24])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([(this.childIndex >>> 16) & 0xff])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([(this.childIndex >>> 8) & 0xff])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([this.childIndex & 0xff])]); - // Chain code - this.extended_public_key = this.extended_public_key.concat(this.chain_code); + // Chain code + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, this.chainCode]); - // Public key - this.extended_public_key = this.extended_public_key.concat(this.eckey.pub.getEncoded(true)); + // Public key + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, this.eckey.public]); } -BIP32.prototype.extended_public_key_string = function(format) { - if( format === undefined || format === "base58" ) { - var hash = Crypto.SHA256( Crypto.SHA256( this.extended_public_key, { asBytes: true } ), { asBytes: true } ); - var checksum = hash.slice(0, 4); - var data = this.extended_public_key.concat(checksum); - return Bitcoin.Base58.encode(data); - } else if( format === "hex" ) { - return Crypto.util.bytesToHex(this.extended_public_key); - } else { - throw new Error("bad format"); - } +BIP32.prototype.extendedPublicKeyString = function(format) { + if (format === undefined || format === "base58") { + var hash = coinUtil.sha256(coinUtil.sha256(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("bad format"); + } } -BIP32.prototype.build_extended_private_key = function() { - if( !this.has_private_key ) return; - this.extended_private_key = []; +BIP32.prototype.buildExtendedPrivateKey = function() { + if (!this.hasPrivateKey) return; + this.extendedPrivateKey = new Buffer([]); - var v = this.version; + var v = this.version; - // Version - this.extended_private_key.push(v >> 24); - this.extended_private_key.push((v >> 16) & 0xff); - this.extended_private_key.push((v >> 8) & 0xff); - this.extended_private_key.push(v & 0xff); + // Version + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([v >> 24])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([(v >> 16) & 0xff])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([(v >> 8) & 0xff])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([v & 0xff])]); - // Depth - this.extended_private_key.push(this.depth); + // Depth + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([this.depth])]); - // Parent fingerprint - this.extended_private_key = this.extended_private_key.concat(this.parent_fingerprint); + // Parent fingerprint + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, this.parentFingerprint]); - // Child index - this.extended_private_key.push(this.child_index >>> 24); - this.extended_private_key.push((this.child_index >>> 16) & 0xff); - this.extended_private_key.push((this.child_index >>> 8) & 0xff); - this.extended_private_key.push(this.child_index & 0xff); + // Child index + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([this.childIndex >>> 24])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([(this.childIndex >>> 16) & 0xff])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([(this.childIndex >>> 8) & 0xff])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([this.childIndex & 0xff])]); - // Chain code - this.extended_private_key = this.extended_private_key.concat(this.chain_code); + // Chain code + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, this.chainCode]); - // Private key - this.extended_private_key.push(0); - this.extended_private_key = this.extended_private_key.concat(this.eckey.priv.toByteArrayUnsigned()); + // Private key + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([0])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, this.eckey.private]); } -BIP32.prototype.extended_private_key_string = function(format) { - if( format === undefined || format === "base58" ) { - var hash = Crypto.SHA256( Crypto.SHA256( this.extended_private_key, { asBytes: true } ), { asBytes: true } ); - var checksum = hash.slice(0, 4); - var data = this.extended_private_key.concat(checksum); - return Bitcoin.Base58.encode(data); - } else if( format === "hex" ) { - return Crypto.util.bytesToHex(this.extended_private_key); - } else { - throw new Error("bad format"); - } +BIP32.prototype.extendedPrivateKeyString = function(format) { + if (format === undefined || format === "base58") { + var hash = coinUtil.sha256(coinUtil.sha256(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("bad format"); + } } BIP32.prototype.derive = function(path) { - var e = path.split('/'); + var e = path.split('/'); - // Special cases: - if( path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'' ) return this; + // 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]; + 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); + if (i == 0 ) { + if (c != 'm') throw new Error("invalid path"); + continue; } - return bip32; + 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.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 ); +BIP32.prototype.deriveChild = 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 usePrivate = (i & 0x80000000) != 0; - 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 isPrivate = + (this.version == networks['livenet'].bip32private || + this.version == networks['testnet'].bip32private ); - if( use_private && (!this.has_private_key || !is_private) ) throw new Error("Cannot do private key derivation without private key"); + if (usePrivate && (!this.hasPrivateKey || !isPrivate)) + 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 = [0].concat(this.eckey.priv.toByteArrayUnsigned()).concat(ib); - } else { - data = this.eckey.pub.getEncoded(true).concat(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)); - - // ki = IL + kpar (mod n). - var curve = ecparams.getCurve(); - var k = il.add(this.eckey.priv).mod(ecparams.getN()); - - ret = new BIP32(); - ret.chain_code = ir; - - ret.eckey = new Bitcoin.ECKey(k.toByteArrayUnsigned()); - ret.eckey.pub = ret.eckey.getPubPoint(); - ret.has_private_key = true; + var ret = null; + if (this.hasPrivateKey) { + var data = null; + if (usePrivate) { + data = Buffer.concat([new Buffer([0]), this.eckey.private, ib]); } else { - var data = this.eckey.pub.getEncoded(true).concat(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)); - - // Ki = (IL + kpar)*G = IL*G + Kpar - var k = ecparams.getG().multiply(il).add(this.eckey.pub); - - ret = new BIP32(); - ret.chain_code = ir; - - ret.eckey = new Bitcoin.ECKey(); - ret.eckey.pub = k; - ret.has_private_key = false; + data = Buffer.concat([this.eckey.public, ib]); } - ret.child_index = i; - ret.parent_fingerprint = this.eckey.pubKeyHash.slice(0,4); - ret.version = this.version; - ret.depth = this.depth + 1; + var hash = coinUtil.sha512hmac(data, this.chainCode); + var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); + var ir = hash.slice(32, 64); - ret.eckey.setCompressed(true); - ret.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(ret.eckey.pub.getEncoded(true)); + // ki = IL + kpar (mod n). + var priv = bignum.fromBuffer(this.eckey.private, {size: 32}); + var k = il.add(priv).mod(secp256k1_n); - ret.build_extended_public_key(); - ret.build_extended_private_key(); + ret = new BIP32(); + ret.chainCode = ir; - return ret; + ret.eckey = new Key(); + ret.eckey.private = k.toBuffer({size: 32}); + ret.eckey.regenerateSync(); + ret.hasPrivateKey = true; + + } else { + var data = Buffer.concat([this.eckey.public, ib]); + var hash = coinUtil.sha512hmac(data, this.chainCode); + var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); + var ir = hash.slice(32, 64); + + // Ki = (IL + kpar)*G = IL*G + Kpar + var ilGkey = new Key(); + ilGkey.private = il.toBuffer({size: 32}); + ilGkey.regenerateSync(); + var ilG = Point.fromKey(ilGkey); + var oldkey = new Key(); + oldkey.public = this.eckey.public; + var Kpar = Point.fromKey(oldkey); + var newpub = Point.add(ilG, Kpar).toKey().public; + + ret = new BIP32(); + ret.chainCode = new Buffer(ir); + + var eckey = new Key(); + eckey.public = newpub; + ret.eckey = eckey; + ret.hasPrivateKey = false; + } + + ret.childIndex = i; + ret.parentFingerprint = 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.buildExtendedPublicKey(); + ret.buildExtendedPrivateKey(); + + 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; + 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); } +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);} -function decompress_pubkey(key_bytes) { - 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); diff --git a/Key.js b/Key.js index 4c880ab51..f386a7b42 100644 --- a/Key.js +++ b/Key.js @@ -10,7 +10,12 @@ if (process.versions) { var ECKey = require('./browser/vendor-bundle.js').ECKey; var buffertools = require('buffertools'); - var bufferToArray = function(buffer) { + var kSpec = function() { + this._pub = null; + this.compressed = true; // default + }; + + var bufferToArray = kSpec.bufferToArray = function(buffer) { var ret = []; var l = buffer.length; @@ -21,11 +26,6 @@ if (process.versions) { return ret; } - var kSpec = function() { - this._pub = null; - this.compressed = true; // default - }; - Object.defineProperty(kSpec.prototype, 'public', { set: function(p){ diff --git a/Point.js b/Point.js new file mode 100644 index 000000000..b18971d35 --- /dev/null +++ b/Point.js @@ -0,0 +1,138 @@ +"use strict"; + +var imports = require('soop').imports(); +var Key = imports.Key || require('./Key'); +var bignum = imports.bignum || require('bignum'); +var assert = require('assert'); + +//browser +if (!process.versions) { + var ECKey = require('./browser/vendor-bundle.js').ECKey; + var ECPointFp = require('./browser/vendor-bundle.js').ECPointFp; + var ECFieldElementFp = require('./browser/vendor-bundle.js').ECFieldElementFp; + var getSECCurveByName = require('./browser/vendor-bundle.js').getSECCurveByName; + var BigInteger = require('./browser/vendor-bundle.js').BigInteger; + var should = require('chai').should(); +} + + +//a point on the secp256k1 curve +//x and y are bignums +var Point = function(x, y) { + this.x = x; + this.y = y; +}; + +Point.add = function(p1, p2) { + + //node + if (process.versions) { + var key1 = p1.toKey(); + key1.compressed = false; + var key2 = p2.toKey(); + key2.compressed = false; + var pubKey = Key.addUncompressed(key1.public, key2.public); + var key = new Key(); + key.compressed = false; + key.public = pubKey; + key.compressed = true; + return Point.fromKey(key); + } + + //browser + else { + var ecparams = getSECCurveByName('secp256k1'); + + var p1xhex = p1.x.toBuffer({size: 32}).toString('hex'); + var p1x = new BigInteger(p1xhex, 16); + var p1yhex = p1.y.toBuffer({size: 32}).toString('hex'); + var p1y = new BigInteger(p1yhex, 16); + var p1px = new ECFieldElementFp(ecparams.getCurve().getQ(), p1x); + var p1py = new ECFieldElementFp(ecparams.getCurve().getQ(), p1y); + var p1p = new ECPointFp(ecparams.getCurve(), p1px, p1py); + + var p2xhex = p2.x.toBuffer({size: 32}).toString('hex'); + var p2x = new BigInteger(p2xhex, 16); + var p2yhex = p2.y.toBuffer({size: 32}).toString('hex'); + var p2y = new BigInteger(p2yhex, 16); + var p2px = new ECFieldElementFp(ecparams.getCurve().getQ(), p2x); + var p2py = new ECFieldElementFp(ecparams.getCurve().getQ(), p2y); + var p2p = new ECPointFp(ecparams.getCurve(), p2px, p2py); + + var p = p1p.add(p2p); + + var point = new Point(); + var pointxbuf = new Buffer(p.getX().toBigInteger().toByteArrayUnsigned()); + point.x = bignum.fromBuffer(pointxbuf, {size: pointxbuf.length}); + assert(pointxbuf.length <= 32); + var pointybuf = new Buffer(p.getY().toBigInteger().toByteArrayUnsigned()); + assert(pointybuf.length <= 32); + point.y = bignum.fromBuffer(pointybuf, {size: pointybuf.length}); + + return point; + } + +}; + +//convert the public key of a Key into a Point +Point.fromKey = function(key) { + + //node + if (process.versions) { + var point = new Point(); + var pubKeyBuf = new Buffer(key.public); + var key2 = new Key(); + key2.compressed = key.compressed; + key2.public = pubKeyBuf; + key2.compressed = false; + point.x = bignum.fromBuffer(key2.public.slice(1, 33), {size: 32}); + point.y = bignum.fromBuffer(key2.public.slice(33, 65), {size: 32}); + return point; + } + + //browser + else { + var point = new Point(); + var pubKeyBuf = new Buffer(key.public); + var key2 = new ECKey(); + key2.setCompressed(key.compressed); + key2.setPub(Key.bufferToArray(pubKeyBuf)); + key2.setCompressed(false); + point.x = bignum.fromBuffer((new Buffer(key2.getPub())).slice(1, 33), {size: 32}); + point.y = bignum.fromBuffer((new Buffer(key2.getPub())).slice(33, 65), {size: 32}); + return point; + } +}; + +//convert the Point into the Key containing a compressed public key +Point.prototype.toKey = function() { + + //node + if (process.versions) { + var xbuf = this.x.toBuffer({size: 32}); + var ybuf = this.y.toBuffer({size: 32}); + var key = new Key(); + key.compressed = false; + var prefix = new Buffer([0x04]); + key.public = Buffer.concat([prefix, xbuf, ybuf]); //this might be wrong + key.compressed = true; + return key; + } + + //browser + else { + var xbuf = this.x.toBuffer({size: 32}); + var ybuf = this.y.toBuffer({size: 32}); + var key = new ECKey(); + key.setCompressed(false); + var prefix = new Buffer([0x04]); + var pub = Buffer.concat([prefix, xbuf, ybuf]); //this might be wrong + key.setPub(Key.bufferToArray(pub)); + key.setCompressed(true); + var key2 = new Key(); + key2.public = new Buffer(key.getPub()); + return key2; + } +}; + +module.exports = require('soop')(Point); diff --git a/bitcore.js b/bitcore.js index 568f023af..bcd0ea78c 100644 --- a/bitcore.js +++ b/bitcore.js @@ -24,6 +24,8 @@ requireWhenAccessed('EncodedData', './util/EncodedData'); requireWhenAccessed('VersionedData', './util/VersionedData'); requireWhenAccessed('BinaryParser', './util/BinaryParser'); requireWhenAccessed('Address', './Address'); +requireWhenAccessed('BIP32', './BIP32'); +requireWhenAccessed('Point', './Point'); requireWhenAccessed('Opcode', './Opcode'); requireWhenAccessed('Script', './Script'); requireWhenAccessed('Transaction', './Transaction'); diff --git a/browser/build.js b/browser/build.js index f3816e5c6..545219f39 100644 --- a/browser/build.js +++ b/browser/build.js @@ -24,6 +24,7 @@ var pack = function (params) { var modules = [ 'Address', + 'BIP32', 'Block', 'Bloom', 'Buffers.monkey', @@ -37,6 +38,7 @@ var modules = [ 'PrivateKey', 'RpcClient', 'Key', + 'Point', 'SIN', 'SINKey', 'Script', diff --git a/browser/vendor/ec.js b/browser/vendor/ec.js index 43ded3ea7..4fb77e64d 100644 --- a/browser/vendor/ec.js +++ b/browser/vendor/ec.js @@ -314,3 +314,6 @@ ECCurveFp.prototype.equals = curveFpEquals; ECCurveFp.prototype.getInfinity = curveFpGetInfinity; ECCurveFp.prototype.fromBigInteger = curveFpFromBigInteger; ECCurveFp.prototype.decodePointHex = curveFpDecodePointHex; + +module.exports.ECPointFp = ECPointFp; +module.exports.ECFieldElementFp = ECFieldElementFp; diff --git a/browser/vendor/jsbn2.js b/browser/vendor/jsbn2.js index 5b2b725c4..e9ff49050 100644 --- a/browser/vendor/jsbn2.js +++ b/browser/vendor/jsbn2.js @@ -654,3 +654,5 @@ BigInteger.prototype.square = bnSquare; // int hashCode() // long longValue() // static BigInteger valueOf(long val) + +module.exports.BigInteger = BigInteger; diff --git a/browser/vendor/sec.js b/browser/vendor/sec.js index e496571dc..57ee9368f 100644 --- a/browser/vendor/sec.js +++ b/browser/vendor/sec.js @@ -171,3 +171,5 @@ function getSECCurveByName(name) { if(name == "secp256r1") return secp256r1(); return null; } + +module.exports.getSECCurveByName = getSECCurveByName; diff --git a/networks.js b/networks.js index 07e10f341..2f8aa210d 100644 --- a/networks.js +++ b/networks.js @@ -42,6 +42,8 @@ exports.livenet = { checkpoints: [], // need to put checkpoint blocks here addressPubkey: 0, addressScript: 5, + bip32public: 0x0488b21e, + bip32private: 0x0488ade4, keySecret: 128, }; @@ -64,5 +66,7 @@ exports.testnet = { checkpoints: [], // need to put checkput blocks here addressPubkey: 111, addressScript: 196, + bip32public: 0x043587cf, + bip32private: 0x04358394, keySecret: 239, }; diff --git a/package.json b/package.json index eb5a3c5c2..61d4857e0 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "postinstall": "node browser/build.js -a" }, "dependencies": { + "jssha": "=1.5.0", "soop": "=0.1.5", "base58-native": "=0.1.3", "bindings": "=1.1.1", diff --git a/src/eckey.cc b/src/eckey.cc index ed632605d..9f0886e82 100644 --- a/src/eckey.cc +++ b/src/eckey.cc @@ -10,6 +10,8 @@ #include #include +#include + #include "common.h" #include "eckey.h" @@ -121,6 +123,7 @@ void Key::Init(Handle target) // Static methods NODE_SET_METHOD(s_ct->GetFunction(), "generateSync", GenerateSync); NODE_SET_METHOD(s_ct->GetFunction(), "fromDER", FromDER); + NODE_SET_METHOD(s_ct->GetFunction(), "addUncompressed", AddUncompressed); target->Set(String::NewSymbol("Key"), s_ct->GetFunction()); @@ -402,6 +405,81 @@ Key::FromDER(const Arguments& args) return scope.Close(result); } +Handle +Key::AddUncompressed(const Arguments& args) +{ + HandleScope scope; + + if (args.Length() != 2) { + return VException("Two arguments expected: point0, point1"); + } + if (!Buffer::HasInstance(args[0])) { + return VException("Argument 'point0' must be of type Buffer"); + } + if (Buffer::Length(args[0]) != 65) { + return VException("Argument 'point0' must have length 65"); + } + if (!Buffer::HasInstance(args[1])) { + return VException("Argument 'point1' must be of type Buffer"); + } + if (Buffer::Length(args[1]) != 65) { + return VException("Argument 'point1' must have length 65"); + } + + Handle point0_buf = args[0]->ToObject(); + unsigned char *point0 = (unsigned char*) Buffer::Data(point0_buf); + + Handle point1_buf = args[1]->ToObject(); + unsigned char *point1 = (unsigned char*) Buffer::Data(point1_buf); + + EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1); + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + BN_CTX *ctx; + EC_POINT *p0, *p1, *r; + BIGNUM *p0x, *p0y, *p1x, *p1y, *rx, *ry; + Buffer *rbuf; + + p0 = EC_POINT_new(group); + p1 = EC_POINT_new(group); + r = EC_POINT_new(group); + + p0x = BN_bin2bn(&point0[1], 32, BN_new()); + p0y = BN_bin2bn(&point0[33], 32, BN_new()); + p1x = BN_bin2bn(&point1[1], 32, BN_new()); + p1y = BN_bin2bn(&point1[33], 32, BN_new()); + + ctx = BN_CTX_new(); + + EC_POINT_set_affine_coordinates_GFp(group, p0, p0x, p0y, ctx); + EC_POINT_set_affine_coordinates_GFp(group, p1, p1x, p1y, ctx); + + EC_POINT_add(group, r, p0, p1, ctx); + + rx = BN_new(); + ry = BN_new(); + EC_POINT_get_affine_coordinates_GFp(group, r, rx, ry, ctx); + + rbuf = Buffer::New(65); + EC_POINT_point2oct(group, r, POINT_CONVERSION_UNCOMPRESSED, (unsigned char *)Buffer::Data(rbuf), 65, ctx); + + //free: eckey, p0, p1, r, p0x, p0y, p1x, p1y, ctx, rx, ry, /*rbuf,*/ rcx, rcy + BN_clear_free(ry); + BN_clear_free(rx); + //do not free rbuf - this is returned + BN_CTX_free(ctx); + BN_clear_free(p0x); + BN_clear_free(p0y); + BN_clear_free(p1x); + BN_clear_free(p1y); + EC_POINT_free(r); + EC_POINT_free(p1); + EC_POINT_free(p0); + EC_KEY_free(eckey); + + return scope.Close(rbuf->handle_); +} + Handle Key::VerifySignature(const Arguments& args) { diff --git a/src/eckey.h b/src/eckey.h index 8322ff123..1c2b9222e 100644 --- a/src/eckey.h +++ b/src/eckey.h @@ -86,6 +86,9 @@ public: static Handle FromDER(const Arguments& args); + static Handle + AddUncompressed(const Arguments& args); + static Handle VerifySignature(const Arguments& args); diff --git a/test/index.html b/test/index.html index 5351712c3..668712ab2 100644 --- a/test/index.html +++ b/test/index.html @@ -17,6 +17,7 @@ + diff --git a/test/test.BIP32.js b/test/test.BIP32.js new file mode 100644 index 000000000..6bf7c80f7 --- /dev/null +++ b/test/test.BIP32.js @@ -0,0 +1,297 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var bitcore = bitcore || require('../bitcore'); +var BIP32 = bitcore.BIP32; + +describe('BIP32', function() { + + //test vectors: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + var vector1_master = '000102030405060708090a0b0c0d0e0f'; + var vector1_m_public = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8' + var vector1_m_private = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'; + var vector1_m0h_public = 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw'; + var vector1_m0h_private = 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7'; + var vector1_m0h1_public = 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ'; + var vector1_m0h1_private = 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs'; + var vector1_m0h12h_public = 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5'; + var vector1_m0h12h_private = 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM'; + var vector1_m0h12h2_public = 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV'; + var vector1_m0h12h2_private = 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334'; + var vector1_m0h12h21000000000_public = 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy'; + var vector1_m0h12h21000000000_private = 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76'; + var vector2_master = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542'; + var vector2_m_public = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB'; + var vector2_m_private = 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U'; + var vector2_m0_public = 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH'; + var vector2_m0_private = 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt'; + var vector2_m02147483647h_public = 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a'; + var vector2_m02147483647h_private = 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9'; + var vector2_m02147483647h1_public = 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon'; + var vector2_m02147483647h1_private = 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef'; + var vector2_m02147483647h12147483646h_public = 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL'; + var vector2_m02147483647h12147483646h_private = 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc'; + var vector2_m02147483647h12147483646h2_public = 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt'; + var vector2_m02147483647h12147483646h2_private = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j'; + + + it('should initialize the class', function() { + should.exist(BIP32); + }); + + it('should create a mainnet bip32', function() { + var bip32 = new BIP32('mainnet'); + should.exist(bip32); + }); + + it('should create a testnet bip32', function() { + var bip32 = new BIP32('testnet'); + should.exist(bip32); + }); + + it('should initialize test vector 1 from the extended public key', function() { + var bip32 = new BIP32(vector1_m_public); + should.exist(bip32); + }); + + it('should initialize test vector 1 from the extended private key', function() { + var bip32 = new BIP32(vector1_m_private); + should.exist(bip32); + }); + + it('should get the extended public key from the extended private key for test vector 1', function() { + var bip32 = new BIP32(vector1_m_private); + bip32.extendedPublicKeyString().should.equal(vector1_m_public); + }); + + it("should get m/0' ext. private key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector1_m0h_private); + }); + + it("should get m/0' ext. public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector1_m0h_public); + }); + + it("should get m/0'/1 ext. private key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector1_m0h1_private); + }); + + it("should get m/0'/1 ext. public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector1_m0h1_public); + }); + + it("should get m/0'/1 ext. public key from m/0' public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'"); + var child_pub = new BIP32(child.extendedPublicKeyString()); + var child2 = child_pub.derive("m/1"); + should.exist(child2); + child2.extendedPublicKeyString().should.equal(vector1_m0h1_public); + }); + + it("should get m/0'/1/2h ext. private key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector1_m0h12h_private); + }); + + it("should get m/0'/1/2h ext. public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector1_m0h12h_public); + }); + + it("should get m/0'/1/2h/2 ext. private key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'/2"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector1_m0h12h2_private); + }); + + it("should get m/0'/1/2'/2 ext. public key from m/0'/1/2' public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'"); + var child_pub = new BIP32(child.extendedPublicKeyString()); + var child2 = child_pub.derive("m/2"); + should.exist(child2); + child2.extendedPublicKeyString().should.equal(vector1_m0h12h2_public); + }); + + it("should get m/0'/1/2h/2 ext. public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'/2"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector1_m0h12h2_public); + }); + + it("should get m/0'/1/2h/2/1000000000 ext. private key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'/2/1000000000"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector1_m0h12h21000000000_private); + }); + + it("should get m/0'/1/2h/2/1000000000 ext. public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'/2/1000000000"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector1_m0h12h21000000000_public); + }); + + it("should get m/0'/1/2'/2/1000000000 ext. public key from m/0'/1/2'/2 public key from test vector 1", function() { + var bip32 = new BIP32(vector1_m_private); + var child = bip32.derive("m/0'/1/2'/2"); + var child_pub = new BIP32(child.extendedPublicKeyString()); + var child2 = child_pub.derive("m/1000000000"); + should.exist(child2); + child2.extendedPublicKeyString().should.equal(vector1_m0h12h21000000000_public); + }); + + it('should initialize test vector 2 from the extended public key', function() { + var bip32 = new BIP32(vector2_m_public); + should.exist(bip32); + }); + + it('should initialize test vector 2 from the extended private key', function() { + var bip32 = new BIP32(vector2_m_private); + should.exist(bip32); + }); + + it('should get the extended public key from the extended private key for test vector 2', function() { + var bip32 = new BIP32(vector2_m_private); + bip32.extendedPublicKeyString().should.equal(vector2_m_public); + }); + + it("should get m/0 ext. private key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector2_m0_private); + }); + + it("should get m/0 ext. public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector2_m0_public); + }); + + it("should get m/0 ext. public key from m public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m"); + var child_pub = new BIP32(child.extendedPublicKeyString()); + var child2 = child_pub.derive("m/0"); + should.exist(child2); + child2.extendedPublicKeyString().should.equal(vector2_m0_public); + }); + + it("should get m/0/2147483647h ext. private key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector2_m02147483647h_private); + }); + + it("should get m/0/2147483647h ext. public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector2_m02147483647h_public); + }); + + it("should get m/0/2147483647h/1 ext. private key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector2_m02147483647h1_private); + }); + + it("should get m/0/2147483647h/1 ext. public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector2_m02147483647h1_public); + }); + + it("should get m/0/2147483647h/1 ext. public key from m/0/2147483647h public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'"); + var child_pub = new BIP32(child.extendedPublicKeyString()); + var child2 = child_pub.derive("m/1"); + should.exist(child2); + child2.extendedPublicKeyString().should.equal(vector2_m02147483647h1_public); + }); + + it("should get m/0/2147483647h/1/2147483646h ext. private key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1/2147483646'"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector2_m02147483647h12147483646h_private); + }); + + it("should get m/0/2147483647h/1/2147483646h ext. public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1/2147483646'"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector2_m02147483647h12147483646h_public); + }); + + it("should get m/0/2147483647h/1/2147483646h/2 ext. private key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1/2147483646'/2"); + should.exist(child); + child.extendedPrivateKeyString().should.equal(vector2_m02147483647h12147483646h2_private); + }); + + it("should get m/0/2147483647h/1/2147483646h/2 ext. public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1/2147483646'/2"); + should.exist(child); + child.extendedPublicKeyString().should.equal(vector2_m02147483647h12147483646h2_public); + }); + + it("should get m/0/2147483647h/1/2147483646h/2 ext. public key from m/0/2147483647h/2147483646h public key from test vector 2", function() { + var bip32 = new BIP32(vector2_m_private); + var child = bip32.derive("m/0/2147483647'/1/2147483646'"); + var child_pub = new BIP32(child.extendedPublicKeyString()); + var child2 = child_pub.derive("m/2"); + should.exist(child2); + child2.extendedPublicKeyString().should.equal(vector2_m02147483647h12147483646h2_public); + }); + + describe('#seed', function() { + + it('should initialize a new BIP32 correctly from test vector 1 seed', function() { + var hex = vector1_master; + var bip32 = BIP32.seed(hex, 'livenet'); + should.exist(bip32); + bip32.extendedPrivateKeyString().should.equal(vector1_m_private); + bip32.extendedPublicKeyString().should.equal(vector1_m_public); + }); + + it('should initialize a new BIP32 correctly from test vector 2 seed', function() { + var hex = vector2_master; + var bip32 = BIP32.seed(hex, 'livenet'); + should.exist(bip32); + bip32.extendedPrivateKeyString().should.equal(vector2_m_private); + bip32.extendedPublicKeyString().should.equal(vector2_m_public); + }); + + }); + +}); diff --git a/test/test.Key.js b/test/test.Key.js index 4918e9013..60bd587a7 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -1,15 +1,16 @@ 'use strict'; +var assert = require('assert'); var chai = chai || require('chai'); var bitcore = bitcore || require('../bitcore'); - +var coinUtil = coinUtil || require('../util/util'); var buffertools = require('buffertools'); var should = chai.should(); var Key = bitcore.Key; describe('Key', function() { - it('should initialze the main object', function() { + it('should initialize the main object', function() { should.exist(Key); }); it('should be able to create instance', function() { @@ -114,4 +115,52 @@ describe('Key', function() { ret.should.equal(false); }); + describe('#addUncompressed', function() { + it('should exist', function() { + should.exist(Key.addUncompressed); + }); + + it('should add two uncompressed public keys', function() { + var key1 = Key.generateSync(); + key1.compressed = false; + var key2 = Key.generateSync(); + key2.compressed = false; + var pubkey1 = key1.public; + var pubkey2 = key2.public; + var pubkey = Key.addUncompressed(pubkey1, pubkey2); + pubkey.length.should.equal(65); + }); + + it('a + b should equal b + a', function() { + var key1 = Key.generateSync(); + key1.compressed = false; + var key2 = Key.generateSync(); + key2.compressed = false; + var pubkey1 = key1.public; + var pubkey2 = key2.public; + var r1 = Key.addUncompressed(pubkey1, pubkey2); + var r2 = Key.addUncompressed(pubkey2, pubkey1); + r1.toString('hex').should.equal(r2.toString('hex')); + }); + + it('should be able to add these two public keys without error', function() { + var key1 = new Key(); + key1.private = coinUtil.sha256("first " + 3); + key1.compressed = false; + key1.regenerateSync(); + var key2 = new Key(); + key2.private = coinUtil.sha256("second " + 3); + key2.compressed = false; + key2.regenerateSync(); + var pubkey1 = key1.public; + var pubkey2 = key2.public; + var pubkey = Key.addUncompressed(pubkey1, pubkey2); + pubkey.length.should.equal(65); + var key = new Key(); + key.public = pubkey; + assert(key.public !== null); + }); + + }); + }); diff --git a/test/test.Point.js b/test/test.Point.js new file mode 100644 index 000000000..f7e3163a5 --- /dev/null +++ b/test/test.Point.js @@ -0,0 +1,69 @@ +'use strict'; + +var assert = require('assert'); +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); +var coinUtil = coinUtil || require('../util/util'); +var buffertools = require('buffertools'); +var bignum = require('bignum'); + +var should = chai.should(); + +var Point = bitcore.Point; +var Key = bitcore.Key; + +describe('Key', function() { + + it('should initialize the main object', function() { + should.exist(Point); + }); + + it('should be able to create instance', function() { + var p = new Point(); + should.exist(p); + }); + + it('should add these two points correctly', function() { + //these points are from one of the BIP32 test vectors + var axhex = "69b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; + var ayhex = "eeedc91342b3c8982c1e676435780fe5f9d62f3f692e8d1512485d77fab35997"; + var a = new Point(bignum.fromBuffer((new Buffer(axhex, 'hex')), {size: 32}), bignum.fromBuffer((new Buffer(ayhex, 'hex')), {size: 32})); + var bxhex = "5a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56"; + var byhex = "7f717885be239daadce76b568958305183ad616ff74ed4dc219a74c26d35f839"; + var b = new Point(bignum.fromBuffer((new Buffer(bxhex, 'hex')), {size: 32}), bignum.fromBuffer((new Buffer(byhex, 'hex')), {size: 32})); + var sxhex = "501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c"; + var syhex = "008794c1df8131b9ad1e1359965b3f3ee2feef0866be693729772be14be881ab"; + var s = new Point(bignum.fromBuffer((new Buffer(sxhex, 'hex')), {size: 32}), bignum.fromBuffer((new Buffer(syhex, 'hex')), {size: 32})); + var sum = Point.add(a, b); + s.x.toBuffer({size: 32}).toString('hex').should.equal(sum.x.toBuffer({size: 32}).toString('hex')); + s.y.toBuffer({size: 32}).toString('hex').should.equal(sum.y.toBuffer({size: 32}).toString('hex')); + }); + + it('should convert a Point into the public key of a Key', function() { + var axhex = "69b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; + var axbuf = new Buffer(axhex, 'hex'); + var ayhex = "eeedc91342b3c8982c1e676435780fe5f9d62f3f692e8d1512485d77fab35997"; + var aybuf = new Buffer(ayhex, 'hex'); + var a = new Point(bignum.fromBuffer(axbuf, {size: 32}), bignum.fromBuffer(aybuf, {size: 32})); + + var pubKeyBufCompressedHex = "0369b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; + var key = new Key(); + key.public = new Buffer(pubKeyBufCompressedHex, 'hex'); + + key.public.toString('hex').should.equal(a.toKey().public.toString('hex')); + }); + + it('should convert the public key of a Key into a Point', function() { + var axhex = "69b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; + var ayhex = "eeedc91342b3c8982c1e676435780fe5f9d62f3f692e8d1512485d77fab35997"; + + var pubKeyBufCompressedHex = "0369b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; + var key = new Key(); + key.public = new Buffer(pubKeyBufCompressedHex, 'hex'); + + var point = Point.fromKey(key); + point.x.toBuffer({size: 32}).toString('hex').should.equal(axhex); + point.y.toBuffer({size: 32}).toString('hex').should.equal(ayhex); + }); + +}); diff --git a/util/util.js b/util/util.js index 921cda5dc..dc7972ba5 100644 --- a/util/util.js +++ b/util/util.js @@ -3,6 +3,7 @@ var bignum = require('bignum'); var Binary = require('binary'); var Put = require('bufferput'); var buffertools = require('buffertools'); +var jssha = require('jssha'); var browser; var inBrowser = !process.versions; if (inBrowser) { @@ -13,7 +14,20 @@ if (inBrowser) { var sha256 = exports.sha256 = function(data) { return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary'); }; -var ripe160 = exports.ripe160 = function(data) { + +var sha512hmac = exports.sha512hmac = function (data, key) { + if (inBrowser) { + var j = new jssha(data.toString('hex'), 'HEX'); + var hash = j.getHMAC(key.toString('hex'), "HEX", "SHA-512", "HEX"); + hash = new Buffer(hash, 'hex'); + return hash; + }; + var hmac = crypto.createHmac('sha512', key); + var hash = hmac.update(data).digest(); + return hash; +}; + +var ripe160 = exports.ripe160 = function (data) { if (!Buffer.isBuffer(data)) { throw new Error('arg should be a buffer'); }