diff --git a/bitcore.js b/bitcore.js index aa0de4bc0..bb92336ae 100644 --- a/bitcore.js +++ b/bitcore.js @@ -17,9 +17,11 @@ requireWhenAccessed('buffertools', 'buffertools'); requireWhenAccessed('Buffers.monkey', './patches/Buffers.monkey'); requireWhenAccessed('config', './config'); requireWhenAccessed('const', './const'); +requireWhenAccessed('Curve', './lib/Curve'); requireWhenAccessed('Deserialize', './lib/Deserialize'); requireWhenAccessed('log', './util/log'); requireWhenAccessed('networks', './networks'); +requireWhenAccessed('SecureRandom', './lib/SecureRandom'); requireWhenAccessed('util', './util/util'); requireWhenAccessed('EncodedData', './util/EncodedData'); requireWhenAccessed('VersionedData', './util/VersionedData'); diff --git a/browser/build.js b/browser/build.js index 05f7a6266..0b8b59f40 100644 --- a/browser/build.js +++ b/browser/build.js @@ -28,6 +28,7 @@ var modules = [ 'lib/Block', 'lib/Bloom', 'lib/Connection', + 'lib/Curve', 'lib/Deserialize', 'lib/Electrum', 'lib/Message', @@ -42,6 +43,7 @@ var modules = [ 'lib/SINKey', 'lib/Script', 'lib/ScriptInterpreter', + 'lib/SecureRandom', 'lib/Sign', 'lib/Transaction', 'lib/TransactionBuilder', diff --git a/examples/ConnectionTor.js b/examples/ConnectionTor.js index 2d9d8a662..c96040fec 100644 --- a/examples/ConnectionTor.js +++ b/examples/ConnectionTor.js @@ -1,5 +1,5 @@ -var Peer = require('../Peer'); -var Connection = require('../Connection'); +var Peer = require('../lib/Peer'); +var Connection = require('../lib/Connection'); var dns = require('dns'); // get a peer from dns seed diff --git a/lib/BIP32.js b/lib/BIP32.js index 94a7602aa..6484af02e 100644 --- a/lib/BIP32.js +++ b/lib/BIP32.js @@ -3,6 +3,7 @@ var base58 = imports.base58 || require('base58-native').base58; var coinUtil = imports.coinUtil || require('../util'); var Key = imports.Key || require('./Key'); var Point = imports.Point || require('./Point'); +var SecureRandom = imports.SecureRandom || require('./SecureRandom'); var bignum = imports.bignum || require('bignum'); var crypto = require('crypto'); var networks = require('../networks'); @@ -27,7 +28,7 @@ var BIP32 = function(bytes) { this.depth = 0x00; this.parentFingerprint = new Buffer([0, 0, 0, 0]); this.childIndex = new Buffer([0, 0, 0, 0]); - this.chainCode = Key.generateSync().private; + this.chainCode = SecureRandom.getRandomBuffer(32); this.eckey = Key.generateSync(); this.hasPrivateKey = true; this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); @@ -288,17 +289,20 @@ BIP32.prototype.deriveChild = function(i) { var ilGkey = new Key(); ilGkey.private = il; ilGkey.regenerateSync(); - var ilG = Point.fromKey(ilGkey); + ilGkey.compressed = false; + var ilG = Point.fromUncompressedPubKey(ilGkey.public); var oldkey = new Key(); oldkey.public = this.eckey.public; - var Kpar = Point.fromKey(oldkey); - var newpub = Point.add(ilG, Kpar).toKey().public; + oldkey.compressed = false; + var Kpar = Point.fromUncompressedPubKey(oldkey.public); + var newpub = Point.add(ilG, Kpar).toUncompressedPubKey(); ret = new BIP32(null); ret.chainCode = new Buffer(ir); var eckey = new Key(); eckey.public = newpub; + eckey.compressed = true; ret.eckey = eckey; ret.hasPrivateKey = false; } diff --git a/lib/Connection.js b/lib/Connection.js index f03e95330..1377545c6 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -17,7 +17,8 @@ var util = imports.util || require('../util'); var Parser = imports.Parser || require('../util/BinaryParser'); var buffertools = imports.buffertools || require('buffertools'); var doubleSha256 = imports.doubleSha256 || util.twoSha256; -var nonce = util.generateNonce(); +var SecureRandom = imports.SecureRandom || require('./SecureRandom'); +var nonce = SecureRandom.getPseudoRandomBuffer(8); var BIP0031_VERSION = 60000; diff --git a/lib/Curve.js b/lib/Curve.js new file mode 100644 index 000000000..f2d140d4a --- /dev/null +++ b/lib/Curve.js @@ -0,0 +1,22 @@ +"use strict"; +var imports = require('soop'); +var bignum = imports.bignum || require('bignum'); +var Point = imports.Point || require('./Point'); + +var n = bignum.fromBuffer(new Buffer("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 'hex'), {size: 32}); +var G = new Point(bignum.fromBuffer(new Buffer("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 'hex'), {size: 32}), + bignum.fromBuffer(new Buffer("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 'hex'), {size: 32})); + +/* secp256k1 curve */ +var Curve = function() { +}; + +Curve.getG = function() { + return G; +}; + +Curve.getN = function() { + return n; +}; + +module.exports = require('soop')(Curve); diff --git a/lib/Electrum.js b/lib/Electrum.js index 1cf499e1a..02457798f 100644 --- a/lib/Electrum.js +++ b/lib/Electrum.js @@ -31,8 +31,9 @@ Electrum.prototype.generatePubKey = function (n, for_change) { var sequence_key = new Key(); sequence_key.private = sequence.toBuffer(); sequence_key.regenerateSync(); + sequence_key.compressed = false; - var sequence_pt = Point.fromKey(sequence_key); + var sequence_pt = Point.fromUncompressedPubKey(sequence_key.public); pt = Point.add(mpk_pt, sequence_pt); diff --git a/lib/SecureRandom.js b/lib/SecureRandom.js new file mode 100644 index 000000000..fa7f5ef4d --- /dev/null +++ b/lib/SecureRandom.js @@ -0,0 +1,5 @@ +if (process.versions) { + module.exports = require('./node/SecureRandom'); + return; +} +module.exports = require('./browser/SecureRandom'); diff --git a/lib/browser/Key.js b/lib/browser/Key.js index a35437bd9..790efd112 100644 --- a/lib/browser/Key.js +++ b/lib/browser/Key.js @@ -1,8 +1,10 @@ var ECKey = require('../../browser/vendor-bundle.js').ECKey; +var SecureRandom = require('../SecureRandom'); +var Curve = require('../Curve'); var Key = function() { this._pub = null; - this.compressed = true; // default + this._compressed = true; // default }; var bufferToArray = Key.bufferToArray = function(buffer) { @@ -16,14 +18,13 @@ var bufferToArray = Key.bufferToArray = function(buffer) { return ret; } - Object.defineProperty(Key.prototype, 'public', { set: function(p){ if (!Buffer.isBuffer(p) ) { throw new Error('Arg should be a buffer'); } var type = p[0]; - this.compressed = type!==0x04; + this._compressed = type!==0x04; this._pub = p; }, get: function(){ @@ -31,14 +32,48 @@ Object.defineProperty(Key.prototype, 'public', { } }); +Object.defineProperty(Key.prototype, 'compressed', { + set: function(c) { + var oldc = this._compressed; + this._compressed = !!c; + if (oldc == this._compressed) + return; + var oldp = this._pub; + if (this._pub) { + var eckey = new ECKey(); + eckey.setPub(bufferToArray(this.public)); + eckey.setCompressed(this._compressed); + this._pub = new Buffer(eckey.getPub()); + } + if (!this._compressed) { + //bug in eckey + //oldp.slice(1).copy(this._pub, 1); + } + }, + get: function() { + return this._compressed; + } +}); + Key.generateSync = function() { - var eck = new ECKey(); + var privbuf; + + while(true) { + privbuf = SecureRandom.getRandomBuffer(32); + if ((bignum.fromBuffer(privbuf, {size: 32})).cmp(Curve.getN()) < 0) + break; + } + + var privhex = privbuf.toString('hex'); + var eck = new ECKey(privhex); eck.setCompressed(true); var pub = eck.getPub(); - var ret = new Key(); - ret.private = new Buffer(eck.priv.toByteArrayUnsigned()); - ret.public = new Buffer(pub); + ret = new Key(); + ret.private = privbuf; + ret._compressed = true; + ret.public = new Buffer(eck.getPub()); + return ret; }; @@ -48,8 +83,8 @@ Key.prototype.regenerateSync = function() { } var eck = new ECKey(this.private.toString('hex')); - eck.setCompressed(this.compressed); - this.public = new Buffer(eck.getPub()); + eck.setCompressed(this._compressed); + this._pub = new Buffer(eck.getPub()); return this; }; @@ -62,7 +97,7 @@ Key.prototype.signSync = function(hash) { throw new Error('Arg should be a 32 bytes hash buffer'); } var eck = new ECKey(this.private.toString('hex')); - eck.setCompressed(this.compressed); + eck.setCompressed(this._compressed); var signature = eck.sign(bufferToArray(hash)); // return it as a buffer to keep c++ compatibility return new Buffer(signature); @@ -92,7 +127,7 @@ Key.prototype.verifySignatureSync = function(hash, sig) { var eck = new ECKey(); eck.setPub(bufferToArray(self.public)); - eck.setCompressed(self.compressed); + eck.setCompressed(self._compressed); var sigA = bufferToArray(sig); var ret = eck.verify(bufferToArray(hash),sigA); return ret; diff --git a/lib/browser/Point.js b/lib/browser/Point.js index b9f4c3638..fba0b3d45 100644 --- a/lib/browser/Point.js +++ b/lib/browser/Point.js @@ -51,31 +51,20 @@ Point.add = function(p1, p2) { }; //convert the public key of a Key into a Point -Point.fromKey = function(key) { +Point.fromUncompressedPubKey = function(pubkey) { 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}); + point.x = bignum.fromBuffer((new Buffer(pubkey)).slice(1, 33), {size: 32}); + point.y = bignum.fromBuffer((new Buffer(pubkey)).slice(33, 65), {size: 32}); return point; }; //convert the Point into the Key containing a compressed public key -Point.prototype.toKey = function() { +Point.prototype.toUncompressedPubKey = function() { 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; + var pub = Buffer.concat([prefix, xbuf, ybuf]); + return pub; }; module.exports = require('soop')(Point); diff --git a/lib/browser/SecureRandom.js b/lib/browser/SecureRandom.js new file mode 100644 index 000000000..79f1db50c --- /dev/null +++ b/lib/browser/SecureRandom.js @@ -0,0 +1,23 @@ +var imports = require('soop'); + +var SecureRandom = require('../common/SecureRandom'); + +SecureRandom.getRandomBuffer = function(size) { + if (!window.crypto && !window.msCrypto) + throw new Error('window.crypto not available'); + + if (window.crypto && window.crypto.getRandomValues) + var crypto = window.crypto; + else if (window.msCrypto && window.msCrypto.getRandomValues) //internet explorer + var crypto = window.msCrypto; + else + throw new Error('window.crypto.getRandomValues not available'); + + var bbuf = new Uint8Array(size); + crypto.getRandomValues(bbuf); + var buf = new Buffer(bbuf); + + return buf; +}; + +module.exports = require('soop')(SecureRandom); diff --git a/lib/common/SecureRandom.js b/lib/common/SecureRandom.js new file mode 100644 index 000000000..a276d22ac --- /dev/null +++ b/lib/common/SecureRandom.js @@ -0,0 +1,28 @@ +var imports = require('soop'); + +var SecureRandom = function() { +}; + +/* secure random bytes that sometimes throws an error due to lack of entropy */ +SecureRandom.getRandomBuffer = function() {}; + +/* insecure random bytes, but it never fails */ +SecureRandom.getPseudoRandomBuffer = function(size) { + var b32 = 0x100000000; + var b = new Buffer(size); + + for (var i = 0; i <= size; i++) { + var j = Math.floor(i / 4); + var k = i - j * 4; + if (k == 0) { + r = Math.random() * b32; + b[i] = r & 0xff; + } else { + b[i] = (r = r >>> 8) & 0xff; + } + } + + return b; +}; + +module.exports = require('soop')(SecureRandom); diff --git a/lib/node/Key.js b/lib/node/Key.js index 8e079d9d2..bec7c80a3 100644 --- a/lib/node/Key.js +++ b/lib/node/Key.js @@ -1 +1,3 @@ -module.exports = require('bindings')('KeyModule').Key; +var Key = require('bindings')('KeyModule').Key; + +module.exports = Key; diff --git a/lib/node/Point.js b/lib/node/Point.js index 38a6466b8..64d4dd7f8 100644 --- a/lib/node/Point.js +++ b/lib/node/Point.js @@ -1,8 +1,8 @@ "use strict"; var imports = require('soop').imports(); -var Key = imports.Key || require('../Key'); var bignum = imports.bignum || require('bignum'); +var CPPKey = imports.CPPKey || require('bindings')('KeyModule').Key; var assert = require('assert'); //a point on the secp256k1 curve @@ -13,41 +13,27 @@ var Point = function(x, y) { }; Point.add = function(p1, p2) { - 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); + var u1 = p1.toUncompressedPubKey(); + var u2 = p2.toUncompressedPubKey(); + var pubKey = CPPKey.addUncompressed(u1, u2); + return Point.fromUncompressedPubKey(pubKey); }; //convert the public key of a Key into a Point -Point.fromKey = function(key) { +Point.fromUncompressedPubKey = function(pubkey) { 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}); + point.x = bignum.fromBuffer(pubkey.slice(1, 33), {size: 32}); + point.y = bignum.fromBuffer(pubkey.slice(33, 65), {size: 32}); return point; }; //convert the Point into the Key containing a compressed public key -Point.prototype.toKey = function() { +Point.prototype.toUncompressedPubKey = function() { var xbuf = this.x.toBuffer({size: 32}); var ybuf = this.y.toBuffer({size: 32}); - var key = new Key(); - key.compressed = false; var prefix = new Buffer([0x04]); - key.public = Buffer.concat([prefix, xbuf, ybuf]); //this might be wrong - key.compressed = true; - return key; + var pubkey = Buffer.concat([prefix, xbuf, ybuf]); + return pubkey; }; module.exports = require('soop')(Point); diff --git a/lib/node/SecureRandom.js b/lib/node/SecureRandom.js new file mode 100644 index 000000000..3639f75c1 --- /dev/null +++ b/lib/node/SecureRandom.js @@ -0,0 +1,10 @@ +var imports = require('soop'); +var crypto = imports.crypto || require('crypto'); + +var SecureRandom = require('../common/SecureRandom'); + +SecureRandom.getRandomBuffer = function(size) { + return crypto.randomBytes(size); +} + +module.exports = require('soop')(SecureRandom); diff --git a/test/index.html b/test/index.html index 6a864096d..5f88d545d 100644 --- a/test/index.html +++ b/test/index.html @@ -21,6 +21,7 @@ + @@ -36,6 +37,7 @@ + diff --git a/test/test.Curve.js b/test/test.Curve.js new file mode 100644 index 000000000..211645060 --- /dev/null +++ b/test/test.Curve.js @@ -0,0 +1,37 @@ +'use strict'; + +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); +var coinUtil = coinUtil || bitcore.util; +var buffertools = require('buffertools'); +var bignum = require('bignum'); + +var should = chai.should(); +var assert = chai.assert; + +var Curve = bitcore.Curve; + +describe('Curve', function() { + + it('should initialize the main object', function() { + should.exist(Curve); + }); + + describe('getN', function() { + it('should return a big number', function() { + var N = Curve.getN(); + should.exist(N); + N.toBuffer({size: 32}).toString('hex').length.should.equal(64); + }); + }); + + describe('getG', function() { + it('should return a Point', function() { + var G = Curve.getG(); + should.exist(G.x); + G.x.toBuffer({size: 32}).toString('hex').length.should.equal(64); + G.y.toBuffer({size: 32}).toString('hex').length.should.equal(64); + }); + }); + +}); diff --git a/test/test.Electrum.js b/test/test.Electrum.js index 8e82c18ad..b6bfe8e8b 100644 --- a/test/test.Electrum.js +++ b/test/test.Electrum.js @@ -66,7 +66,6 @@ describe('Electrum', function() { //change pubkey = elec.generateChangePubKey(i); addr = Address.fromPubKey(pubkey); - console.log('change'); addr.as('base58').should.equal(expected_values.change[i]); } }); diff --git a/test/test.Key.js b/test/test.Key.js index 25cdb263f..d2bef6105 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -7,6 +7,9 @@ var should = chai.should(); var assert = chai.assert; var Key = bitcore.Key; +var Point = bitcore.Point; +var bignum = require('bignum'); + describe('Key', function() { it('should initialize the main object', function() { should.exist(Key); @@ -15,6 +18,20 @@ describe('Key', function() { var k = new Key(); should.exist(k); }); + it('should set change compressed to uncompressed', function() { + var key = Key.generateSync(); + key.public.length.should.equal(33); + key.compressed = false; + key.public.length.should.equal(65); + }); + it('should change uncompressed to compressed', function() { + var key = Key.generateSync(); + key.compressed = false; + var key2 = new Key(); + key2.public = key.public; + key2.compressed = true; + key2.public.length.should.equal(33); + }); it('should be able to generateSync instance', function() { var k = Key.generateSync(); should.exist(k); @@ -112,4 +129,5 @@ describe('Key', function() { var ret= k.verifySignatureSync(a_hash, sig2); ret.should.equal(false); }); + }); diff --git a/test/test.Point.js b/test/test.Point.js index 66e32c14c..4ca45a556 100644 --- a/test/test.Point.js +++ b/test/test.Point.js @@ -49,8 +49,9 @@ describe('Point', function() { var pubKeyBufCompressedHex = "0369b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; var key = new Key(); key.public = new Buffer(pubKeyBufCompressedHex, 'hex'); + key.compressed = false; - key.public.toString('hex').should.equal(a.toKey().public.toString('hex')); + key.public.toString('hex').should.equal(a.toUncompressedPubKey().toString('hex')); }); it('should convert the public key of a Key into a Point', function() { @@ -60,8 +61,9 @@ describe('Point', function() { var pubKeyBufCompressedHex = "0369b154b42ff9452c31251cb341d7db01ad603dc56d64f9c5fb9e7031b89a241d"; var key = new Key(); key.public = new Buffer(pubKeyBufCompressedHex, 'hex'); + key.compressed = false; - var point = Point.fromKey(key); + var point = Point.fromUncompressedPubKey(key.public); point.x.toBuffer({size: 32}).toString('hex').should.equal(axhex); point.y.toBuffer({size: 32}).toString('hex').should.equal(ayhex); }); diff --git a/test/test.SecureRandom.js b/test/test.SecureRandom.js new file mode 100644 index 000000000..ab8207b99 --- /dev/null +++ b/test/test.SecureRandom.js @@ -0,0 +1,57 @@ +'use strict'; + +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); +var should = chai.should(); +var assert = chai.assert; +var SecureRandom = bitcore.SecureRandom; + +describe('SecureRandom', function() { + + describe('getRandomBuffer', function() { + + it('should return a buffer', function() { + var bytes = SecureRandom.getRandomBuffer(8); + bytes.length.should.equal(8); + Buffer.isBuffer(bytes).should.equal(true); + }); + + it('should not equate two 256 bit random buffers', function() { + var bytes1 = SecureRandom.getRandomBuffer(32); + var bytes2 = SecureRandom.getRandomBuffer(32); + bytes1.toString('hex').should.not.equal(bytes2.toString('hex')); + }); + + }); + + describe('getPseudoRandomBuffer', function() { + + it('should generate 7 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(7); + buf.length.should.equal(7); + }); + + it('should generate 8 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(8); + buf.length.should.equal(8); + }); + + it('should generate 9 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(9); + buf.length.should.equal(9); + }); + + it('should generate 90 random bytes', function() { + var buf = SecureRandom.getPseudoRandomBuffer(90); + buf.length.should.equal(90); + }); + + it('should generate two 8 byte buffers that are not equal', function() { + var buf1 = SecureRandom.getPseudoRandomBuffer(8); + var buf2 = SecureRandom.getPseudoRandomBuffer(8); + buf1.toString('hex').should.not.equal(buf2.toString('hex')); + }); + + }); + +}); diff --git a/util/util.js b/util/util.js index f89dce844..a59df4b35 100644 --- a/util/util.js +++ b/util/util.js @@ -10,7 +10,6 @@ if (inBrowser) { browser = require('../browser/vendor-bundle.js'); } - var sha256 = exports.sha256 = function(data) { return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary'); }; @@ -336,36 +335,6 @@ var createSynchrotron = exports.createSynchrotron = function(fn) { }; }; -/** - * Generate a random 64-bit number. - * - * With ideas from node-uuid: - * Copyright (c) 2010 Robert Kieffer - * https://github.com/broofa/node-uuid/ - * - * @returns Buffer random nonce - */ -var generateNonce = exports.generateNonce = function() { - var b32 = 0x100000000, - ff = 0xff; - var b = new Buffer(8), - i = 0; - - // Generate eight random bytes - r = Math.random() * b32; - b[i++] = r & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - r = Math.random() * b32; - b[i++] = r & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - b[i++] = (r = r >>> 8) & ff; - - return b; -}; - /** * Decode difficulty bits. *