diff --git a/src/index.html b/src/index.html
index e54f716..93666e3 100644
--- a/src/index.html
+++ b/src/index.html
@@ -57,7 +57,7 @@
Wikipedia entry for shamir secret sharing scheme.
This project is 100% open-source code.
Project source code
- SSSS javascript library by amper5and
+ SSSS javascript library by amper5and and Glenn Rempe
Bootstrap stylesheet
Offline Usage
This tool has been designed to be used offline.
diff --git a/src/js/secrets.js b/src/js/secrets.js
index 29bd08f..4b479c3 100644
--- a/src/js/secrets.js
+++ b/src/js/secrets.js
@@ -1,532 +1,975 @@
-// 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!'
-};
+// @preserve author Alexander Stetsyuk
+// @preserve author Glenn Rempe
+// @license MIT
-// Protected settings object
-var config = {};
+/*jslint passfail: false, bitwise: true, nomen: true, plusplus: true, todo: false, maxerr: 1000 */
+/*global define, require, module, exports, window, Uint32Array, sjcl */
-/** @expose **/
-exports.getConfig = function(){
- return {
- 'bits': config.bits,
- 'unsafePRNG': config.unsafePRNG
- };
-};
+// eslint : http://eslint.org/docs/configuring/
+/*eslint-env node, browser, jasmine, sjcl */
+/*eslint no-underscore-dangle:0 */
-function init(bits){
- if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bitsdefaults.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){
- x ^= primitive;
- x &= config.max;
- }
- }
-
- config.logs = logs;
- config.exps = exps;
-};
+// UMD (Universal Module Definition)
+// Uses Node, AMD or browser globals to create a module. This module creates
+// a global even when AMD is used. This is useful if you have some scripts
+// that are loaded by an AMD loader, but they still want access to globals.
+// See : https://github.com/umdjs/umd
+// See : https://github.com/umdjs/umd/blob/master/returnExportsGlobal.js
+//
+(function (root, factory) {
+ "use strict";
-/** @expose **/
-exports.init = init;
+ if (typeof define === "function" && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], function () {
+ /*eslint-disable no-return-assign */
+ return (root.secrets = factory());
+ /*eslint-enable no-return-assign */
+ });
+ } else if (typeof exports === "object") {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory(require("crypto"));
+ } else {
+ // Browser globals (root is window)
+ root.secrets = factory(root.crypto);
+ }
+}(this, function (crypto) {
+ "use strict";
-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;
-};
+ var defaults,
+ config,
+ preGenPadding,
+ runCSPRNGTest,
+ sjclParanoia,
+ CSPRNGTypes;
-// 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 Math.pow(2,53)
- while( str === null ){
- crypto['getRandomValues'](arr);
- str = construct(bits, arr, 10, 32);
- }
-
- return str;
- }
- }
+ // 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]
+ };
+ config = {};
+ preGenPadding = new Array(1024).join("0"); // Pre-generate a string of 1024 0's for use by padLeft().
+ runCSPRNGTest = true;
+ sjclParanoia = 10;
- // 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 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;
-};
+ return false;
+ }
-function isSetRNG(){
- return typeof config.rng === 'function';
-};
+ // Pads a string `str` with zeros on the left so that its length is a multiple of `bits`
+ function padLeft(str, multipleOfBits) {
+ var missing;
-// 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));
-}
+ if (multipleOfBits === 0 || multipleOfBits === 1) {
+ return str;
+ }
-// 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 1024) {
+ throw new Error("Padding must be multiples of no larger than 1024 bits.");
+ }
-// 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 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;
-};
+ multipleOfBits = multipleOfBits || config.bits;
-function inArray(arr,val){
- for(var i = 0,len=arr.length; i < len; i++) {
- if(arr[i] === val){
- return true;
- }
- }
- return false;
-};
+ if (str) {
+ missing = str.length % multipleOfBits;
+ }
-function processShare(share){
-
- var bits = parseInt(share[0], 36);
- if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bitsdefaults.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
- };
-};
+ if (missing) {
+ return (preGenPadding + str).slice(-(multipleOfBits - missing + str.length));
+ }
-/** @expose **/
-exports._processShare = processShare;
+ return str;
+ }
-// 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= 0; i--) {
+ num = parseInt(str[i], 16);
-// Combine `shares` Array into the original secret
-/** @expose **/
-exports.combine = function(shares){
- return combine(0, shares);
-};
+ if (isNaN(num)) {
+ throw new Error("Invalid hex character.");
+ }
-// 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.');
- }
+ bin = padLeft(num.toString(2), 4) + bin;
+ }
+ return bin;
+ }
- 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; iconfig.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;
-};
+ for (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;
+ }
-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;
-}
+ return hex;
+ }
-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 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 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;
+ }
+
+ // Polynomial evaluation at `x` using Horner's Method
+ // 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],
+ fx = 0,
+ i;
+
+ for (i = coeffs.length - 1; i >= 0; i--) {
+ if (fx !== 0) {
+ fx = config.exps[(logx + config.logs[fx]) % config.maxShares] ^ coeffs[i];
+ } else {
+ fx = coeffs[i];
+ }
+ }
+
+ return fx;
+ }
+
+ // 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,
+ len,
+ product,
+ i,
+ j;
+
+ for (i = 0, len = x.length; i < len; i++) {
+ if (y[i]) {
+
+ product = config.logs[y[i]];
+
+ for (j = 0; j < len; j++) {
+ if (i !== j) {
+ 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.maxShares) % config.maxShares; // to make sure it's not negative
+ }
+ }
+
+ // though exps[-1] === undefined and undefined ^ anything = anything in
+ // chrome, this behavior may not hold everywhere, so do the check
+ sum = product === -1 ? sum : sum ^ config.exps[product];
+ }
+
+ }
+
+ return sum;
+ }
+
+ // 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 `secret` is NOT
+ // a NUMBER less than 2^bits-1, the output will be incorrect!
+ function getShares(secret, numShares, threshold) {
+ var shares = [],
+ coeffs = [secret],
+ i,
+ len;
+
+ for (i = 1; i < threshold; i++) {
+ coeffs[i] = parseInt(config.rng(config.bits), 2);
+ }
+
+ for (i = 1, len = numShares + 1; i < len; i++) {
+ shares[i - 1] = {
+ x: i,
+ y: horner(i, coeffs)
+ };
+ }
+
+ return shares;
+ }
+
+ function constructPublicShareString(bits, id, data) {
+ var bitsBase36,
+ idHex,
+ idMax,
+ idPaddingLen,
+ newShareString;
+
+ id = parseInt(id, config.radix);
+ bits = parseInt(bits, 10) || config.bits;
+ bitsBase36 = bits.toString(36).toUpperCase();
+ idMax = Math.pow(2, bits) - 1;
+ idPaddingLen = idMax.toString(config.radix).length;
+ idHex = padLeft(id.toString(config.radix), idPaddingLen);
+
+ if (typeof id !== "number" || id % 1 !== 0 || id < 1 || id > idMax) {
+ throw new Error("Share id must be an integer between 1 and " + idMax + ", inclusive.");
+ }
+
+ newShareString = bitsBase36 + idHex + data;
+
+ return newShareString;
+ }
+
+ // EXPORTED FUNCTIONS
+ // //////////////////
+
+ var secrets = {
+
+ init: function (bits, rngType) {
+ var logs = [],
+ exps = [],
+ x = 1,
+ primitive,
+ i;
+
+ // reset all config back to initial state
+ reset();
+
+ 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.");
+ }
+
+ if (rngType && CSPRNGTypes.indexOf(rngType) === -1) {
+ throw new Error("Invalid RNG type argument : '" + rngType + "'");
+ }
+
+ config.radix = defaults.radix;
+ config.bits = bits || defaults.bits;
+ config.size = Math.pow(2, config.bits);
+ config.maxShares = config.size - 1;
+
+ // Construct the exp and log tables for multiplication.
+ primitive = defaults.primitivePolynomials[config.bits];
+
+ for (i = 0; i < config.size; i++) {
+ exps[i] = x;
+ logs[x] = i;
+ x = x << 1; // Left shift assignment
+ if (x >= config.size) {
+ x = x ^ primitive; // Bitwise XOR assignment
+ x = x & config.maxShares; // Bitwise AND assignment
+ }
+ }
+
+ config.logs = logs;
+ config.exps = exps;
+
+ if (rngType) {
+ this.setRNG(rngType);
+ }
+
+ if (!isSetRNG()) {
+ this.setRNG();
+ }
+
+ // Setup SJCL and start collecting entropy from mouse movements
+ if (hasSJCL() && config.typeCSPRNG === "browserSJCLRandom") {
+ /*eslint-disable new-cap */
+ sjcl.random = new sjcl.prng(sjclParanoia);
+ /*eslint-enable new-cap */
+
+ // In a Browser
+ if (hasCryptoGetRandomValues()) {
+ // Collects entropy from browser mouse movement
+ // which obviously won't work in Node.js.
+ sjcl.random.startCollectors();
+ }
+
+ // see SJCL with browser or Node.js RNG if available.
+ this.seedRNG();
+ }
+
+ if (!isSetRNG() || !config.bits || !config.size || !config.maxShares || !config.logs || !config.exps || config.logs.length !== config.size || config.exps.length !== config.size) {
+ throw new Error("Initialization failed.");
+ }
+
+ },
+
+ // Pass in additional secure entropy, and an estimate of the bits of entropy
+ // provided, and a source name, and it will be used to seed the SJCL PRNG. This is
+ // useful since SJCL may take a while to be seeded since it depends on mouse
+ // movement and this can kickstart the generator almost immediately. SJCL will
+ // also continue to collect entropy from mouse movements after seeding.
+ //
+ // e.g. from random data sources like:
+ // https://api.random.org/json-rpc/1/
+ // https://entropy.ubuntu.com/?challenge=123
+ // https://qrng.anu.edu.au/API/api-demo.php
+ //
+ // See `examples/example_js_global.html` for sample usage with an
+ // external source of entropy.
+ seedRNG: function (data, estimatedEntropy, source) {
+
+ var bytes, rand;
+
+ estimatedEntropy = parseInt(estimatedEntropy, 10);
+ source = source || "seedRNG";
+
+ // Seed with browser RNG
+ if (hasSJCL() && hasCryptoGetRandomValues()) {
+ bytes = new Uint32Array(256);
+ rand = crypto.getRandomValues(bytes);
+ //console.log(rand);
+ sjcl.random.addEntropy(rand, 2048, "cryptoGetRandomValues");
+ }
+
+ // See with Node.js RNG (Async)
+ if (hasSJCL() && hasCryptoRandomBytes()) {
+ crypto.randomBytes(256, function(ex, buf) {
+ if (ex) { throw ex; }
+ //console.log("Have %d bytes of random data containing %s", buf.length, buf.toString('hex'));
+ sjcl.random.addEntropy(buf.toString("hex"), 2048, "cryptoRandomBytes");
+ });
+ }
+
+ if (hasSJCL() && data && estimatedEntropy && source && config.typeCSPRNG === "browserSJCLRandom") {
+ sjcl.random.addEntropy(data, estimatedEntropy, source);
+ }
+ },
+
+ // 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'.
+ combine: function (shares, at) {
+ var i,
+ j,
+ len,
+ len2,
+ result = "",
+ setBits,
+ share,
+ splitShare,
+ x = [],
+ y = [];
+
+ at = at || 0;
+
+ for (i = 0, len = shares.length; i < len; i++) {
+ share = this.extractShareComponents(shares[i]);
+
+ // All shares must have the same bits settings.
+ if (setBits === undefined) {
+ setBits = share.bits;
+ } else if (share.bits !== setBits) {
+ throw new Error("Mismatched shares: Different bit settings.");
+ }
+
+ // Reset everything to the bit settings of the shares.
+ if (config.bits !== setBits) {
+ this.init(setBits);
+ }
+
+ // Proceed if this share.id is not already in the Array 'x' and
+ // then split each share's hex data into an Array of Integers,
+ // then 'rotate' those arrays where the first element of each row is converted to
+ // its own array, the second element of each to its own Array, and so on for all of the rest.
+ // Essentially zipping all of the shares together.
+ //
+ // e.g.
+ // [ 193, 186, 29, 150, 5, 120, 44, 46, 49, 59, 6, 1, 102, 98, 177, 196 ]
+ // [ 53, 105, 139, 49, 187, 240, 91, 92, 98, 118, 12, 2, 204, 196, 127, 149 ]
+ // [ 146, 211, 249, 167, 209, 136, 118, 114, 83, 77, 10, 3, 170, 166, 206, 81 ]
+ //
+ // becomes:
+ //
+ // [ [ 193, 53, 146 ],
+ // [ 186, 105, 211 ],
+ // [ 29, 139, 249 ],
+ // [ 150, 49, 167 ],
+ // [ 5, 187, 209 ],
+ // [ 120, 240, 136 ],
+ // [ 44, 91, 118 ],
+ // [ 46, 92, 114 ],
+ // [ 49, 98, 83 ],
+ // [ 59, 118, 77 ],
+ // [ 6, 12, 10 ],
+ // [ 1, 2, 3 ],
+ // [ 102, 204, 170 ],
+ // [ 98, 196, 166 ],
+ // [ 177, 127, 206 ],
+ // [ 196, 149, 81 ] ]
+ //
+ if (x.indexOf(share.id) === -1) {
+ x.push(share.id);
+ splitShare = splitNumStringToIntArray(hex2bin(share.data));
+ for (j = 0, len2 = splitShare.length; j < len2; j++) {
+ y[j] = y[j] || [];
+ y[j][x.length - 1] = splitShare[j];
+ }
+ }
+
+ }
+
+ // Extract the secret from the 'rotated' share data and return a
+ // string of Binary digits which represent the secret directly. or in the
+ // case of a newShare() return the binary string representing just that
+ // new share.
+ for (i = 0, len = y.length; i < len; i++) {
+ result = padLeft(lagrange(at, x, y[i]).toString(2)) + result;
+ }
+
+ // If 'at' is non-zero combine() was called from newShare(). In this
+ // case return the result (the new share data) directly.
+ //
+ // Otherwise find the first '1' which was added in the share() function as a padding marker
+ // and return only the data after the padding and the marker. Convert this Binary string
+ // to hex, which represents the final secret result (which can be converted from hex back
+ // to the original string in user space using `hex2str()`).
+ return bin2hex(at >= 1 ? result : result.slice(result.indexOf("1") + 1));
+ },
+
+ getConfig: function () {
+ var obj = {};
+ obj.radix = config.radix;
+ obj.bits = config.bits;
+ obj.maxShares = config.maxShares;
+ obj.hasCSPRNG = isSetRNG();
+ obj.typeCSPRNG = config.typeCSPRNG;
+ return obj;
+ },
+
+ // Given a public share, extract the bits (Integer), share ID (Integer), and share data (Hex)
+ // and return an Object containing those components.
+ extractShareComponents: function (share) {
+ var bits,
+ id,
+ idLen,
+ max,
+ obj = {},
+ regexStr,
+ shareComponents;
+
+ // Extract the first char which represents the bits in Base 36
+ bits = parseInt(share.substr(0, 1), 36);
+
+ if (bits && (typeof bits !== "number" || bits % 1 !== 0 || bits < defaults.minBits || bits > defaults.maxBits)) {
+ throw new Error("Invalid share : Number of bits must be an integer between " + defaults.minBits + " and " + defaults.maxBits + ", inclusive.");
+ }
+
+ // calc the max shares allowed for given bits
+ max = Math.pow(2, bits) - 1;
+
+ // Determine the ID length which is variable and based on the bit count.
+ idLen = (Math.pow(2, bits) - 1).toString(config.radix).length;
+
+ // Extract all the parts now that the segment sizes are known.
+ regexStr = "^([a-kA-K3-9]{1})([a-fA-F0-9]{" + idLen + "})([a-fA-F0-9]+)$";
+ shareComponents = new RegExp(regexStr).exec(share);
+
+ // The ID is a Hex number and needs to be converted to an Integer
+ if (shareComponents) {
+ id = parseInt(shareComponents[2], config.radix);
+ }
+
+ if (typeof id !== "number" || id % 1 !== 0 || id < 1 || id > max) {
+ throw new Error("Invalid share : Share id must be an integer between 1 and " + config.maxShares + ", inclusive.");
+ }
+
+ if (shareComponents && shareComponents[3]) {
+ obj.bits = bits;
+ obj.id = id;
+ obj.data = shareComponents[3];
+ return obj;
+ }
+
+ throw new Error("The share data provided is invalid : " + share);
+
+ },
+
+ // Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG()
+ setRNG: function (rng) {
+
+ var errPrefix = "Random number generator is invalid ",
+ errSuffix = " Supply an CSPRNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's.";
+
+ if (rng && typeof rng === "string" && CSPRNGTypes.indexOf(rng) === -1) {
+ throw new Error("Invalid RNG type argument : '" + rng + "'");
+ }
+
+ // If RNG was not specified at all,
+ // try to pick one appropriate for this env.
+ if (!rng) {
+ rng = getRNG();
+ }
+
+ // If `rng` is a string, try to forcibly
+ // set the RNG to the type specified.
+ if (rng && typeof rng === "string") {
+ rng = getRNG(rng);
+ }
+
+ if (runCSPRNGTest) {
+
+ if (rng && typeof rng !== "function") {
+ throw new Error(errPrefix + "(Not a function)." + errSuffix);
+ }
+
+ if (rng && typeof rng(config.bits) !== "string") {
+ throw new Error(errPrefix + "(Output is not a string)." + errSuffix);
+ }
+
+ if (rng && !parseInt(rng(config.bits), 2)) {
+ throw new Error(errPrefix + "(Binary string output not parseable to an Integer)." + errSuffix);
+ }
+
+ if (rng && rng(config.bits).length > config.bits) {
+ throw new Error(errPrefix + "(Output length is greater than config.bits)." + errSuffix);
+ }
+
+ if (rng && rng(config.bits).length < config.bits) {
+ throw new Error(errPrefix + "(Output length is less than config.bits)." + errSuffix);
+ }
+
+ }
+
+ config.rng = rng;
+
+ return true;
+ },
+
+ // 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 which defaults to 2.
+ str2hex: function (str, bytesPerChar) {
+ var hexChars,
+ max,
+ out = "",
+ neededBytes,
+ num,
+ i,
+ len;
+
+ if (typeof str !== "string") {
+ throw new Error("Input must be a character string.");
+ }
+
+ if (!bytesPerChar) {
+ bytesPerChar = defaults.bytesPerChar;
+ }
+
+ if (typeof bytesPerChar !== "number" || bytesPerChar < 1 || bytesPerChar > defaults.maxBytesPerChar || bytesPerChar % 1 !== 0) {
+ throw new Error("Bytes per character must be an integer between 1 and " + defaults.maxBytesPerChar + ", inclusive.");
+ }
+
+ hexChars = 2 * bytesPerChar;
+ max = Math.pow(16, hexChars) - 1;
+
+ for (i = 0, len = str.length; i < len; i++) {
+ num = str[i].charCodeAt();
+
+ if (isNaN(num)) {
+ throw new Error("Invalid character: " + str[i]);
+ }
+
+ if (num > max) {
+ 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.");
+ }
+
+ out = padLeft(num.toString(16), hexChars) + out;
+ }
+ return out;
+ },
+
+ // Converts a given HEX number string to a UTF16 character string.
+ hex2str: function (str, bytesPerChar) {
+ var hexChars,
+ out = "",
+ i,
+ len;
+
+ 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.");
+ }
+
+ hexChars = 2 * bytesPerChar;
+
+ str = padLeft(str, hexChars);
+
+ for (i = 0, len = str.length; i < len; i += hexChars) {
+ out = String.fromCharCode(parseInt(str.slice(i, i + hexChars), 16)) + out;
+ }
+
+ return out;
+ },
+
+ // Generates a random bits-length number string using the PRNG
+ random: function (bits) {
+
+ if (typeof bits !== "number" || bits % 1 !== 0 || bits < 2 || bits > 65536) {
+ throw new Error("Number of bits must be an Integer between 1 and 65536.");
+ }
+
+ if (config.typeCSPRNG === "browserSJCLRandom" && sjcl.random.isReady(sjclParanoia) < 1) {
+ throw new Error("SJCL isn't finished seeding the RNG yet. Needs new entropy added or more mouse movement.");
+ }
+
+ 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.
+ share: function (secret, numShares, threshold, padLength) {
+ var neededBits,
+ subShares,
+ x = new Array(numShares),
+ y = new Array(numShares),
+ i,
+ j,
+ len;
+
+ // Security:
+ // For additional security, pad in multiples of 128 bits by default.
+ // A small trade-off in larger share size to help prevent leakage of information
+ // about small-ish secrets and increase the difficulty of attacking them.
+ padLength = padLength || 128;
+
+ 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.maxShares + "), inclusive.");
+ }
+
+ if (numShares > config.maxShares) {
+ 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.maxShares + "), 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.maxShares + "), inclusive.");
+ }
+
+ if (threshold > config.maxShares) {
+ 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.maxShares + "), inclusive. To use a threshold of " + threshold + ", use at least " + neededBits + " bits.");
+ }
+
+ if (threshold > numShares) {
+ throw new Error("Threshold number of shares was " + threshold + " but must be less than or equal to the " + numShares + " shares specified as the total to generate.");
+ }
+
+ if (typeof padLength !== "number" || padLength % 1 !== 0 || padLength < 0 || padLength > 1024) {
+ throw new Error("Zero-pad length must be an integer between 0 and 1024 inclusive.");
+ }
+
+ secret = "1" + hex2bin(secret); // append a 1 as a marker so that we can preserve the correct number of leading zeros in our secret
+ secret = splitNumStringToIntArray(secret, padLength);
+
+ for (i = 0, len = secret.length; i < len; i++) {
+ subShares = getShares(secret[i], numShares, threshold);
+ for (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] || "");
+ }
+ }
+
+ for (i = 0; i < numShares; i++) {
+ x[i] = constructPublicShareString(config.bits, x[i], bin2hex(y[i]));
+ }
+
+ return x;
+ },
+
+ // 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)
+ newShare: function (id, shares) {
+ var share;
+
+ if (id && typeof id === "string") {
+ id = parseInt(id, config.radix);
+ }
+
+ if (id && shares && shares[0]) {
+ share = this.extractShareComponents(shares[0]);
+ return constructPublicShareString(share.bits, id, this.combine(shares, id));
+ }
+
+ throw new Error("Invalid 'id' or 'shares' Array argument to newShare().");
+ },
+
+ /* test-code */
+ // export private functions so they can be unit tested directly.
+ _reset: reset,
+ _padLeft: padLeft,
+ _hex2bin: hex2bin,
+ _bin2hex: bin2hex,
+ _hasCryptoGetRandomValues: hasCryptoGetRandomValues,
+ _hasCryptoRandomBytes: hasCryptoRandomBytes,
+ _hasSJCL: hasSJCL,
+ _getRNG: getRNG,
+ _isSetRNG: isSetRNG,
+ _splitNumStringToIntArray: splitNumStringToIntArray,
+ _horner: horner,
+ _lagrange: lagrange,
+ _getShares: getShares,
+ _constructPublicShareString: constructPublicShareString
+ /* end-test-code */
+
+ };
+
+ // Always initialize secrets with default settings.
+ secrets.init();
+
+ return secrets;
+
+}));