add new SecureRandom class that does the right thing

Generating random numbers properly depends on the platform. The new
getRandomBuffer method does the right thing on the right platform. It will
sometimes fail due to insufficient entropy. The getPseudoRandomBuffer class is
also provided that will never fail, but it is not cryptographically secure and
should not be used for keys.
This commit is contained in:
Ryan X. Charles 2014-04-22 22:18:59 -03:00
parent 2c553c0dd9
commit ba692aaa20
11 changed files with 130 additions and 33 deletions

View File

@ -20,6 +20,7 @@ requireWhenAccessed('const', './const');
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');

View File

@ -42,6 +42,7 @@ var modules = [
'lib/SINKey',
'lib/Script',
'lib/ScriptInterpreter',
'lib/SecureRandom',
'lib/Sign',
'lib/Transaction',
'lib/TransactionBuilder',

View File

@ -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);

View File

@ -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 = function() {return SecureRandom.getPseudoRandomBuffer(8)};
var BIP0031_VERSION = 60000;

5
lib/SecureRandom.js Normal file
View File

@ -0,0 +1,5 @@
if (process.versions) {
module.exports = require('./node/SecureRandom');
return;
}
module.exports = require('./browser/SecureRandom');

View File

@ -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);

View File

@ -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);

10
lib/node/SecureRandom.js Normal file
View File

@ -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);

View File

@ -35,6 +35,7 @@
<script src="test.RpcClient.js"></script>
<script src="test.Script.js"></script>
<script src="test.ScriptInterpreter.js"></script>
<script src="test.SecureRandom.js"></script>
<script src="test.sighash.js"></script>
<script src="test.SIN.js"></script>
<script src="test.SINKey.js"></script>

57
test/test.SecureRandom.js Normal file
View File

@ -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'));
});
});
});

View File

@ -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.
*