Merge branch 'split-wallet' of git://github.com/weissjeffm/bitaddress.org into split-wallet
This commit is contained in:
commit
1e38837ab2
5 changed files with 1336 additions and 1 deletions
|
@ -24,6 +24,7 @@ module.exports = function (grunt) {
|
||||||
{ token: "//cryptojs.ripemd160.js", file: "./src/cryptojs.ripemd160.js" },
|
{ token: "//cryptojs.ripemd160.js", file: "./src/cryptojs.ripemd160.js" },
|
||||||
{ token: "//crypto-scrypt.js", file: "./src/crypto-scrypt.js" },
|
{ token: "//crypto-scrypt.js", file: "./src/crypto-scrypt.js" },
|
||||||
{ token: "//ellipticcurve.js", file: "./src/ellipticcurve.js" },
|
{ token: "//ellipticcurve.js", file: "./src/ellipticcurve.js" },
|
||||||
|
{ token: "//secrets.js", file: "./src/secrets.js" },
|
||||||
{ token: "//ninja.key.js", file: "./src/ninja.key.js" },
|
{ token: "//ninja.key.js", file: "./src/ninja.key.js" },
|
||||||
{ token: "//ninja.misc.js", file: "./src/ninja.misc.js" },
|
{ token: "//ninja.misc.js", file: "./src/ninja.misc.js" },
|
||||||
{ token: "//ninja.onload.js", file: "./src/ninja.onload.js" },
|
{ token: "//ninja.onload.js", file: "./src/ninja.onload.js" },
|
||||||
|
@ -34,6 +35,7 @@ module.exports = function (grunt) {
|
||||||
{ token: "//ninja.bulkwallet.js", file: "./src/ninja.bulkwallet.js" },
|
{ token: "//ninja.bulkwallet.js", file: "./src/ninja.bulkwallet.js" },
|
||||||
{ token: "//ninja.brainwallet.js", file: "./src/ninja.brainwallet.js" },
|
{ token: "//ninja.brainwallet.js", file: "./src/ninja.brainwallet.js" },
|
||||||
{ token: "//ninja.vanitywallet.js", file: "./src/ninja.vanitywallet.js" },
|
{ token: "//ninja.vanitywallet.js", file: "./src/ninja.vanitywallet.js" },
|
||||||
|
{ token: "//ninja.splitwallet.js", file: "./src/ninja.splitwallet.js" },
|
||||||
{ token: "//ninja.detailwallet.js", file: "./src/ninja.detailwallet.js" },
|
{ token: "//ninja.detailwallet.js", file: "./src/ninja.detailwallet.js" },
|
||||||
{ token: "//qrcode.js", file: "./src/qrcode.js" },
|
{ token: "//qrcode.js", file: "./src/qrcode.js" },
|
||||||
{ token: "//securerandom.js", file: "./src/securerandom.js" },
|
{ token: "//securerandom.js", file: "./src/securerandom.js" },
|
||||||
|
@ -46,4 +48,4 @@ module.exports = function (grunt) {
|
||||||
grunt.file.defaultEncoding = 'utf-8';
|
grunt.file.defaultEncoding = 'utf-8';
|
||||||
grunt.loadNpmTasks("grunt-combine");
|
grunt.loadNpmTasks("grunt-combine");
|
||||||
grunt.registerTask("default", ["combine:single"]);
|
grunt.registerTask("default", ["combine:single"]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2344,6 +2344,540 @@ if (typeof Crypto == "undefined" || !Crypto.util) {
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
// secrets.js - by Alexander Stetsyuk - released under MIT License
|
||||||
|
(function(exports, global){
|
||||||
|
var defaults = {
|
||||||
|
bits: 8, // default number of bits
|
||||||
|
radix: 16, // work with HEX by default
|
||||||
|
minBits: 3,
|
||||||
|
maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS!
|
||||||
|
|
||||||
|
bytesPerChar: 2,
|
||||||
|
maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53)
|
||||||
|
|
||||||
|
// Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30
|
||||||
|
// The index of each term in the array corresponds to the n for that polynomial
|
||||||
|
// i.e. to get the polynomial for n=16, use primitivePolynomials[16]
|
||||||
|
primitivePolynomials: [null,null,1,3,3,5,3,3,29,17,9,5,83,27,43,3,45,9,39,39,9,5,3,33,27,9,71,39,9,5,83],
|
||||||
|
|
||||||
|
// warning for insecure PRNG
|
||||||
|
warning: 'WARNING:\nA secure random number generator was not found.\nUsing Math.random(), which is NOT cryptographically strong!'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Protected settings object
|
||||||
|
var config = {};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports.getConfig = function(){
|
||||||
|
return {
|
||||||
|
'bits': config.bits,
|
||||||
|
'unsafePRNG': config.unsafePRNG
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function init(bits){
|
||||||
|
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
|
||||||
|
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
config.radix = defaults.radix;
|
||||||
|
config.bits = bits || defaults.bits;
|
||||||
|
config.size = Math.pow(2, config.bits);
|
||||||
|
config.max = config.size - 1;
|
||||||
|
|
||||||
|
// Construct the exp and log tables for multiplication.
|
||||||
|
var logs = [], exps = [], x = 1, primitive = defaults.primitivePolynomials[config.bits];
|
||||||
|
for(var i=0; i<config.size; i++){
|
||||||
|
exps[i] = x;
|
||||||
|
logs[x] = i;
|
||||||
|
x <<= 1;
|
||||||
|
if(x >= config.size){
|
||||||
|
x ^= primitive;
|
||||||
|
x &= config.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.logs = logs;
|
||||||
|
config.exps = exps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports.init = init;
|
||||||
|
|
||||||
|
function isInited(){
|
||||||
|
if(!config.bits || !config.size || !config.max || !config.logs || !config.exps || config.logs.length !== config.size || config.exps.length !== config.size){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a pseudo-random number generator of the form function(bits){}
|
||||||
|
// which should output a random string of 1's and 0's of length `bits`
|
||||||
|
function getRNG(){
|
||||||
|
var randomBits, crypto;
|
||||||
|
|
||||||
|
function construct(bits, arr, radix, size){
|
||||||
|
var str = '',
|
||||||
|
i = 0,
|
||||||
|
len = arr.length-1;
|
||||||
|
while( i<len || (str.length < bits) ){
|
||||||
|
str += padLeft(parseInt(arr[i], radix).toString(2), size);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
str = str.substr(-bits);
|
||||||
|
if( (str.match(/0/g)||[]).length === str.length){ // all zeros?
|
||||||
|
return null;
|
||||||
|
}else{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// node.js crypto.randomBytes()
|
||||||
|
if(typeof require === 'function' && (crypto=require('crypto')) && (randomBits=crypto['randomBytes'])){
|
||||||
|
return function(bits){
|
||||||
|
var bytes = Math.ceil(bits/8),
|
||||||
|
str = null;
|
||||||
|
|
||||||
|
while( str === null ){
|
||||||
|
str = construct(bits, randomBits(bytes).toString('hex'), 16, 4);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// browsers with window.crypto.getRandomValues()
|
||||||
|
if(global['crypto'] && typeof global['crypto']['getRandomValues'] === 'function' && typeof global['Uint32Array'] === 'function'){
|
||||||
|
crypto = global['crypto'];
|
||||||
|
return function(bits){
|
||||||
|
var elems = Math.ceil(bits/32),
|
||||||
|
str = null,
|
||||||
|
arr = new global['Uint32Array'](elems);
|
||||||
|
|
||||||
|
while( str === null ){
|
||||||
|
crypto['getRandomValues'](arr);
|
||||||
|
str = construct(bits, arr, 10, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A totally insecure RNG!!! (except in Safari)
|
||||||
|
// Will produce a warning every time it is called.
|
||||||
|
config.unsafePRNG = true;
|
||||||
|
warn();
|
||||||
|
|
||||||
|
var bitsPerNum = 32;
|
||||||
|
var max = Math.pow(2,bitsPerNum)-1;
|
||||||
|
return function(bits){
|
||||||
|
var elems = Math.ceil(bits/bitsPerNum);
|
||||||
|
var arr = [], str=null;
|
||||||
|
while(str===null){
|
||||||
|
for(var i=0; i<elems; i++){
|
||||||
|
arr[i] = Math.floor(Math.random() * max + 1);
|
||||||
|
}
|
||||||
|
str = construct(bits, arr, 10, bitsPerNum);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Warn about using insecure rng.
|
||||||
|
// Called when Math.random() is being used.
|
||||||
|
function warn(){
|
||||||
|
global['console']['warn'](defaults.warning);
|
||||||
|
if(typeof global['alert'] === 'function' && config.alert){
|
||||||
|
global['alert'](defaults.warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG()
|
||||||
|
/** @expose **/
|
||||||
|
exports.setRNG = function(rng, alert){
|
||||||
|
if(!isInited()){
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
config.unsafePRNG=false;
|
||||||
|
rng = rng || getRNG();
|
||||||
|
|
||||||
|
// test the RNG (5 times)
|
||||||
|
if(typeof rng !== 'function' || typeof rng(config.bits) !== 'string' || !parseInt(rng(config.bits),2) || rng(config.bits).length > config.bits || rng(config.bits).length < config.bits){
|
||||||
|
throw new Error("Random number generator is invalid. Supply an RNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's.")
|
||||||
|
}else{
|
||||||
|
config.rng = rng;
|
||||||
|
}
|
||||||
|
config.alert = !!alert;
|
||||||
|
|
||||||
|
return !!config.unsafePRNG;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSetRNG(){
|
||||||
|
return typeof config.rng === 'function';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generates a random bits-length number string using the PRNG
|
||||||
|
/** @expose **/
|
||||||
|
exports.random = function(bits){
|
||||||
|
if(!isSetRNG()){
|
||||||
|
this.setRNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof bits !== 'number' || bits%1 !== 0 || bits < 2){
|
||||||
|
throw new Error('Number of bits must be an integer greater than 1.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.unsafePRNG){
|
||||||
|
warn();
|
||||||
|
}
|
||||||
|
return bin2hex(config.rng(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16)
|
||||||
|
// into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`),
|
||||||
|
// requiring `threshold` number of shares to reconstruct the secret.
|
||||||
|
// Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing.
|
||||||
|
/** @expose **/
|
||||||
|
exports.share = function(secret, numShares, threshold, padLength, withoutPrefix){
|
||||||
|
if(!isInited()){
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
if(!isSetRNG()){
|
||||||
|
this.setRNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
padLength = padLength || 0;
|
||||||
|
|
||||||
|
if(typeof secret !== 'string'){
|
||||||
|
throw new Error('Secret must be a string.');
|
||||||
|
}
|
||||||
|
if(typeof numShares !== 'number' || numShares%1 !== 0 || numShares < 2){
|
||||||
|
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.')
|
||||||
|
}
|
||||||
|
if(numShares > config.max){
|
||||||
|
var neededBits = Math.ceil(Math.log(numShares +1)/Math.LN2);
|
||||||
|
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To create ' + numShares + ' shares, use at least ' + neededBits + ' bits.')
|
||||||
|
}
|
||||||
|
if(typeof threshold !== 'number' || threshold%1 !== 0 || threshold < 2){
|
||||||
|
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.');
|
||||||
|
}
|
||||||
|
if(threshold > config.max){
|
||||||
|
var neededBits = Math.ceil(Math.log(threshold +1)/Math.LN2);
|
||||||
|
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To use a threshold of ' + threshold + ', use at least ' + neededBits + ' bits.');
|
||||||
|
}
|
||||||
|
if(typeof padLength !== 'number' || padLength%1 !== 0 ){
|
||||||
|
throw new Error('Zero-pad length must be an integer greater than 1.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.unsafePRNG){
|
||||||
|
warn();
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = '1' + hex2bin(secret); // append a 1 so that we can preserve the correct number of leading zeros in our secret
|
||||||
|
secret = split(secret, padLength);
|
||||||
|
var x = new Array(numShares), y = new Array(numShares);
|
||||||
|
for(var i=0, len = secret.length; i<len; i++){
|
||||||
|
var subShares = this._getShares(secret[i], numShares, threshold);
|
||||||
|
for(var j=0; j<numShares; j++){
|
||||||
|
x[j] = x[j] || subShares[j].x.toString(config.radix);
|
||||||
|
y[j] = padLeft(subShares[j].y.toString(2)) + (y[j] ? y[j] : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var padding = config.max.toString(config.radix).length;
|
||||||
|
if(withoutPrefix){
|
||||||
|
for(var i=0; i<numShares; i++){
|
||||||
|
x[i] = bin2hex(y[i]);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
for(var i=0; i<numShares; i++){
|
||||||
|
x[i] = config.bits.toString(36).toUpperCase() + padLeft(x[i],padding) + bin2hex(y[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the basic polynomial generation and evaluation function
|
||||||
|
// for a `config.bits`-length secret (NOT an arbitrary length)
|
||||||
|
// Note: no error-checking at this stage! If `secrets` is NOT
|
||||||
|
// a NUMBER less than 2^bits-1, the output will be incorrect!
|
||||||
|
/** @expose **/
|
||||||
|
exports._getShares = function(secret, numShares, threshold){
|
||||||
|
var shares = [];
|
||||||
|
var coeffs = [secret];
|
||||||
|
|
||||||
|
for(var i=1; i<threshold; i++){
|
||||||
|
coeffs[i] = parseInt(config.rng(config.bits),2);
|
||||||
|
}
|
||||||
|
for(var i=1, len = numShares+1; i<len; i++){
|
||||||
|
shares[i-1] = {
|
||||||
|
x: i,
|
||||||
|
y: horner(i, coeffs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shares;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Polynomial evaluation at `x` using Horner's Method
|
||||||
|
// TODO: this can possibly be sped up using other methods
|
||||||
|
// NOTE: fx=fx * x + coeff[i] -> exp(log(fx) + log(x)) + coeff[i],
|
||||||
|
// so if fx===0, just set fx to coeff[i] because
|
||||||
|
// using the exp/log form will result in incorrect value
|
||||||
|
function horner(x, coeffs){
|
||||||
|
var logx = config.logs[x];
|
||||||
|
var fx = 0;
|
||||||
|
for(var i=coeffs.length-1; i>=0; i--){
|
||||||
|
if(fx === 0){
|
||||||
|
fx = coeffs[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fx = config.exps[ (logx + config.logs[fx]) % config.max ] ^ coeffs[i];
|
||||||
|
}
|
||||||
|
return fx;
|
||||||
|
};
|
||||||
|
|
||||||
|
function inArray(arr,val){
|
||||||
|
for(var i = 0,len=arr.length; i < len; i++) {
|
||||||
|
if(arr[i] === val){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function processShare(share){
|
||||||
|
|
||||||
|
var bits = parseInt(share[0], 36);
|
||||||
|
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
|
||||||
|
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var max = Math.pow(2, bits) - 1;
|
||||||
|
var idLength = max.toString(config.radix).length;
|
||||||
|
|
||||||
|
var id = parseInt(share.substr(1, idLength), config.radix);
|
||||||
|
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
|
||||||
|
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
|
||||||
|
}
|
||||||
|
share = share.substr(idLength + 1);
|
||||||
|
if(!share.length){
|
||||||
|
throw new Error('Invalid share: zero-length share.')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'bits': bits,
|
||||||
|
'id': id,
|
||||||
|
'value': share
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports._processShare = processShare;
|
||||||
|
|
||||||
|
// Protected method that evaluates the Lagrange interpolation
|
||||||
|
// polynomial at x=`at` for individual config.bits-length
|
||||||
|
// segments of each share in the `shares` Array.
|
||||||
|
// Each share is expressed in base `inputRadix`. The output
|
||||||
|
// is expressed in base `outputRadix'
|
||||||
|
function combine(at, shares){
|
||||||
|
var setBits, share, x = [], y = [], result = '', idx;
|
||||||
|
|
||||||
|
for(var i=0, len = shares.length; i<len; i++){
|
||||||
|
share = processShare(shares[i]);
|
||||||
|
if(typeof setBits === 'undefined'){
|
||||||
|
setBits = share['bits'];
|
||||||
|
}else if(share['bits'] !== setBits){
|
||||||
|
throw new Error('Mismatched shares: Different bit settings.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.bits !== setBits){
|
||||||
|
init(setBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inArray(x, share['id'])){ // repeated x value?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = x.push(share['id']) - 1;
|
||||||
|
share = split(hex2bin(share['value']));
|
||||||
|
for(var j=0, len2 = share.length; j<len2; j++){
|
||||||
|
y[j] = y[j] || [];
|
||||||
|
y[j][idx] = share[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i=0, len=y.length; i<len; i++){
|
||||||
|
result = padLeft(lagrange(at, x, y[i]).toString(2)) + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(at===0){// reconstructing the secret
|
||||||
|
var idx = result.indexOf('1'); //find the first 1
|
||||||
|
return bin2hex(result.slice(idx+1));
|
||||||
|
}else{// generating a new share
|
||||||
|
return bin2hex(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine `shares` Array into the original secret
|
||||||
|
/** @expose **/
|
||||||
|
exports.combine = function(shares){
|
||||||
|
return combine(0, shares);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate a new share with id `id` (a number between 1 and 2^bits-1)
|
||||||
|
// `id` can be a Number or a String in the default radix (16)
|
||||||
|
/** @expose **/
|
||||||
|
exports.newShare = function(id, shares){
|
||||||
|
if(typeof id === 'string'){
|
||||||
|
id = parseInt(id, config.radix);
|
||||||
|
}
|
||||||
|
|
||||||
|
var share = processShare(shares[0]);
|
||||||
|
var max = Math.pow(2, share['bits']) - 1;
|
||||||
|
|
||||||
|
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
|
||||||
|
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = max.toString(config.radix).length;
|
||||||
|
return config.bits.toString(36).toUpperCase() + padLeft(id.toString(config.radix), padding) + combine(id, shares);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Evaluate the Lagrange interpolation polynomial at x = `at`
|
||||||
|
// using x and y Arrays that are of the same length, with
|
||||||
|
// corresponding elements constituting points on the polynomial.
|
||||||
|
function lagrange(at, x, y){
|
||||||
|
var sum = 0,
|
||||||
|
product,
|
||||||
|
i, j;
|
||||||
|
|
||||||
|
for(var i=0, len = x.length; i<len; i++){
|
||||||
|
if(!y[i]){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
product = config.logs[y[i]];
|
||||||
|
for(var j=0; j<len; j++){
|
||||||
|
if(i === j){ continue; }
|
||||||
|
if(at === x[j]){ // happens when computing a share that is in the list of shares used to compute it
|
||||||
|
product = -1; // fix for a zero product term, after which the sum should be sum^0 = sum, not sum^1
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
product = ( product + config.logs[at ^ x[j]] - config.logs[x[i] ^ x[j]] + config.max/* to make sure it's not negative */ ) % config.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = product === -1 ? sum : sum ^ config.exps[product]; // though exps[-1]= undefined and undefined ^ anything = anything in chrome, this behavior may not hold everywhere, so do the check
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports._lagrange = lagrange;
|
||||||
|
|
||||||
|
// Splits a number string `bits`-length segments, after first
|
||||||
|
// optionally zero-padding it to a length that is a multiple of `padLength.
|
||||||
|
// Returns array of integers (each less than 2^bits-1), with each element
|
||||||
|
// representing a `bits`-length segment of the input string from right to left,
|
||||||
|
// i.e. parts[0] represents the right-most `bits`-length segment of the input string.
|
||||||
|
function split(str, padLength){
|
||||||
|
if(padLength){
|
||||||
|
str = padLeft(str, padLength)
|
||||||
|
}
|
||||||
|
var parts = [];
|
||||||
|
for(var i=str.length; i>config.bits; i-=config.bits){
|
||||||
|
parts.push(parseInt(str.slice(i-config.bits, i), 2));
|
||||||
|
}
|
||||||
|
parts.push(parseInt(str.slice(0, i), 2));
|
||||||
|
return parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pads a string `str` with zeros on the left so that its length is a multiple of `bits`
|
||||||
|
function padLeft(str, bits){
|
||||||
|
bits = bits || config.bits
|
||||||
|
var missing = str.length % bits;
|
||||||
|
return (missing ? new Array(bits - missing + 1).join('0') : '') + str;
|
||||||
|
};
|
||||||
|
|
||||||
|
function hex2bin(str){
|
||||||
|
var bin = '', num;
|
||||||
|
for(var i=str.length - 1; i>=0; i--){
|
||||||
|
num = parseInt(str[i], 16)
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid hex character.')
|
||||||
|
}
|
||||||
|
bin = padLeft(num.toString(2), 4) + bin;
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bin2hex(str){
|
||||||
|
var hex = '', num;
|
||||||
|
str = padLeft(str, 4);
|
||||||
|
for(var i=str.length; i>=4; i-=4){
|
||||||
|
num = parseInt(str.slice(i-4, i), 2);
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid binary character.')
|
||||||
|
}
|
||||||
|
hex = num.toString(16) + hex;
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a given UTF16 character string to the HEX representation.
|
||||||
|
// Each character of the input string is represented by
|
||||||
|
// `bytesPerChar` bytes in the output string.
|
||||||
|
/** @expose **/
|
||||||
|
exports.str2hex = function(str, bytesPerChar){
|
||||||
|
if(typeof str !== 'string'){
|
||||||
|
throw new Error('Input must be a character string.');
|
||||||
|
}
|
||||||
|
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
|
||||||
|
|
||||||
|
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
|
||||||
|
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var hexChars = 2*bytesPerChar;
|
||||||
|
var max = Math.pow(16, hexChars) - 1;
|
||||||
|
var out = '', num;
|
||||||
|
for(var i=0, len=str.length; i<len; i++){
|
||||||
|
num = str[i].charCodeAt();
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid character: ' + str[i]);
|
||||||
|
}else if(num > max){
|
||||||
|
var neededBytes = Math.ceil(Math.log(num+1)/Math.log(256));
|
||||||
|
throw new Error('Invalid character code (' + num +'). Maximum allowable is 256^bytes-1 (' + max + '). To convert this character, use at least ' + neededBytes + ' bytes.')
|
||||||
|
}else{
|
||||||
|
out = padLeft(num.toString(16), hexChars) + out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converts a given HEX number string to a UTF16 character string.
|
||||||
|
/** @expose **/
|
||||||
|
exports.hex2str = function(str, bytesPerChar){
|
||||||
|
if(typeof str !== 'string'){
|
||||||
|
throw new Error('Input must be a hexadecimal string.');
|
||||||
|
}
|
||||||
|
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
|
||||||
|
|
||||||
|
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
|
||||||
|
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var hexChars = 2*bytesPerChar;
|
||||||
|
var out = '';
|
||||||
|
str = padLeft(str, hexChars);
|
||||||
|
for(var i=0, len = str.length; i<len; i+=hexChars){
|
||||||
|
out = String.fromCharCode(parseInt(str.slice(i, i+hexChars),16)) + out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// by default, initialize without an RNG
|
||||||
|
exports.init();
|
||||||
|
})(typeof module !== 'undefined' && module['exports'] ? module['exports'] : (window['secrets'] = {}), typeof GLOBAL !== 'undefined' ? GLOBAL : window );
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
/*!
|
/*!
|
||||||
* Basic JavaScript BN library - subset useful for RSA encryption. v1.3
|
* Basic JavaScript BN library - subset useful for RSA encryption. v1.3
|
||||||
*
|
*
|
||||||
|
@ -5983,6 +6517,7 @@ body { font-family: Arial; }
|
||||||
<div class="tab" id="bulkwallet" onclick="ninja.tabSwitch(this);">Bulk Wallet</div>
|
<div class="tab" id="bulkwallet" onclick="ninja.tabSwitch(this);">Bulk Wallet</div>
|
||||||
<div class="tab" id="brainwallet" onclick="ninja.tabSwitch(this);">Brain Wallet</div>
|
<div class="tab" id="brainwallet" onclick="ninja.tabSwitch(this);">Brain Wallet</div>
|
||||||
<div class="tab" id="vanitywallet" onclick="ninja.tabSwitch(this);">Vanity Wallet</div>
|
<div class="tab" id="vanitywallet" onclick="ninja.tabSwitch(this);">Vanity Wallet</div>
|
||||||
|
<div class="tab" id="splitwallet" onclick="ninja.tabSwitch(this);">Split Wallet</div>
|
||||||
<div class="tab" id="detailwallet" onclick="ninja.tabSwitch(this);">Wallet Details</div>
|
<div class="tab" id="detailwallet" onclick="ninja.tabSwitch(this);">Wallet Details</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -6183,7 +6718,28 @@ body { font-family: Arial; }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="splitarea" class="walletarea">
|
||||||
|
<div id="splitcommands" class="left">
|
||||||
|
<span>
|
||||||
|
<label id="splitlabelshares">Number of shares</label>
|
||||||
|
<input type="text" id="splitshares" value="4" size="4"/><br/>
|
||||||
|
<label id="splitlabelthreshhold">Minimum share threshhold needed to combine</label>
|
||||||
|
<input type="text" id="splitthreshhold" value="2" size="4"/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span><input type="button" id="splitview" value="Generate" onclick="ninja.wallets.splitwallet.splitKey();"></span>
|
||||||
|
</div>
|
||||||
|
<div id="combinecommands" class="left commands">
|
||||||
|
<span>
|
||||||
|
<label id="combinelabelentershares">Enter Available Shares (whitespace separated)</label>
|
||||||
|
<textarea id="combineinput" cols="60" rows="10"></textarea><br/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<input type="button" id="combineview" value="Combine Shares" onclick="ninja.wallets.splitwallet.combineShares();">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="detailarea" class="walletarea">
|
<div id="detailarea" class="walletarea">
|
||||||
<div id="detailcommands" class="commands">
|
<div id="detailcommands" class="commands">
|
||||||
<span><label id="detaillabelenterprivatekey" for="detailprivkey">Enter Private Key</label></span>
|
<span><label id="detaillabelenterprivatekey" for="detailprivkey">Enter Private Key</label></span>
|
||||||
|
@ -6272,6 +6828,7 @@ body { font-family: Arial; }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="footer" class="footer">
|
<div id="footer" class="footer">
|
||||||
<div class="authorbtc">
|
<div class="authorbtc">
|
||||||
<div>
|
<div>
|
||||||
|
@ -8474,6 +9031,115 @@ ninja.wallets.detailwallet = {
|
||||||
document.getElementById("detailbip38").style.display = "none";
|
document.getElementById("detailbip38").style.display = "none";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
ninja.wallets.splitwallet = {
|
||||||
|
open: function () {
|
||||||
|
document.getElementById("splitarea").style.display = "block";
|
||||||
|
secrets.setRNG();
|
||||||
|
secrets.init(3);
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function () {
|
||||||
|
document.getElementById("splitarea").style.display = "none";
|
||||||
|
},
|
||||||
|
|
||||||
|
mkOutputRow: function(s,id,lbltxt) {
|
||||||
|
var row = document.createElement("span");
|
||||||
|
var label = document.createElement("label");
|
||||||
|
label.innerHTML= lbltxt + s;
|
||||||
|
var qr = document.createElement("div");
|
||||||
|
|
||||||
|
qr.setAttribute("id", id);
|
||||||
|
row.appendChild(label);
|
||||||
|
row.appendChild(qr);
|
||||||
|
row.appendChild(document.createElement("br"));
|
||||||
|
|
||||||
|
return row;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
stripLeadZeros: function(hex) { return hex.split(/^0+/).slice(-1)[0]; },
|
||||||
|
|
||||||
|
hexToBytes: function (hex) {
|
||||||
|
//if input has odd number of digits, pad it
|
||||||
|
if (hex.length % 2 == 1)
|
||||||
|
hex = "0" + hex;
|
||||||
|
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||||
|
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Split a private key and update information in the HTML
|
||||||
|
splitKey: function () {
|
||||||
|
try {
|
||||||
|
var key = new Bitcoin.ECKey(false);
|
||||||
|
var bitcoinAddress = key.getBitcoinAddress();
|
||||||
|
var element = document.getElementById("splitoutput");
|
||||||
|
if (element != null) element.parentNode.removeChild(element);
|
||||||
|
|
||||||
|
var numshares = parseInt(document.getElementById('splitshares').value);
|
||||||
|
var threshhold = parseInt(document.getElementById('splitthreshhold').value);
|
||||||
|
var shares = secrets.share(Crypto.util.bytesToHex(key.getBitcoinPrivateKeyByteArray()),
|
||||||
|
numshares, threshhold).map(this.hexToBytes).map(Bitcoin.Base58.encode);
|
||||||
|
var output = document.createElement("div");
|
||||||
|
output.setAttribute("id", "splitoutput");
|
||||||
|
var m = {};
|
||||||
|
output.appendChild(this.mkOutputRow(bitcoinAddress, "split_addr", "Bitcoin Address: "));
|
||||||
|
m["split_addr"] = bitcoinAddress;
|
||||||
|
|
||||||
|
for (var i=0; i < shares.length; i++) {
|
||||||
|
var id = "split_qr_"+ i;
|
||||||
|
output.appendChild(this.mkOutputRow(shares[i], id, "Share " + (i+1) + ": "));
|
||||||
|
m[id]=shares[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("splitcommands").appendChild(output);
|
||||||
|
ninja.qrCode.showQrCode(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// browser does not have sufficient JavaScript support to generate a bitcoin address
|
||||||
|
alert(e);
|
||||||
|
document.getElementById("btcaddress").innerHTML = "error";
|
||||||
|
document.getElementById("btcprivwif").innerHTML = "error";
|
||||||
|
document.getElementById("qrcode_public").innerHTML = "";
|
||||||
|
document.getElementById("qrcode_private").innerHTML = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Combine shares of a private key to retrieve the key
|
||||||
|
combineShares: function () {
|
||||||
|
try {
|
||||||
|
var element = document.getElementById("combineoutput");
|
||||||
|
if (element != null) element.parentNode.removeChild(element);
|
||||||
|
|
||||||
|
var shares = document.getElementById("combineinput").value.trim().split(/\W+/);
|
||||||
|
|
||||||
|
var combined = secrets.combine(shares.map(Bitcoin.Base58.decode).
|
||||||
|
map(Crypto.util.bytesToHex).
|
||||||
|
map(this.stripLeadZeros));
|
||||||
|
|
||||||
|
var privkeyBase58 = new Bitcoin.ECKey(this.hexToBytes(combined)).getBitcoinWalletImportFormat();
|
||||||
|
var output = document.createElement("div");
|
||||||
|
output.setAttribute("id", "combineoutput");
|
||||||
|
var txt = document.createElement("input");
|
||||||
|
txt.setAttribute("id","combineoutputtext");
|
||||||
|
txt.setAttribute("value",privkeyBase58);
|
||||||
|
txt.setAttribute("size",55);
|
||||||
|
var lbl = document.createElement("label");
|
||||||
|
lbl.innerHTML="Private key";
|
||||||
|
output.appendChild(lbl);
|
||||||
|
output.appendChild(txt);
|
||||||
|
document.getElementById("combinecommands").appendChild(output);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
(function (ninja) {
|
(function (ninja) {
|
||||||
|
|
|
@ -80,6 +80,9 @@
|
||||||
//ellipticcurve.js
|
//ellipticcurve.js
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
//secrets.js
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
//biginteger.js
|
//biginteger.js
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -132,6 +135,7 @@
|
||||||
<div class="tab" id="bulkwallet" onclick="ninja.tabSwitch(this);">Bulk Wallet</div>
|
<div class="tab" id="bulkwallet" onclick="ninja.tabSwitch(this);">Bulk Wallet</div>
|
||||||
<div class="tab" id="brainwallet" onclick="ninja.tabSwitch(this);">Brain Wallet</div>
|
<div class="tab" id="brainwallet" onclick="ninja.tabSwitch(this);">Brain Wallet</div>
|
||||||
<div class="tab" id="vanitywallet" onclick="ninja.tabSwitch(this);">Vanity Wallet</div>
|
<div class="tab" id="vanitywallet" onclick="ninja.tabSwitch(this);">Vanity Wallet</div>
|
||||||
|
<div class="tab" id="splitwallet" onclick="ninja.tabSwitch(this);">Split Wallet</div>
|
||||||
<div class="tab" id="detailwallet" onclick="ninja.tabSwitch(this);">Wallet Details</div>
|
<div class="tab" id="detailwallet" onclick="ninja.tabSwitch(this);">Wallet Details</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -332,7 +336,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="splitarea" class="walletarea">
|
||||||
|
<div id="splitcommands" class="left">
|
||||||
|
<span>
|
||||||
|
<label id="splitlabelshares">Number of shares</label>
|
||||||
|
<input type="text" id="splitshares" value="4" size="4"/><br/>
|
||||||
|
<label id="splitlabelthreshhold">Minimum share threshhold needed to combine</label>
|
||||||
|
<input type="text" id="splitthreshhold" value="2" size="4"/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span><input type="button" id="splitview" value="Generate" onclick="ninja.wallets.splitwallet.splitKey();"></span>
|
||||||
|
</div>
|
||||||
|
<div id="combinecommands" class="left commands">
|
||||||
|
<span>
|
||||||
|
<label id="combinelabelentershares">Enter Available Shares (whitespace separated)</label>
|
||||||
|
<textarea id="combineinput" cols="60" rows="10"></textarea><br/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<input type="button" id="combineview" value="Combine Shares" onclick="ninja.wallets.splitwallet.combineShares();">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="detailarea" class="walletarea">
|
<div id="detailarea" class="walletarea">
|
||||||
<div id="detailcommands" class="commands">
|
<div id="detailcommands" class="commands">
|
||||||
<span><label id="detaillabelenterprivatekey" for="detailprivkey">Enter Private Key</label></span>
|
<span><label id="detaillabelenterprivatekey" for="detailprivkey">Enter Private Key</label></span>
|
||||||
|
@ -421,6 +446,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="footer" class="footer">
|
<div id="footer" class="footer">
|
||||||
<div class="authorbtc">
|
<div class="authorbtc">
|
||||||
<div>
|
<div>
|
||||||
|
@ -473,6 +499,9 @@
|
||||||
//ninja.detailwallet.js
|
//ninja.detailwallet.js
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
//ninja.splitwallet.js
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
//ninja.unittests.js
|
//ninja.unittests.js
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
106
src/ninja.splitwallet.js
Normal file
106
src/ninja.splitwallet.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
ninja.wallets.splitwallet = {
|
||||||
|
open: function () {
|
||||||
|
document.getElementById("splitarea").style.display = "block";
|
||||||
|
secrets.setRNG();
|
||||||
|
secrets.init(7);
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function () {
|
||||||
|
document.getElementById("splitarea").style.display = "none";
|
||||||
|
},
|
||||||
|
|
||||||
|
mkOutputRow: function(s,id,lbltxt) {
|
||||||
|
var row = document.createElement("span");
|
||||||
|
var label = document.createElement("label");
|
||||||
|
label.innerHTML= lbltxt + s;
|
||||||
|
var qr = document.createElement("div");
|
||||||
|
|
||||||
|
qr.setAttribute("id", id);
|
||||||
|
row.appendChild(label);
|
||||||
|
row.appendChild(qr);
|
||||||
|
row.appendChild(document.createElement("br"));
|
||||||
|
|
||||||
|
return row;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
stripLeadZeros: function(hex) { return hex.split(/^0+/).slice(-1)[0]; },
|
||||||
|
|
||||||
|
hexToBytes: function (hex) {
|
||||||
|
//if input has odd number of digits, pad it
|
||||||
|
if (hex.length % 2 == 1)
|
||||||
|
hex = "0" + hex;
|
||||||
|
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||||
|
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Split a private key and update information in the HTML
|
||||||
|
splitKey: function () {
|
||||||
|
try {
|
||||||
|
var key = new Bitcoin.ECKey(false);
|
||||||
|
var bitcoinAddress = key.getBitcoinAddress();
|
||||||
|
var element = document.getElementById("splitoutput");
|
||||||
|
if (element != null) element.parentNode.removeChild(element);
|
||||||
|
|
||||||
|
var numshares = parseInt(document.getElementById('splitshares').value);
|
||||||
|
var threshhold = parseInt(document.getElementById('splitthreshhold').value);
|
||||||
|
var shares = secrets.share(Crypto.util.bytesToHex(key.getBitcoinPrivateKeyByteArray()),
|
||||||
|
numshares, threshhold).map(this.hexToBytes).map(Bitcoin.Base58.encode);
|
||||||
|
var output = document.createElement("div");
|
||||||
|
output.setAttribute("id", "splitoutput");
|
||||||
|
var m = {};
|
||||||
|
output.appendChild(this.mkOutputRow(bitcoinAddress, "split_addr", "Bitcoin Address: "));
|
||||||
|
m["split_addr"] = bitcoinAddress;
|
||||||
|
|
||||||
|
for (var i=0; i < shares.length; i++) {
|
||||||
|
var id = "split_qr_"+ i;
|
||||||
|
output.appendChild(this.mkOutputRow(shares[i], id, "Share " + (i+1) + ": "));
|
||||||
|
m[id]=shares[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("splitcommands").appendChild(output);
|
||||||
|
ninja.qrCode.showQrCode(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// browser does not have sufficient JavaScript support to generate a bitcoin address
|
||||||
|
alert(e);
|
||||||
|
document.getElementById("btcaddress").innerHTML = "error";
|
||||||
|
document.getElementById("btcprivwif").innerHTML = "error";
|
||||||
|
document.getElementById("qrcode_public").innerHTML = "";
|
||||||
|
document.getElementById("qrcode_private").innerHTML = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Combine shares of a private key to retrieve the key
|
||||||
|
combineShares: function () {
|
||||||
|
try {
|
||||||
|
var element = document.getElementById("combineoutput");
|
||||||
|
if (element != null) element.parentNode.removeChild(element);
|
||||||
|
|
||||||
|
var shares = document.getElementById("combineinput").value.trim().split(/\W+/);
|
||||||
|
|
||||||
|
var combined = secrets.combine(shares.map(Bitcoin.Base58.decode).
|
||||||
|
map(Crypto.util.bytesToHex).
|
||||||
|
map(this.stripLeadZeros));
|
||||||
|
|
||||||
|
var privkeyBase58 = new Bitcoin.ECKey(this.hexToBytes(combined)).getBitcoinWalletImportFormat();
|
||||||
|
var output = document.createElement("div");
|
||||||
|
output.setAttribute("id", "combineoutput");
|
||||||
|
var txt = document.createElement("input");
|
||||||
|
txt.setAttribute("id","combineoutputtext");
|
||||||
|
txt.setAttribute("value",privkeyBase58);
|
||||||
|
txt.setAttribute("size",55);
|
||||||
|
var lbl = document.createElement("label");
|
||||||
|
lbl.innerHTML="Private key";
|
||||||
|
output.appendChild(lbl);
|
||||||
|
output.appendChild(txt);
|
||||||
|
document.getElementById("combinecommands").appendChild(output);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
532
src/secrets.js
Normal file
532
src/secrets.js
Normal file
|
@ -0,0 +1,532 @@
|
||||||
|
// secrets.js - by Alexander Stetsyuk - released under MIT License
|
||||||
|
(function(exports, global){
|
||||||
|
var defaults = {
|
||||||
|
bits: 8, // default number of bits
|
||||||
|
radix: 16, // work with HEX by default
|
||||||
|
minBits: 3,
|
||||||
|
maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS!
|
||||||
|
|
||||||
|
bytesPerChar: 2,
|
||||||
|
maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53)
|
||||||
|
|
||||||
|
// Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30
|
||||||
|
// The index of each term in the array corresponds to the n for that polynomial
|
||||||
|
// i.e. to get the polynomial for n=16, use primitivePolynomials[16]
|
||||||
|
primitivePolynomials: [null,null,1,3,3,5,3,3,29,17,9,5,83,27,43,3,45,9,39,39,9,5,3,33,27,9,71,39,9,5,83],
|
||||||
|
|
||||||
|
// warning for insecure PRNG
|
||||||
|
warning: 'WARNING:\nA secure random number generator was not found.\nUsing Math.random(), which is NOT cryptographically strong!'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Protected settings object
|
||||||
|
var config = {};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports.getConfig = function(){
|
||||||
|
return {
|
||||||
|
'bits': config.bits,
|
||||||
|
'unsafePRNG': config.unsafePRNG
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function init(bits){
|
||||||
|
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
|
||||||
|
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
config.radix = defaults.radix;
|
||||||
|
config.bits = bits || defaults.bits;
|
||||||
|
config.size = Math.pow(2, config.bits);
|
||||||
|
config.max = config.size - 1;
|
||||||
|
|
||||||
|
// Construct the exp and log tables for multiplication.
|
||||||
|
var logs = [], exps = [], x = 1, primitive = defaults.primitivePolynomials[config.bits];
|
||||||
|
for(var i=0; i<config.size; i++){
|
||||||
|
exps[i] = x;
|
||||||
|
logs[x] = i;
|
||||||
|
x <<= 1;
|
||||||
|
if(x >= config.size){
|
||||||
|
x ^= primitive;
|
||||||
|
x &= config.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.logs = logs;
|
||||||
|
config.exps = exps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports.init = init;
|
||||||
|
|
||||||
|
function isInited(){
|
||||||
|
if(!config.bits || !config.size || !config.max || !config.logs || !config.exps || config.logs.length !== config.size || config.exps.length !== config.size){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a pseudo-random number generator of the form function(bits){}
|
||||||
|
// which should output a random string of 1's and 0's of length `bits`
|
||||||
|
function getRNG(){
|
||||||
|
var randomBits, crypto;
|
||||||
|
|
||||||
|
function construct(bits, arr, radix, size){
|
||||||
|
var str = '',
|
||||||
|
i = 0,
|
||||||
|
len = arr.length-1;
|
||||||
|
while( i<len || (str.length < bits) ){
|
||||||
|
str += padLeft(parseInt(arr[i], radix).toString(2), size);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
str = str.substr(-bits);
|
||||||
|
if( (str.match(/0/g)||[]).length === str.length){ // all zeros?
|
||||||
|
return null;
|
||||||
|
}else{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// node.js crypto.randomBytes()
|
||||||
|
if(typeof require === 'function' && (crypto=require('crypto')) && (randomBits=crypto['randomBytes'])){
|
||||||
|
return function(bits){
|
||||||
|
var bytes = Math.ceil(bits/8),
|
||||||
|
str = null;
|
||||||
|
|
||||||
|
while( str === null ){
|
||||||
|
str = construct(bits, randomBits(bytes).toString('hex'), 16, 4);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// browsers with window.crypto.getRandomValues()
|
||||||
|
if(global['crypto'] && typeof global['crypto']['getRandomValues'] === 'function' && typeof global['Uint32Array'] === 'function'){
|
||||||
|
crypto = global['crypto'];
|
||||||
|
return function(bits){
|
||||||
|
var elems = Math.ceil(bits/32),
|
||||||
|
str = null,
|
||||||
|
arr = new global['Uint32Array'](elems);
|
||||||
|
|
||||||
|
while( str === null ){
|
||||||
|
crypto['getRandomValues'](arr);
|
||||||
|
str = construct(bits, arr, 10, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A totally insecure RNG!!! (except in Safari)
|
||||||
|
// Will produce a warning every time it is called.
|
||||||
|
config.unsafePRNG = true;
|
||||||
|
warn();
|
||||||
|
|
||||||
|
var bitsPerNum = 32;
|
||||||
|
var max = Math.pow(2,bitsPerNum)-1;
|
||||||
|
return function(bits){
|
||||||
|
var elems = Math.ceil(bits/bitsPerNum);
|
||||||
|
var arr = [], str=null;
|
||||||
|
while(str===null){
|
||||||
|
for(var i=0; i<elems; i++){
|
||||||
|
arr[i] = Math.floor(Math.random() * max + 1);
|
||||||
|
}
|
||||||
|
str = construct(bits, arr, 10, bitsPerNum);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Warn about using insecure rng.
|
||||||
|
// Called when Math.random() is being used.
|
||||||
|
function warn(){
|
||||||
|
global['console']['warn'](defaults.warning);
|
||||||
|
if(typeof global['alert'] === 'function' && config.alert){
|
||||||
|
global['alert'](defaults.warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG()
|
||||||
|
/** @expose **/
|
||||||
|
exports.setRNG = function(rng, alert){
|
||||||
|
if(!isInited()){
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
config.unsafePRNG=false;
|
||||||
|
rng = rng || getRNG();
|
||||||
|
|
||||||
|
// test the RNG (5 times)
|
||||||
|
if(typeof rng !== 'function' || typeof rng(config.bits) !== 'string' || !parseInt(rng(config.bits),2) || rng(config.bits).length > config.bits || rng(config.bits).length < config.bits){
|
||||||
|
throw new Error("Random number generator is invalid. Supply an RNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's.")
|
||||||
|
}else{
|
||||||
|
config.rng = rng;
|
||||||
|
}
|
||||||
|
config.alert = !!alert;
|
||||||
|
|
||||||
|
return !!config.unsafePRNG;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSetRNG(){
|
||||||
|
return typeof config.rng === 'function';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generates a random bits-length number string using the PRNG
|
||||||
|
/** @expose **/
|
||||||
|
exports.random = function(bits){
|
||||||
|
if(!isSetRNG()){
|
||||||
|
this.setRNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof bits !== 'number' || bits%1 !== 0 || bits < 2){
|
||||||
|
throw new Error('Number of bits must be an integer greater than 1.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.unsafePRNG){
|
||||||
|
warn();
|
||||||
|
}
|
||||||
|
return bin2hex(config.rng(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16)
|
||||||
|
// into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`),
|
||||||
|
// requiring `threshold` number of shares to reconstruct the secret.
|
||||||
|
// Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing.
|
||||||
|
/** @expose **/
|
||||||
|
exports.share = function(secret, numShares, threshold, padLength, withoutPrefix){
|
||||||
|
if(!isInited()){
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
if(!isSetRNG()){
|
||||||
|
this.setRNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
padLength = padLength || 0;
|
||||||
|
|
||||||
|
if(typeof secret !== 'string'){
|
||||||
|
throw new Error('Secret must be a string.');
|
||||||
|
}
|
||||||
|
if(typeof numShares !== 'number' || numShares%1 !== 0 || numShares < 2){
|
||||||
|
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.')
|
||||||
|
}
|
||||||
|
if(numShares > config.max){
|
||||||
|
var neededBits = Math.ceil(Math.log(numShares +1)/Math.LN2);
|
||||||
|
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To create ' + numShares + ' shares, use at least ' + neededBits + ' bits.')
|
||||||
|
}
|
||||||
|
if(typeof threshold !== 'number' || threshold%1 !== 0 || threshold < 2){
|
||||||
|
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.');
|
||||||
|
}
|
||||||
|
if(threshold > config.max){
|
||||||
|
var neededBits = Math.ceil(Math.log(threshold +1)/Math.LN2);
|
||||||
|
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To use a threshold of ' + threshold + ', use at least ' + neededBits + ' bits.');
|
||||||
|
}
|
||||||
|
if(typeof padLength !== 'number' || padLength%1 !== 0 ){
|
||||||
|
throw new Error('Zero-pad length must be an integer greater than 1.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.unsafePRNG){
|
||||||
|
warn();
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = '1' + hex2bin(secret); // append a 1 so that we can preserve the correct number of leading zeros in our secret
|
||||||
|
secret = split(secret, padLength);
|
||||||
|
var x = new Array(numShares), y = new Array(numShares);
|
||||||
|
for(var i=0, len = secret.length; i<len; i++){
|
||||||
|
var subShares = this._getShares(secret[i], numShares, threshold);
|
||||||
|
for(var j=0; j<numShares; j++){
|
||||||
|
x[j] = x[j] || subShares[j].x.toString(config.radix);
|
||||||
|
y[j] = padLeft(subShares[j].y.toString(2)) + (y[j] ? y[j] : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var padding = config.max.toString(config.radix).length;
|
||||||
|
if(withoutPrefix){
|
||||||
|
for(var i=0; i<numShares; i++){
|
||||||
|
x[i] = bin2hex(y[i]);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
for(var i=0; i<numShares; i++){
|
||||||
|
x[i] = config.bits.toString(36).toUpperCase() + padLeft(x[i],padding) + bin2hex(y[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the basic polynomial generation and evaluation function
|
||||||
|
// for a `config.bits`-length secret (NOT an arbitrary length)
|
||||||
|
// Note: no error-checking at this stage! If `secrets` is NOT
|
||||||
|
// a NUMBER less than 2^bits-1, the output will be incorrect!
|
||||||
|
/** @expose **/
|
||||||
|
exports._getShares = function(secret, numShares, threshold){
|
||||||
|
var shares = [];
|
||||||
|
var coeffs = [secret];
|
||||||
|
|
||||||
|
for(var i=1; i<threshold; i++){
|
||||||
|
coeffs[i] = parseInt(config.rng(config.bits),2);
|
||||||
|
}
|
||||||
|
for(var i=1, len = numShares+1; i<len; i++){
|
||||||
|
shares[i-1] = {
|
||||||
|
x: i,
|
||||||
|
y: horner(i, coeffs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shares;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Polynomial evaluation at `x` using Horner's Method
|
||||||
|
// TODO: this can possibly be sped up using other methods
|
||||||
|
// NOTE: fx=fx * x + coeff[i] -> exp(log(fx) + log(x)) + coeff[i],
|
||||||
|
// so if fx===0, just set fx to coeff[i] because
|
||||||
|
// using the exp/log form will result in incorrect value
|
||||||
|
function horner(x, coeffs){
|
||||||
|
var logx = config.logs[x];
|
||||||
|
var fx = 0;
|
||||||
|
for(var i=coeffs.length-1; i>=0; i--){
|
||||||
|
if(fx === 0){
|
||||||
|
fx = coeffs[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fx = config.exps[ (logx + config.logs[fx]) % config.max ] ^ coeffs[i];
|
||||||
|
}
|
||||||
|
return fx;
|
||||||
|
};
|
||||||
|
|
||||||
|
function inArray(arr,val){
|
||||||
|
for(var i = 0,len=arr.length; i < len; i++) {
|
||||||
|
if(arr[i] === val){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function processShare(share){
|
||||||
|
|
||||||
|
var bits = parseInt(share[0], 36);
|
||||||
|
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
|
||||||
|
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var max = Math.pow(2, bits) - 1;
|
||||||
|
var idLength = max.toString(config.radix).length;
|
||||||
|
|
||||||
|
var id = parseInt(share.substr(1, idLength), config.radix);
|
||||||
|
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
|
||||||
|
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
|
||||||
|
}
|
||||||
|
share = share.substr(idLength + 1);
|
||||||
|
if(!share.length){
|
||||||
|
throw new Error('Invalid share: zero-length share.')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'bits': bits,
|
||||||
|
'id': id,
|
||||||
|
'value': share
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports._processShare = processShare;
|
||||||
|
|
||||||
|
// Protected method that evaluates the Lagrange interpolation
|
||||||
|
// polynomial at x=`at` for individual config.bits-length
|
||||||
|
// segments of each share in the `shares` Array.
|
||||||
|
// Each share is expressed in base `inputRadix`. The output
|
||||||
|
// is expressed in base `outputRadix'
|
||||||
|
function combine(at, shares){
|
||||||
|
var setBits, share, x = [], y = [], result = '', idx;
|
||||||
|
|
||||||
|
for(var i=0, len = shares.length; i<len; i++){
|
||||||
|
share = processShare(shares[i]);
|
||||||
|
if(typeof setBits === 'undefined'){
|
||||||
|
setBits = share['bits'];
|
||||||
|
}else if(share['bits'] !== setBits){
|
||||||
|
throw new Error('Mismatched shares: Different bit settings.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.bits !== setBits){
|
||||||
|
init(setBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inArray(x, share['id'])){ // repeated x value?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = x.push(share['id']) - 1;
|
||||||
|
share = split(hex2bin(share['value']));
|
||||||
|
for(var j=0, len2 = share.length; j<len2; j++){
|
||||||
|
y[j] = y[j] || [];
|
||||||
|
y[j][idx] = share[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i=0, len=y.length; i<len; i++){
|
||||||
|
result = padLeft(lagrange(at, x, y[i]).toString(2)) + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(at===0){// reconstructing the secret
|
||||||
|
var idx = result.indexOf('1'); //find the first 1
|
||||||
|
return bin2hex(result.slice(idx+1));
|
||||||
|
}else{// generating a new share
|
||||||
|
return bin2hex(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine `shares` Array into the original secret
|
||||||
|
/** @expose **/
|
||||||
|
exports.combine = function(shares){
|
||||||
|
return combine(0, shares);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate a new share with id `id` (a number between 1 and 2^bits-1)
|
||||||
|
// `id` can be a Number or a String in the default radix (16)
|
||||||
|
/** @expose **/
|
||||||
|
exports.newShare = function(id, shares){
|
||||||
|
if(typeof id === 'string'){
|
||||||
|
id = parseInt(id, config.radix);
|
||||||
|
}
|
||||||
|
|
||||||
|
var share = processShare(shares[0]);
|
||||||
|
var max = Math.pow(2, share['bits']) - 1;
|
||||||
|
|
||||||
|
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
|
||||||
|
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = max.toString(config.radix).length;
|
||||||
|
return config.bits.toString(36).toUpperCase() + padLeft(id.toString(config.radix), padding) + combine(id, shares);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Evaluate the Lagrange interpolation polynomial at x = `at`
|
||||||
|
// using x and y Arrays that are of the same length, with
|
||||||
|
// corresponding elements constituting points on the polynomial.
|
||||||
|
function lagrange(at, x, y){
|
||||||
|
var sum = 0,
|
||||||
|
product,
|
||||||
|
i, j;
|
||||||
|
|
||||||
|
for(var i=0, len = x.length; i<len; i++){
|
||||||
|
if(!y[i]){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
product = config.logs[y[i]];
|
||||||
|
for(var j=0; j<len; j++){
|
||||||
|
if(i === j){ continue; }
|
||||||
|
if(at === x[j]){ // happens when computing a share that is in the list of shares used to compute it
|
||||||
|
product = -1; // fix for a zero product term, after which the sum should be sum^0 = sum, not sum^1
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
product = ( product + config.logs[at ^ x[j]] - config.logs[x[i] ^ x[j]] + config.max/* to make sure it's not negative */ ) % config.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = product === -1 ? sum : sum ^ config.exps[product]; // though exps[-1]= undefined and undefined ^ anything = anything in chrome, this behavior may not hold everywhere, so do the check
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports._lagrange = lagrange;
|
||||||
|
|
||||||
|
// Splits a number string `bits`-length segments, after first
|
||||||
|
// optionally zero-padding it to a length that is a multiple of `padLength.
|
||||||
|
// Returns array of integers (each less than 2^bits-1), with each element
|
||||||
|
// representing a `bits`-length segment of the input string from right to left,
|
||||||
|
// i.e. parts[0] represents the right-most `bits`-length segment of the input string.
|
||||||
|
function split(str, padLength){
|
||||||
|
if(padLength){
|
||||||
|
str = padLeft(str, padLength)
|
||||||
|
}
|
||||||
|
var parts = [];
|
||||||
|
for(var i=str.length; i>config.bits; i-=config.bits){
|
||||||
|
parts.push(parseInt(str.slice(i-config.bits, i), 2));
|
||||||
|
}
|
||||||
|
parts.push(parseInt(str.slice(0, i), 2));
|
||||||
|
return parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pads a string `str` with zeros on the left so that its length is a multiple of `bits`
|
||||||
|
function padLeft(str, bits){
|
||||||
|
bits = bits || config.bits
|
||||||
|
var missing = str.length % bits;
|
||||||
|
return (missing ? new Array(bits - missing + 1).join('0') : '') + str;
|
||||||
|
};
|
||||||
|
|
||||||
|
function hex2bin(str){
|
||||||
|
var bin = '', num;
|
||||||
|
for(var i=str.length - 1; i>=0; i--){
|
||||||
|
num = parseInt(str[i], 16)
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid hex character.')
|
||||||
|
}
|
||||||
|
bin = padLeft(num.toString(2), 4) + bin;
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bin2hex(str){
|
||||||
|
var hex = '', num;
|
||||||
|
str = padLeft(str, 4);
|
||||||
|
for(var i=str.length; i>=4; i-=4){
|
||||||
|
num = parseInt(str.slice(i-4, i), 2);
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid binary character.')
|
||||||
|
}
|
||||||
|
hex = num.toString(16) + hex;
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a given UTF16 character string to the HEX representation.
|
||||||
|
// Each character of the input string is represented by
|
||||||
|
// `bytesPerChar` bytes in the output string.
|
||||||
|
/** @expose **/
|
||||||
|
exports.str2hex = function(str, bytesPerChar){
|
||||||
|
if(typeof str !== 'string'){
|
||||||
|
throw new Error('Input must be a character string.');
|
||||||
|
}
|
||||||
|
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
|
||||||
|
|
||||||
|
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
|
||||||
|
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var hexChars = 2*bytesPerChar;
|
||||||
|
var max = Math.pow(16, hexChars) - 1;
|
||||||
|
var out = '', num;
|
||||||
|
for(var i=0, len=str.length; i<len; i++){
|
||||||
|
num = str[i].charCodeAt();
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid character: ' + str[i]);
|
||||||
|
}else if(num > max){
|
||||||
|
var neededBytes = Math.ceil(Math.log(num+1)/Math.log(256));
|
||||||
|
throw new Error('Invalid character code (' + num +'). Maximum allowable is 256^bytes-1 (' + max + '). To convert this character, use at least ' + neededBytes + ' bytes.')
|
||||||
|
}else{
|
||||||
|
out = padLeft(num.toString(16), hexChars) + out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converts a given HEX number string to a UTF16 character string.
|
||||||
|
/** @expose **/
|
||||||
|
exports.hex2str = function(str, bytesPerChar){
|
||||||
|
if(typeof str !== 'string'){
|
||||||
|
throw new Error('Input must be a hexadecimal string.');
|
||||||
|
}
|
||||||
|
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
|
||||||
|
|
||||||
|
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
|
||||||
|
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var hexChars = 2*bytesPerChar;
|
||||||
|
var out = '';
|
||||||
|
str = padLeft(str, hexChars);
|
||||||
|
for(var i=0, len = str.length; i<len; i+=hexChars){
|
||||||
|
out = String.fromCharCode(parseInt(str.slice(i, i+hexChars),16)) + out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// by default, initialize without an RNG
|
||||||
|
exports.init();
|
||||||
|
})(typeof module !== 'undefined' && module['exports'] ? module['exports'] : (window['secrets'] = {}), typeof GLOBAL !== 'undefined' ? GLOBAL : window );
|
Loading…
Reference in a new issue