123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- // 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 );
|