From 31a024a20ba9c72f039ebb06ebc5a03c4ef7d776 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 16 Mar 2014 16:16:03 -0700 Subject: [PATCH 01/19] bring formatting in line with bitcore standards --- BIP32.js | 556 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 281 insertions(+), 275 deletions(-) diff --git a/BIP32.js b/BIP32.js index 4b42e31..a220830 100644 --- a/BIP32.js +++ b/BIP32.js @@ -1,3 +1,6 @@ +var EncodedData = require('./util/EncodedData'); +var base58 = imports.base58 || require('base58-native').base58Check; + var BITCOIN_MAINNET_PUBLIC = 0x0488b21e; var BITCOIN_MAINNET_PRIVATE = 0x0488ade4; var BITCOIN_TESTNET_PUBLIC = 0x043587cf; @@ -12,338 +15,341 @@ var LITECOIN_TESTNET_PUBLIC = 0x0436f6e1; var LITECOIN_TESTNET_PRIVATE = 0x0436ef7d; 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); - - var hash = Crypto.SHA256( Crypto.SHA256( bytes, { asBytes: true } ), { asBytes: true } ); - - if( hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3] ) { - throw new Error("Invalid checksum"); - } + // 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 = Crypto.SHA256(Crypto.SHA256(bytes, {asBytes: true}), {asBytes: true}); + + 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.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; - } 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"); - } + // 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); - this.build_extended_public_key(); - this.build_extended_private_key(); + 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"); + } + + this.build_extended_public_key(); + this.build_extended_private_key(); } BIP32.prototype.build_extended_public_key = function() { - this.extended_public_key = []; - - 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.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); - - // Depth - this.extended_public_key.push(this.depth); - - // Parent fingerprint - this.extended_public_key = this.extended_public_key.concat(this.parent_fingerprint); - - // 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); - - // Chain code - this.extended_public_key = this.extended_public_key.concat(this.chain_code); - - // Public key - this.extended_public_key = this.extended_public_key.concat(this.eckey.pub.getEncoded(true)); + this.extended_public_key = []; + + 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.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); + + // Depth + this.extended_public_key.push(this.depth); + + // Parent fingerprint + this.extended_public_key = this.extended_public_key.concat(this.parent_fingerprint); + + // 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); + + // Chain code + this.extended_public_key = this.extended_public_key.concat(this.chain_code); + + // Public key + this.extended_public_key = this.extended_public_key.concat(this.eckey.pub.getEncoded(true)); } 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"); - } + 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.build_extended_private_key = function() { - if( !this.has_private_key ) return; - this.extended_private_key = []; + if (!this.has_private_key) return; + this.extended_private_key = []; - 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.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); - // Depth - this.extended_private_key.push(this.depth); + // Depth + this.extended_private_key.push(this.depth); - // Parent fingerprint - this.extended_private_key = this.extended_private_key.concat(this.parent_fingerprint); + // Parent fingerprint + this.extended_private_key = this.extended_private_key.concat(this.parent_fingerprint); - // 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.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); - // Chain code - this.extended_private_key = this.extended_private_key.concat(this.chain_code); + // Chain code + this.extended_private_key = this.extended_private_key.concat(this.chain_code); - // Private key - this.extended_private_key.push(0); - this.extended_private_key = this.extended_private_key.concat(this.eckey.priv.toByteArrayUnsigned()); + // Private key + this.extended_private_key.push(0); + this.extended_private_key = this.extended_private_key.concat(this.eckey.priv.toByteArrayUnsigned()); } 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"); - } + 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.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; - } + 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; + 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; + if( use_private ) + child_index += 0x80000000; - bip32 = bip32.derive_child(child_index); - } + bip32 = bip32.derive_child(child_index); + } - return bip32; + 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 ); - - 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 = [0].concat(this.eckey.priv.toByteArrayUnsigned()).concat(ib); - } else { - data = this.eckey.pub.getEncoded(true).concat(ib); - } + var ib = []; + ib.push((i >> 24) & 0xff); + ib.push((i >> 16) & 0xff); + ib.push((i >> 8) & 0xff); + ib.push(i & 0xff ); + + 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 = [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)); + 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()); + // 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 = new BIP32(); + ret.chain_code = ir; - ret.eckey = new Bitcoin.ECKey(k.toByteArrayUnsigned()); - ret.eckey.pub = ret.eckey.getPubPoint(); - ret.has_private_key = true; + ret.eckey = new Bitcoin.ECKey(k.toByteArrayUnsigned()); + ret.eckey.pub = ret.eckey.getPubPoint(); + ret.has_private_key = true; - } 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)); + } 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); + // 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 = new BIP32(); + ret.chain_code = ir; - ret.eckey = new Bitcoin.ECKey(); - ret.eckey.pub = k; - ret.has_private_key = false; - } + ret.eckey = new Bitcoin.ECKey(); + ret.eckey.pub = k; + ret.has_private_key = false; + } - ret.child_index = i; - ret.parent_fingerprint = this.eckey.pubKeyHash.slice(0,4); - ret.version = this.version; - ret.depth = this.depth + 1; + ret.child_index = i; + ret.parent_fingerprint = this.eckey.pubKeyHash.slice(0,4); + ret.version = this.version; + ret.depth = this.depth + 1; - ret.eckey.setCompressed(true); - ret.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(ret.eckey.pub.getEncoded(true)); + ret.eckey.setCompressed(true); + ret.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(ret.eckey.pub.getEncoded(true)); - ret.build_extended_public_key(); - ret.build_extended_private_key(); + ret.build_extended_public_key(); + ret.build_extended_private_key(); - return ret; + 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)); + 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)); } From c5c0ecc918ff8e550696c50c6eeeec9f5add33dc Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 16 Mar 2014 16:53:28 -0700 Subject: [PATCH 02/19] significant progress towards bitcore compatibility Cryptography updated to use bitcore methods rather than bitcoinjs. --- BIP32.js | 154 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 53 deletions(-) diff --git a/BIP32.js b/BIP32.js index a220830..4ea857a 100644 --- a/BIP32.js +++ b/BIP32.js @@ -1,5 +1,8 @@ -var EncodedData = require('./util/EncodedData'); -var base58 = imports.base58 || require('base58-native').base58Check; +//var base58 = imports.base58 || require('base58-native').base58Check; +var base58 = imports.base58 || require('base58-native').base58; +var coinUtil = imports.coinUtil || require('./util/util'); +var Key = imports.Key || require('./Key'); +var bignum = require('bignum'); var BITCOIN_MAINNET_PUBLIC = 0x0488b21e; var BITCOIN_MAINNET_PRIVATE = 0x0488ade4; @@ -13,6 +16,7 @@ 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 BIP32 = function(bytes) { // decode base58 @@ -23,7 +27,7 @@ var BIP32 = function(bytes) { var checksum = decoded.slice(78, 82); bytes = decoded.slice(0, 78); - var hash = Crypto.SHA256(Crypto.SHA256(bytes, {asBytes: true}), {asBytes: true}); + 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"); @@ -63,6 +67,7 @@ BIP32.prototype.init_from_bytes = function(bytes) { 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); @@ -71,12 +76,26 @@ BIP32.prototype.init_from_bytes = function(bytes) { 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"); } @@ -119,38 +138,39 @@ BIP32.prototype.build_extended_public_key = function() { } // 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); + 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.push(this.depth); + + this.extended_public_key = Buffer.concat([this.extended_public_key, new Buffer([this.depth])]); // Parent fingerprint - this.extended_public_key = this.extended_public_key.concat(this.parent_fingerprint); + this.extended_public_key = Buffer.concat([this.extended_public_key, this.parent_fingerprint]); // 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); + 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 = this.extended_public_key.concat(this.chain_code); + this.extended_public_key = Buffer.concat([this.extended_public_key, this.chain_code]); // Public key - this.extended_public_key = this.extended_public_key.concat(this.eckey.pub.getEncoded(true)); + this.extended_public_key = Buffer.concat([this.extended_public_key, this.eckey.pub]); } 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 hash = coinUtil.sha256(coinUtil.sha256(this.extended_public_key)); var checksum = hash.slice(0, 4); - var data = this.extended_public_key.concat(checksum); - return Bitcoin.Base58.encode(data); + var data = Buffer.concat([this.extended_public_key, checksum]); + return base58.encode(data); } else if (format === "hex") { - return Crypto.util.bytesToHex(this.extended_public_key); + return this.extended_public_key.toString('hex');; } else { throw new Error("bad format"); } @@ -158,44 +178,45 @@ BIP32.prototype.extended_public_key_string = function(format) { BIP32.prototype.build_extended_private_key = function() { if (!this.has_private_key) return; - this.extended_private_key = []; + this.extended_private_key = new Buffer(); 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); + 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.push(this.depth); + this.extended_private_key = Buffer.concat([this.extended_private_key, new Buffer([this.depth])]); // Parent fingerprint - this.extended_private_key = this.extended_private_key.concat(this.parent_fingerprint); + this.extended_private_key = Buffer.concat([this.extended_private_key, this.parent_fingerprint]); // 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); + 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 = this.extended_private_key.concat(this.chain_code); + this.extended_private_key = Buffer.concat([this.extended_private_key, this.chain_code]); // Private key this.extended_private_key.push(0); - this.extended_private_key = this.extended_private_key.concat(this.eckey.priv.toByteArrayUnsigned()); + 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 = Crypto.SHA256(Crypto.SHA256(this.extended_private_key, {asBytes: true}), {asBytes: true}); + var hash = coinUtil.sha256(coinUtil.sha256(this.extended_private_key)); 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); + 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"); } @@ -221,7 +242,7 @@ BIP32.prototype.derive = function(path) { 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 ) + if (use_private) child_index += 0x80000000; bip32 = bip32.derive_child(child_index); @@ -236,9 +257,10 @@ BIP32.prototype.derive_child = function(i) { 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 ecparams = getSECCurveByName("secp256k1"); var is_private = (this.version == BITCOIN_MAINNET_PRIVATE || @@ -256,52 +278,73 @@ BIP32.prototype.derive_child = function(i) { var data = null; if (use_private) { - data = [0].concat(this.eckey.priv.toByteArrayUnsigned()).concat(ib); + data = Buffer.concat([new Buffer([0]), this.eckey.private, ib]); } else { - data = this.eckey.pub.getEncoded(true).concat(ib); + 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 hmac = crypto.createHmac('sha512', this.chain_code); + var hash = hmac.update(data).digest(); + var il = bignum.fromBufer(hash.slice(0, 64), {size: 32}); + var ir = hash.slice(64, 128); // ki = IL + kpar (mod n). - var curve = ecparams.getCurve(); - var k = il.add(this.eckey.priv).mod(ecparams.getN()); + //TODO: Fix this somehow + var priv = bignum.fromBuffer(this.eckey.priv, {size: 32}); + var k = il.add(priv).mod(SECP256K1_N); ret = new BIP32(); - ret.chain_code = ir; + ret.chain_code = ir; - ret.eckey = new Bitcoin.ECKey(k.toByteArrayUnsigned()); - ret.eckey.pub = ret.eckey.getPubPoint(); + ret.eckey = new bitcore.Key(); + ret.eckey.private = k.toBuffer({size: 32}); + ret.eckey.regenerateSync(); ret.has_private_key = true; } else { - var data = this.eckey.pub.getEncoded(true).concat(ib); + /* + 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.sha512(this.chain_code); //TODO: replace with HMAC + var il = bignum.fromBuffer(hash.slice(0, 64).toString('hex'), 16); + var ir = hash.slice(64, 128); // Ki = (IL + kpar)*G = IL*G + Kpar - var k = ecparams.getG().multiply(il).add(this.eckey.pub); + //TODO: Fix this somehow + var key = new bitcore.Key(); + key.private = il; + key.regenerateSync(); + var k = key.public; + //TODO: now add this.eckey.pub + //var k = ecparams.getG().multiply(il).add(this.eckey.pub); ret = new BIP32(); - ret.chain_code = ir; + ret.chain_code = new Buffer(ir); - ret.eckey = new Bitcoin.ECKey(); + ret.eckey = new bitcore.key(); ret.eckey.pub = k; ret.has_private_key = false; } ret.child_index = i; - ret.parent_fingerprint = this.eckey.pubKeyHash.slice(0,4); + ret.parent_fingerprint = this.pubKeyHash.slice(0,4); ret.version = this.version; - ret.depth = this.depth + 1; + ret.depth = this.depth + 1; ret.eckey.setCompressed(true); - ret.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(ret.eckey.pub.getEncoded(true)); + ret.pubKeyHash = coinUtil.sha256ripe160(ret.eckey.pub.getEncoded(true)); ret.build_extended_public_key(); ret.build_extended_private_key(); @@ -326,7 +369,11 @@ 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"); @@ -353,3 +400,4 @@ function decompress_pubkey(key_bytes) { return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y)); } +*/ From 22b57feb7b18afd3f4ee86ede527a952ef12e526 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 22 Mar 2014 12:23:40 -0700 Subject: [PATCH 03/19] Get test vector 1 working in node --- BIP32.js | 56 +++++++++++----------- bitcore.js | 1 + test/test.BIP32.js | 115 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 27 deletions(-) create mode 100644 test/test.BIP32.js diff --git a/BIP32.js b/BIP32.js index 4ea857a..3e1705f 100644 --- a/BIP32.js +++ b/BIP32.js @@ -1,8 +1,9 @@ -//var base58 = imports.base58 || require('base58-native').base58Check; +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 = require('bignum'); +var bignum = imports.bignum || require('bignum'); +var crypto = require('crypto'); var BITCOIN_MAINNET_PUBLIC = 0x0488b21e; var BITCOIN_MAINNET_PRIVATE = 0x0488ade4; @@ -16,7 +17,8 @@ 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_n = new bignum("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); +var secp256k1_G = new bignum("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); //x coordinate var BIP32 = function(bytes) { // decode base58 @@ -105,7 +107,7 @@ BIP32.prototype.init_from_bytes = function(bytes) { } BIP32.prototype.build_extended_public_key = function() { - this.extended_public_key = []; + this.extended_public_key = new Buffer([]); var v = null; switch(this.version) { @@ -160,7 +162,7 @@ BIP32.prototype.build_extended_public_key = function() { 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.pub]); + this.extended_public_key = Buffer.concat([this.extended_public_key, this.eckey.public]); } BIP32.prototype.extended_public_key_string = function(format) { @@ -178,7 +180,7 @@ BIP32.prototype.extended_public_key_string = function(format) { BIP32.prototype.build_extended_private_key = function() { if (!this.has_private_key) return; - this.extended_private_key = new Buffer(); + this.extended_private_key = new Buffer([]); var v = this.version; @@ -204,7 +206,6 @@ BIP32.prototype.build_extended_private_key = function() { this.extended_private_key = Buffer.concat([this.extended_private_key, this.chain_code]); // Private key - this.extended_private_key.push(0); 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]); } @@ -256,7 +257,7 @@ BIP32.prototype.derive_child = function(i) { ib.push((i >> 24) & 0xff); ib.push((i >> 16) & 0xff); ib.push((i >> 8) & 0xff); - ib.push(i & 0xff ); + ib.push(i & 0xff); ib = new Buffer(ib); var use_private = (i & 0x80000000) != 0; @@ -291,18 +292,17 @@ BIP32.prototype.derive_child = function(i) { */ var hmac = crypto.createHmac('sha512', this.chain_code); var hash = hmac.update(data).digest(); - var il = bignum.fromBufer(hash.slice(0, 64), {size: 32}); - var ir = hash.slice(64, 128); + var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); + var ir = hash.slice(32, 64); // ki = IL + kpar (mod n). - //TODO: Fix this somehow - var priv = bignum.fromBuffer(this.eckey.priv, {size: 32}); - var k = il.add(priv).mod(SECP256K1_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 bitcore.Key(); + ret.eckey = new Key(); ret.eckey.private = k.toBuffer({size: 32}); ret.eckey.regenerateSync(); ret.has_private_key = true; @@ -317,24 +317,24 @@ BIP32.prototype.derive_child = function(i) { var ir = Crypto.util.hexToBytes(hash.slice(64, 128)); */ var data = Buffer.concat([this.eckey.public, ib]); - var hash = coinUtil.sha512(this.chain_code); //TODO: replace with HMAC - var il = bignum.fromBuffer(hash.slice(0, 64).toString('hex'), 16); - var ir = hash.slice(64, 128); + var hmac = crypto.createHmac('sha512', this.chain_code); + var hash = hmac.update(data).digest(); + var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); + var ir = hash.slice(32, 64); // Ki = (IL + kpar)*G = IL*G + Kpar - //TODO: Fix this somehow - var key = new bitcore.Key(); - key.private = il; - key.regenerateSync(); - var k = key.public; - //TODO: now add this.eckey.pub //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 bitcore.key(); - ret.eckey.pub = k; + ret.eckey = new Key(); + ret.eckey.public = kbuf; ret.has_private_key = false; } @@ -343,8 +343,8 @@ BIP32.prototype.derive_child = function(i) { ret.version = this.version; ret.depth = this.depth + 1; - ret.eckey.setCompressed(true); - ret.pubKeyHash = coinUtil.sha256ripe160(ret.eckey.pub.getEncoded(true)); + ret.eckey.compressed = true; + ret.pubKeyHash = coinUtil.sha256ripe160(ret.eckey.public); ret.build_extended_public_key(); ret.build_extended_private_key(); @@ -401,3 +401,5 @@ function decompress_pubkey(key_bytes) { return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y)); } */ + +module.exports = require('soop')(BIP32); diff --git a/bitcore.js b/bitcore.js index 79e8ffb..548b3d9 100644 --- a/bitcore.js +++ b/bitcore.js @@ -23,6 +23,7 @@ requireWhenAccessed('EncodedData', './util/EncodedData'); requireWhenAccessed('VersionedData', './util/VersionedData'); requireWhenAccessed('BinaryParser', './util/BinaryParser'); requireWhenAccessed('Address', './Address'); +requireWhenAccessed('BIP32', './BIP32'); requireWhenAccessed('Opcode', './Opcode'); requireWhenAccessed('Script', './Script'); requireWhenAccessed('Transaction', './Transaction'); diff --git a/test/test.BIP32.js b/test/test.BIP32.js new file mode 100644 index 0000000..63157d2 --- /dev/null +++ b/test/test.BIP32.js @@ -0,0 +1,115 @@ +'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'; + + + it('should initialize the class', function() { + 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', function() { + var bip32 = new BIP32(vector1_m_private); + bip32.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().should.equal(vector1_m0h12h2_private); + }); + + 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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().should.equal(vector1_m0h12h21000000000_public); + }); + +}); From ba59d97a73aa7850201bbcdc183bb9a3bcc05bf3 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 22 Mar 2014 13:49:58 -0700 Subject: [PATCH 04/19] make things work in the browser by fixing sha512 ...had to use jsSHA package to do SHA512 in the browser. Unfortunately it is quite slow compared to node. --- BIP32.js | 6 ++---- browser/build.js | 1 + package.json | 1 + test/index.html | 1 + util/util.js | 16 +++++++++++++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/BIP32.js b/BIP32.js index 3e1705f..f333b41 100644 --- a/BIP32.js +++ b/BIP32.js @@ -290,8 +290,7 @@ BIP32.prototype.derive_child = function(i) { var il = new BigInteger(hash.slice(0, 64), 16); var ir = Crypto.util.hexToBytes(hash.slice(64, 128)); */ - var hmac = crypto.createHmac('sha512', this.chain_code); - var hash = hmac.update(data).digest(); + var hash = coinUtil.sha512hmac(data, this.chain_code); var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); var ir = hash.slice(32, 64); @@ -317,8 +316,7 @@ BIP32.prototype.derive_child = function(i) { var ir = Crypto.util.hexToBytes(hash.slice(64, 128)); */ var data = Buffer.concat([this.eckey.public, ib]); - var hmac = crypto.createHmac('sha512', this.chain_code); - var hash = hmac.update(data).digest(); + var hash = coinUtil.sha512hmac(data, this.chain_code); var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); var ir = hash.slice(32, 64); diff --git a/browser/build.js b/browser/build.js index 736ae3d..24efa9b 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', diff --git a/package.json b/package.json index eb5a3c5..61d4857 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/test/index.html b/test/index.html index bbd0d61..510190e 100644 --- a/test/index.html +++ b/test/index.html @@ -17,6 +17,7 @@ + diff --git a/util/util.js b/util/util.js index 921cda5..dc7972b 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'); } From 47fe12ea19e1d051095158e62b50b9a4ed7d466b Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 22 Mar 2014 14:25:11 -0700 Subject: [PATCH 05/19] all vector 2 tests work --- test/test.BIP32.js | 100 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/test/test.BIP32.js b/test/test.BIP32.js index 63157d2..132cd53 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -21,6 +21,19 @@ describe('BIP32', function() { 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() { @@ -37,7 +50,7 @@ describe('BIP32', function() { should.exist(bip32); }); - it('should get the extended public key from the extended private key', function() { + 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.extended_public_key_string().should.equal(vector1_m_public); }); @@ -112,4 +125,89 @@ describe('BIP32', function() { child.extended_public_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().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.extended_private_key_string().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.extended_public_key_string().should.equal(vector2_m02147483647h12147483646h2_public); + }); + }); From 0677ae46f8b95500d84bdb659c1a3a5ecdb346d7 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 22 Mar 2014 14:39:07 -0700 Subject: [PATCH 06/19] remove obsolete commented-out code --- BIP32.js | 74 +++----------------------------------------------------- 1 file changed, 3 insertions(+), 71 deletions(-) diff --git a/BIP32.js b/BIP32.js index f333b41..64d6021 100644 --- a/BIP32.js +++ b/BIP32.js @@ -69,34 +69,16 @@ BIP32.prototype.init_from_bytes = function(bytes) { 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.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); 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.eckey.public = key_bytes; + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); this.has_private_key = false; } else { throw new Error("Invalid key"); @@ -146,7 +128,6 @@ BIP32.prototype.build_extended_public_key = function() { 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 @@ -261,7 +242,6 @@ BIP32.prototype.derive_child = function(i) { ib = new Buffer(ib); var use_private = (i & 0x80000000) != 0; - //var ecparams = getSECCurveByName("secp256k1"); var is_private = (this.version == BITCOIN_MAINNET_PRIVATE || @@ -284,12 +264,6 @@ BIP32.prototype.derive_child = function(i) { 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); @@ -307,21 +281,12 @@ BIP32.prototype.derive_child = function(i) { 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); @@ -367,37 +332,4 @@ 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); From b7550fc8625f78383290d27d0a50a2826ade21a1 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 22 Mar 2014 15:03:22 -0700 Subject: [PATCH 07/19] add convenience constructor for making new bip32s Added the ability to create a new master bip32 with new private key and chain code. The way this works is like this: var bip32 = new BIP32('mainnet'); or: var bip32 = new BIP32('testnet'); --- BIP32.js | 18 ++++++++++++++++++ test/test.BIP32.js | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/BIP32.js b/BIP32.js index 64d6021..3e0d5e5 100644 --- a/BIP32.js +++ b/BIP32.js @@ -21,6 +21,24 @@ var secp256k1_n = new bignum("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBF var secp256k1_G = new bignum("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); //x coordinate var BIP32 = function(bytes) { + if (bytes == 'mainnet' || bytes == 'livenet') + this.version = BITCOIN_MAINNET_PRIVATE; + else if (bytes == 'testnet') + this.version = BITCOIN_TESTNET_PRIVATE; + + if (bytes == 'mainnet' || bytes == 'livenet' || bytes == 'testnet') { + this.depth = 0x00; + this.parent_fingerprint = new Buffer([0, 0, 0, 0]); + this.child_index = new Buffer([0, 0, 0, 0]); + this.chain_code = Key.generateSync().private; + this.eckey = Key.generateSync(); + this.has_private_key = true; + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.build_extended_public_key(); + this.build_extended_private_key(); + return; + } + // decode base58 if (typeof bytes === "string") { var decoded = base58.decode(bytes); diff --git a/test/test.BIP32.js b/test/test.BIP32.js index 132cd53..95d7ae0 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -40,6 +40,16 @@ describe('BIP32', 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); From 78a753a2d43c5c6e7352e375c70a4748b1291230 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 22 Mar 2014 16:31:38 -0700 Subject: [PATCH 08/19] move version constants to networks.js ...with all the other network-specific constants. --- BIP32.js | 69 ++++++++++++----------------------------------------- networks.js | 4 ++++ 2 files changed, 19 insertions(+), 54 deletions(-) diff --git a/BIP32.js b/BIP32.js index 3e0d5e5..6bfc60d 100644 --- a/BIP32.js +++ b/BIP32.js @@ -4,27 +4,16 @@ var coinUtil = imports.coinUtil || require('./util/util'); var Key = imports.Key || require('./Key'); var bignum = imports.bignum || require('bignum'); var crypto = require('crypto'); +var networks = require('./networks'); -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) { if (bytes == 'mainnet' || bytes == 'livenet') - this.version = BITCOIN_MAINNET_PRIVATE; + this.version = networks['livenet'].bip32private; else if (bytes == 'testnet') - this.version = BITCOIN_TESTNET_PRIVATE; + this.version = networks['testnet'].bip32private; if (bytes == 'mainnet' || bytes == 'livenet' || bytes == 'testnet') { this.depth = 0x00; @@ -71,20 +60,12 @@ BIP32.prototype.init_from_bytes = function(bytes) { 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 ); + (this.version == networks['livenet'].bip32private || + this.version == networks['testnet'].bip32private ); 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 ); + (this.version == networks['livenet'].bip32public || + this.version == networks['testnet'].bip32public ); if (is_private && key_bytes[0] == 0) { this.eckey = new Key(); @@ -111,29 +92,13 @@ BIP32.prototype.build_extended_public_key = function() { var v = null; switch(this.version) { - case BITCOIN_MAINNET_PUBLIC: - case BITCOIN_MAINNET_PRIVATE: - v = BITCOIN_MAINNET_PUBLIC; + case networks['livenet'].bip32public: + case networks['livenet'].bip32private: + v = networks['livenet'].bip32public; 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; + case networks['testnet'].bip32public: + case networks['testnet'].bip32private: + v = networks['testnet'].bip32public; break; default: throw new Error("Unknown version"); @@ -262,12 +227,8 @@ BIP32.prototype.derive_child = function(i) { var use_private = (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); + (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"); diff --git a/networks.js b/networks.js index 07e10f3..2f8aa21 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, }; From d11361be9e2b0417f177ac52043f3036feba7ba0 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 23 Mar 2014 10:35:28 -0700 Subject: [PATCH 09/19] expose group operation BIP32 needs to be able to add two points on the secp256k1 curve. This functionality was not already being exposed from OpenSSL in bitcore. I have added an "addUncompressed" function to the Key class which takes in two points in uncompressed form, adds them, and returns the result. This is necessary for BIP32. --- src/eckey.cc | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ src/eckey.h | 3 ++ test/test.Key.js | 18 +++++++++- 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/eckey.cc b/src/eckey.cc index ed63260..d8831b2 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,91 @@ 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; + unsigned char *rcx, *rcy; + + 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); + rcx = (unsigned char *)malloc(32); + rcy = (unsigned char *)malloc(32); + BN_bn2bin(rx, rcx); + BN_bn2bin(ry, rcy); + memcpy(&(((unsigned char *)Buffer::Data(rbuf))[1]), rcx, 32); + memcpy(&(((unsigned char *)Buffer::Data(rbuf))[33]), rcy, 32); + ((unsigned char *)Buffer::Data(rbuf))[0] = 0x04; + + //free: eckey, p0, p1, r, p0x, p0y, p1x, p1y, ctx, rx, ry, /*rbuf,*/ rcx, rcy + free(rcy); //TODO: also clear + free(rcx); //TODO: also clear + 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 8322ff1..1c2b922 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/test.Key.js b/test/test.Key.js index 4918e90..23cbc95 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -9,7 +9,7 @@ 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 +114,20 @@ 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); + }); + }); + }); From a686e63b0bb137eeee5687ea3e22315a92da22c5 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 23 Mar 2014 15:11:32 -0700 Subject: [PATCH 10/19] fix issue by outputing proper pubkey format The way I was outputting the pubkeys would be incorrect if the first byte of one of the coordinates was 0, since it would print the first non-zero byte first. The solution was to use the standard openssl function that outputs a public key to oct. --- src/eckey.cc | 14 ++----------- test/test.Key.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/eckey.cc b/src/eckey.cc index d8831b2..9f0886e 100644 --- a/src/eckey.cc +++ b/src/eckey.cc @@ -435,12 +435,10 @@ Key::AddUncompressed(const Arguments& args) 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; - unsigned char *rcx, *rcy; p0 = EC_POINT_new(group); p1 = EC_POINT_new(group); @@ -461,19 +459,11 @@ Key::AddUncompressed(const Arguments& args) rx = BN_new(); ry = BN_new(); EC_POINT_get_affine_coordinates_GFp(group, r, rx, ry, ctx); - + rbuf = Buffer::New(65); - rcx = (unsigned char *)malloc(32); - rcy = (unsigned char *)malloc(32); - BN_bn2bin(rx, rcx); - BN_bn2bin(ry, rcy); - memcpy(&(((unsigned char *)Buffer::Data(rbuf))[1]), rcx, 32); - memcpy(&(((unsigned char *)Buffer::Data(rbuf))[33]), rcy, 32); - ((unsigned char *)Buffer::Data(rbuf))[0] = 0x04; + 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 - free(rcy); //TODO: also clear - free(rcx); //TODO: also clear BN_clear_free(ry); BN_clear_free(rx); //do not free rbuf - this is returned diff --git a/test/test.Key.js b/test/test.Key.js index 23cbc95..6daeae6 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -1,8 +1,9 @@ '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(); @@ -118,6 +119,7 @@ describe('Key', function() { it('should exist', function() { should.exist(Key.addUncompressed); }); + it('should add two uncompressed public keys', function() { var key1 = Key.generateSync(); key1.compressed = false; @@ -128,6 +130,56 @@ describe('Key', function() { 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); + }); + + it('should be able to add many public keys without error', function() { + for (var i = 0; i <= 1000; i++) { + var key1 = new Key(); + key1.private = coinUtil.sha256("first " + i); + key1.compressed = false; + key1.regenerateSync(); + var key2 = new Key(); + key2.private = coinUtil.sha256("second " + i); + 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); + }; + }); }); }); From a4393c0657667e6e647fe3068724cb7d52ad08bc Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 23 Mar 2014 15:12:52 -0700 Subject: [PATCH 11/19] update BIP32 to be able to derive pubkeys ...using the new addCompressed interface in Key.js --- BIP32.js | 36 ++++++++++++++++++++++++++++-------- test/test.BIP32.js | 9 +++++++++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/BIP32.js b/BIP32.js index 6bfc60d..dac4c33 100644 --- a/BIP32.js +++ b/BIP32.js @@ -7,7 +7,7 @@ var crypto = require('crypto'); var networks = require('./networks'); var secp256k1_n = new bignum("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); -var secp256k1_G = new bignum("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); //x coordinate +var secp256k1_Gx = new bignum("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); var BIP32 = function(bytes) { if (bytes == 'mainnet' || bytes == 'livenet') @@ -266,17 +266,37 @@ BIP32.prototype.derive_child = function(i) { var ir = hash.slice(32, 64); // Ki = (IL + kpar)*G = IL*G + Kpar - var pub = new bignum(this.eckey.public, {size: 32}); - var k = secp256k1_G.mul(il).add(pub); + var key = new Key(); + key.private = il.toBuffer({size: 32}); + key.regenerateSync(); + key.compressed = false; + var oldkey = new Key(); + oldkey.public = this.eckey.public; + oldkey.compressed = false; + var newpub = Key.addUncompressed(key.public, oldkey.public); + + var eckey = new Key(); + eckey.compressed = false; + eckey.public = newpub; + if (eckey.public === null) { + console.log('invalid public key'); + return this.derive_child(i+1); + } + eckey.compressed = true; - //compressed pubkey must start with 0x02 just like compressed G - var kbuf = Buffer.concat([new Buffer(0x02), k.toBuffer({size: 32})]); +/* + if (k.gt(secp256k1_n)) + return this.derive_child(i+1); +*/ ret = new BIP32(); - ret.chain_code = new Buffer(ir); + ret.chain_code = new Buffer(ir); - ret.eckey = new Key(); - ret.eckey.public = kbuf; + var eckey = new Key(); + eckey.compressed = false; + eckey.public = newpub; + eckey.compressed = true; + ret.eckey = eckey; ret.has_private_key = false; } diff --git a/test/test.BIP32.js b/test/test.BIP32.js index 95d7ae0..8ed1148 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -93,6 +93,15 @@ describe('BIP32', function() { child.extended_public_key_string().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.extended_public_key_string()); + var child2 = child_pub.derive("m/1"); + should.exist(child2); + child2.extended_public_key_string().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'"); From 7904efe147b637f6c7018c4c76aab4172f134eec Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 23 Mar 2014 15:30:31 -0700 Subject: [PATCH 12/19] remove redundant and slow test --- test/test.Key.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/test.Key.js b/test/test.Key.js index 6daeae6..60bd587 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -161,25 +161,6 @@ describe('Key', function() { assert(key.public !== null); }); - it('should be able to add many public keys without error', function() { - for (var i = 0; i <= 1000; i++) { - var key1 = new Key(); - key1.private = coinUtil.sha256("first " + i); - key1.compressed = false; - key1.regenerateSync(); - var key2 = new Key(); - key2.private = coinUtil.sha256("second " + i); - 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); - }; - }); }); }); From 5c21866fe263448b501528a29b1c74725e004835 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sun, 23 Mar 2014 15:32:52 -0700 Subject: [PATCH 13/19] remove commented-out code --- BIP32.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/BIP32.js b/BIP32.js index dac4c33..6efb73a 100644 --- a/BIP32.js +++ b/BIP32.js @@ -284,11 +284,6 @@ BIP32.prototype.derive_child = function(i) { } eckey.compressed = true; -/* - if (k.gt(secp256k1_n)) - return this.derive_child(i+1); -*/ - ret = new BIP32(); ret.chain_code = new Buffer(ir); From 91181de234a2403474eab838b2f6b2b31015e749 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 27 Mar 2014 18:59:47 -0400 Subject: [PATCH 14/19] add new Point class and update BIP32 to use it The Point class will ultimately be an all purpose tool for dealing with points on the secp256k1 tool. For now, it just needs to have the ability to add two points together, and work both in node and the browser, so that it can be used for BIP32. --- BIP32.js | 24 ++++++------------ Point.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 Point.js diff --git a/BIP32.js b/BIP32.js index 6efb73a..878a1ac 100644 --- a/BIP32.js +++ b/BIP32.js @@ -2,6 +2,7 @@ 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'); @@ -266,31 +267,20 @@ BIP32.prototype.derive_child = function(i) { var ir = hash.slice(32, 64); // Ki = (IL + kpar)*G = IL*G + Kpar - var key = new Key(); - key.private = il.toBuffer({size: 32}); - key.regenerateSync(); - key.compressed = false; + 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; - oldkey.compressed = false; - var newpub = Key.addUncompressed(key.public, oldkey.public); - - var eckey = new Key(); - eckey.compressed = false; - eckey.public = newpub; - if (eckey.public === null) { - console.log('invalid public key'); - return this.derive_child(i+1); - } - eckey.compressed = true; + var Kpar = Point.fromKey(oldkey); + var newpub = Point.add(ilG, Kpar).toKey().public; ret = new BIP32(); ret.chain_code = new Buffer(ir); var eckey = new Key(); - eckey.compressed = false; eckey.public = newpub; - eckey.compressed = true; ret.eckey = eckey; ret.has_private_key = false; } diff --git a/Point.js b/Point.js new file mode 100644 index 0000000..b028e37 --- /dev/null +++ b/Point.js @@ -0,0 +1,75 @@ +var imports = require('soop').imports(); +var Key = imports.Key || require('./Key'); +var bignum = imports.bignum || require('bignum'); + +//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 { + } + +}; + +//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 { + } +}; + +//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 is probably wrong + key.compressed = true; + return key; + } + + //browser + else { + } +}; + +module.exports = require('soop')(Point); From 63ce079f2b1b419f934c94126a39d7d2370bc1a7 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 27 Mar 2014 19:19:29 -0400 Subject: [PATCH 15/19] change from under_scores to camelCase camelCase is the bitcore way --- BIP32.js | 162 ++++++++++++++++++++++----------------------- test/test.BIP32.js | 48 +++++++------- 2 files changed, 105 insertions(+), 105 deletions(-) diff --git a/BIP32.js b/BIP32.js index 878a1ac..0c774d1 100644 --- a/BIP32.js +++ b/BIP32.js @@ -18,14 +18,14 @@ var BIP32 = function(bytes) { if (bytes == 'mainnet' || bytes == 'livenet' || bytes == 'testnet') { this.depth = 0x00; - this.parent_fingerprint = new Buffer([0, 0, 0, 0]); - this.child_index = new Buffer([0, 0, 0, 0]); - this.chain_code = Key.generateSync().private; + 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.has_private_key = true; + this.hasPrivateKey = true; this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); - this.build_extended_public_key(); - this.build_extended_private_key(); + this.buildExtendedPublicKey(); + this.buildExtendedPrivateKey(); return; } @@ -45,51 +45,51 @@ var BIP32 = function(bytes) { } if (bytes !== undefined) - this.init_from_bytes(bytes); + this.initFromBytes(bytes); } -BIP32.prototype.init_from_bytes = function(bytes) { +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.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); + this.parentFingerprint = bytes.slice(5, 9); + this.childIndex = u32(bytes.slice(9, 13)); + this.chainCode = bytes.slice(13, 45); - var key_bytes = bytes.slice(45, 78); + var keyBytes = bytes.slice(45, 78); - var is_private = + var isPrivate = (this.version == networks['livenet'].bip32private || this.version == networks['testnet'].bip32private ); - var is_public = + var isPublic = (this.version == networks['livenet'].bip32public || this.version == networks['testnet'].bip32public ); - if (is_private && key_bytes[0] == 0) { + if (isPrivate && keyBytes[0] == 0) { this.eckey = new Key(); - this.eckey.private = key_bytes.slice(1, 33); + this.eckey.private = keyBytes.slice(1, 33); this.eckey.compressed = true; this.eckey.regenerateSync(); this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); - this.has_private_key = true; - } else if (is_public && (key_bytes[0] == 0x02 || key_bytes[0] == 0x03)) { + this.hasPrivateKey = true; + } else if (isPublic && (keyBytes[0] == 0x02 || keyBytes[0] == 0x03)) { this.eckey = new Key(); - this.eckey.public = key_bytes; + this.eckey.public = keyBytes; this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); - this.has_private_key = false; + this.hasPrivateKey = false; } else { throw new Error("Invalid key"); } - this.build_extended_public_key(); - this.build_extended_private_key(); + this.buildExtendedPublicKey(); + this.buildExtendedPrivateKey(); } -BIP32.prototype.build_extended_public_key = function() { - this.extended_public_key = new Buffer([]); +BIP32.prototype.buildExtendedPublicKey = function() { + this.extendedPublicKey = new Buffer([]); var v = null; switch(this.version) { @@ -106,83 +106,83 @@ BIP32.prototype.build_extended_public_key = function() { } // 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])]); + 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 = Buffer.concat([this.extended_public_key, new Buffer([this.depth])]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, new Buffer([this.depth])]); // Parent fingerprint - this.extended_public_key = Buffer.concat([this.extended_public_key, this.parent_fingerprint]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, this.parentFingerprint]); // 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])]); + 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 = Buffer.concat([this.extended_public_key, this.chain_code]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, this.chainCode]); // Public key - this.extended_public_key = Buffer.concat([this.extended_public_key, this.eckey.public]); + this.extendedPublicKey = Buffer.concat([this.extendedPublicKey, this.eckey.public]); } -BIP32.prototype.extended_public_key_string = function(format) { +BIP32.prototype.extendedPublicKeyString = function(format) { if (format === undefined || format === "base58") { - var hash = coinUtil.sha256(coinUtil.sha256(this.extended_public_key)); + var hash = coinUtil.sha256(coinUtil.sha256(this.extendedPublicKey)); var checksum = hash.slice(0, 4); - var data = Buffer.concat([this.extended_public_key, checksum]); + var data = Buffer.concat([this.extendedPublicKey, checksum]); return base58.encode(data); } else if (format === "hex") { - return this.extended_public_key.toString('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 = new Buffer([]); +BIP32.prototype.buildExtendedPrivateKey = function() { + if (!this.hasPrivateKey) return; + this.extendedPrivateKey = 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])]); + 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 = Buffer.concat([this.extended_private_key, new Buffer([this.depth])]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, new Buffer([this.depth])]); // Parent fingerprint - this.extended_private_key = Buffer.concat([this.extended_private_key, this.parent_fingerprint]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, this.parentFingerprint]); // 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])]); + 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 = Buffer.concat([this.extended_private_key, this.chain_code]); + this.extendedPrivateKey = Buffer.concat([this.extendedPrivateKey, this.chainCode]); // 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]); + 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) { +BIP32.prototype.extendedPrivateKeyString = function(format) { if (format === undefined || format === "base58") { - var hash = coinUtil.sha256(coinUtil.sha256(this.extended_private_key)); + var hash = coinUtil.sha256(coinUtil.sha256(this.extendedPrivateKey)); var checksum = hash.slice(0, 4); - var data = Buffer.concat([this.extended_private_key, checksum]); + var data = Buffer.concat([this.extendedPrivateKey, checksum]); return base58.encode(data); } else if (format === "hex") { - return this.extended_private_key.toString('hex'); + return this.extendedPrivateKey.toString('hex'); } else { throw new Error("bad format"); } @@ -205,19 +205,19 @@ BIP32.prototype.derive = function(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; + var usePrivate = (c.length > 1) && (c[c.length-1] == '\''); + var childIndex = parseInt(usePrivate ? c.slice(0, c.length - 1) : c) & 0x7fffffff; - if (use_private) - child_index += 0x80000000; + if (usePrivate) + childIndex += 0x80000000; - bip32 = bip32.derive_child(child_index); + bip32 = bip32.deriveChild(childIndex); } return bip32; } -BIP32.prototype.derive_child = function(i) { +BIP32.prototype.deriveChild = function(i) { var ib = []; ib.push((i >> 24) & 0xff); ib.push((i >> 16) & 0xff); @@ -225,26 +225,26 @@ BIP32.prototype.derive_child = function(i) { ib.push(i & 0xff); ib = new Buffer(ib); - var use_private = (i & 0x80000000) != 0; + var usePrivate = (i & 0x80000000) != 0; - var is_private = + var isPrivate = (this.version == networks['livenet'].bip32private || this.version == networks['testnet'].bip32private ); - if (use_private && (!this.has_private_key || !is_private)) + if (usePrivate && (!this.hasPrivateKey || !isPrivate)) throw new Error("Cannot do private key derivation without private key"); var ret = null; - if (this.has_private_key) { + if (this.hasPrivateKey) { var data = null; - if (use_private) { + if (usePrivate) { data = Buffer.concat([new Buffer([0]), this.eckey.private, ib]); } else { data = Buffer.concat([this.eckey.public, ib]); } - var hash = coinUtil.sha512hmac(data, this.chain_code); + var hash = coinUtil.sha512hmac(data, this.chainCode); var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); var ir = hash.slice(32, 64); @@ -253,16 +253,16 @@ BIP32.prototype.derive_child = function(i) { var k = il.add(priv).mod(secp256k1_n); ret = new BIP32(); - ret.chain_code = ir; + ret.chainCode = ir; ret.eckey = new Key(); ret.eckey.private = k.toBuffer({size: 32}); ret.eckey.regenerateSync(); - ret.has_private_key = true; + ret.hasPrivateKey = true; } else { var data = Buffer.concat([this.eckey.public, ib]); - var hash = coinUtil.sha512hmac(data, this.chain_code); + var hash = coinUtil.sha512hmac(data, this.chainCode); var il = bignum.fromBuffer(hash.slice(0, 32), {size: 32}); var ir = hash.slice(32, 64); @@ -277,24 +277,24 @@ BIP32.prototype.derive_child = function(i) { var newpub = Point.add(ilG, Kpar).toKey().public; ret = new BIP32(); - ret.chain_code = new Buffer(ir); + ret.chainCode = new Buffer(ir); var eckey = new Key(); eckey.public = newpub; ret.eckey = eckey; - ret.has_private_key = false; + ret.hasPrivateKey = false; } - ret.child_index = i; - ret.parent_fingerprint = this.pubKeyHash.slice(0,4); + 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.build_extended_public_key(); - ret.build_extended_private_key(); + ret.buildExtendedPublicKey(); + ret.buildExtendedPrivateKey(); return ret; } diff --git a/test/test.BIP32.js b/test/test.BIP32.js index 8ed1148..94188b4 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -62,86 +62,86 @@ describe('BIP32', function() { 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.extended_public_key_string().should.equal(vector1_m_public); + 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.extended_private_key_string().should.equal(vector1_m0h_private); + 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.extended_public_key_string().should.equal(vector1_m0h_public); + 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.extended_private_key_string().should.equal(vector1_m0h1_private); + 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.extended_public_key_string().should.equal(vector1_m0h1_public); + 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.extended_public_key_string()); + var child_pub = new BIP32(child.extendedPublicKeyString()); var child2 = child_pub.derive("m/1"); should.exist(child2); - child2.extended_public_key_string().should.equal(vector1_m0h1_public); + 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.extended_private_key_string().should.equal(vector1_m0h12h_private); + 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.extended_public_key_string().should.equal(vector1_m0h12h_public); + 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.extended_private_key_string().should.equal(vector1_m0h12h2_private); + child.extendedPrivateKeyString().should.equal(vector1_m0h12h2_private); }); 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.extended_public_key_string().should.equal(vector1_m0h12h2_public); + 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.extended_private_key_string().should.equal(vector1_m0h12h21000000000_private); + 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.extended_public_key_string().should.equal(vector1_m0h12h21000000000_public); + child.extendedPublicKeyString().should.equal(vector1_m0h12h21000000000_public); }); it('should initialize test vector 2 from the extended public key', function() { @@ -156,77 +156,77 @@ describe('BIP32', function() { 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.extended_public_key_string().should.equal(vector2_m_public); + 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.extended_private_key_string().should.equal(vector2_m0_private); + 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.extended_public_key_string().should.equal(vector2_m0_public); + child.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.extended_private_key_string().should.equal(vector2_m02147483647h_private); + 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.extended_public_key_string().should.equal(vector2_m02147483647h_public); + 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.extended_private_key_string().should.equal(vector2_m02147483647h1_private); + 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.extended_public_key_string().should.equal(vector2_m02147483647h1_public); + child.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.extended_private_key_string().should.equal(vector2_m02147483647h12147483646h_private); + 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.extended_public_key_string().should.equal(vector2_m02147483647h12147483646h_public); + 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.extended_private_key_string().should.equal(vector2_m02147483647h12147483646h2_private); + 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.extended_public_key_string().should.equal(vector2_m02147483647h12147483646h2_public); + child.extendedPublicKeyString().should.equal(vector2_m02147483647h12147483646h2_public); }); }); From c03d3c581859af67dcad58a2da5d4bfe898a6abf Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 27 Mar 2014 23:34:17 -0400 Subject: [PATCH 16/19] get BIP32 working in the browser by exposing more crypto --- Key.js | 12 ++++---- Point.js | 67 +++++++++++++++++++++++++++++++++++++++-- browser/vendor/ec.js | 3 ++ browser/vendor/jsbn2.js | 2 ++ browser/vendor/sec.js | 2 ++ test/test.BIP32.js | 1 + 6 files changed, 79 insertions(+), 8 deletions(-) diff --git a/Key.js b/Key.js index 4c880ab..f386a7b 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 index b028e37..b18971d 100644 --- a/Point.js +++ b/Point.js @@ -1,6 +1,20 @@ +"use strict"; + var imports = require('soop').imports(); -var Key = imports.Key || require('./Key'); +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 @@ -27,6 +41,35 @@ Point.add = function(p1, p2) { //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; } }; @@ -49,6 +92,15 @@ Point.fromKey = function(key) { //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; } }; @@ -62,13 +114,24 @@ Point.prototype.toKey = function() { var key = new Key(); key.compressed = false; var prefix = new Buffer([0x04]); - key.public = Buffer.concat([prefix, xbuf, ybuf]); //this is probably wrong + 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; } }; diff --git a/browser/vendor/ec.js b/browser/vendor/ec.js index 43ded3e..4fb77e6 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 5b2b725..e9ff490 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 e496571..57ee936 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/test/test.BIP32.js b/test/test.BIP32.js index 94188b4..122bd16 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -94,6 +94,7 @@ describe('BIP32', function() { }); it("should get m/0'/1 ext. public key from m/0' public key from test vector 1", function() { + //TEST var bip32 = new BIP32(vector1_m_private); var child = bip32.derive("m/0'"); var child_pub = new BIP32(child.extendedPublicKeyString()); From 0eedeed449020b033ed32cbb66ab8536b9649805 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Fri, 28 Mar 2014 16:29:12 -0400 Subject: [PATCH 17/19] add remaining public key derivation test vectors ...all pass in node and the browser. --- test/test.BIP32.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/test.BIP32.js b/test/test.BIP32.js index 122bd16..4c0380d 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -94,7 +94,6 @@ describe('BIP32', function() { }); it("should get m/0'/1 ext. public key from m/0' public key from test vector 1", function() { - //TEST var bip32 = new BIP32(vector1_m_private); var child = bip32.derive("m/0'"); var child_pub = new BIP32(child.extendedPublicKeyString()); @@ -124,6 +123,15 @@ describe('BIP32', function() { 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"); @@ -145,6 +153,15 @@ describe('BIP32', function() { 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); @@ -174,6 +191,15 @@ describe('BIP32', function() { 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'"); @@ -202,6 +228,15 @@ describe('BIP32', function() { 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'"); @@ -230,4 +265,13 @@ describe('BIP32', function() { 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); + }); + }); From f6aa01c44572ffd22245ad7cc6b7a39d77be0a2c Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Fri, 28 Mar 2014 18:07:23 -0400 Subject: [PATCH 18/19] add basic tests for all functions in Point --- bitcore.js | 1 + browser/build.js | 1 + test/test.Point.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 test/test.Point.js diff --git a/bitcore.js b/bitcore.js index 548b3d9..2831359 100644 --- a/bitcore.js +++ b/bitcore.js @@ -24,6 +24,7 @@ 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 24efa9b..3969418 100644 --- a/browser/build.js +++ b/browser/build.js @@ -38,6 +38,7 @@ var modules = [ 'PrivateKey', 'RpcClient', 'Key', + 'Point', 'SIN', 'SINKey', 'Script', diff --git a/test/test.Point.js b/test/test.Point.js new file mode 100644 index 0000000..f7e3163 --- /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); + }); + +}); From 343a6af7c389935e9adaa41d709bdb248cdb63c2 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Fri, 28 Mar 2014 18:46:09 -0400 Subject: [PATCH 19/19] add seed function to generate master privkey This follows the spec of BIP32. With tests for main test vectors. --- BIP32.js | 28 ++++++++++++++++++++++++++++ test/test.BIP32.js | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/BIP32.js b/BIP32.js index 0c774d1..7b70d0b 100644 --- a/BIP32.js +++ b/BIP32.js @@ -48,6 +48,34 @@ var BIP32 = function(bytes) { this.initFromBytes(bytes); } +BIP32.seed = function(bytes, network) { + if (!network) + return false; + + 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 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); + + bip32.buildExtendedPublicKey(); + bip32.buildExtendedPrivateKey(); + + return bip32; +}; + BIP32.prototype.initFromBytes = function(bytes) { // Both pub and private extended keys are 78 bytes if(bytes.length != 78) throw new Error("not enough data"); diff --git a/test/test.BIP32.js b/test/test.BIP32.js index 4c0380d..6bf7c80 100644 --- a/test/test.BIP32.js +++ b/test/test.BIP32.js @@ -274,4 +274,24 @@ describe('BIP32', function() { 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); + }); + + }); + });