UNPKG

secret-sharing.js

Version:

Shamir's Secret Sharing Scheme for javascript - https://www.virtualcapitalofamerica.com

945 lines (786 loc) 33 kB
// "test": "./node_modules/.bin/standard \"secret-sharing.js\" && node ./node_modules/jasmine-node/bin/jasmine-node spec/" // @preserve author Alexander Stetsyuk // @preserve author Glenn Rempe <glenn@rempe.us> // @preserve author Camilo Alexander Rodriguez Cuaran <camilo.rodriguez@virtualcapitalofamerica.com> // @license MIT /* global define, sjcl */ // 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) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], function () { return (root.secrets = factory()) }) } 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' var defaults, config, preGenPadding, runCSPRNGTest, sjclParanoia, CSPRNGTypes function reset () { 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] } config = {} preGenPadding = new Array(1024).join('0') // Pre-generate a string of 1024 0's for use by padLeft(). runCSPRNGTest = true sjclParanoia = 10 // WARNING : Never use 'testRandom' except for testing. CSPRNGTypes = ['nodeCryptoRandomBytes', 'browserCryptoGetRandomValues', 'browserSJCLRandom', 'testRandom'] } function isSetRNG () { if (config && config.rng && typeof config.rng === 'function') { return true } return false } // Pads a string `str` with zeros on the left so that its length is a multiple of `bits` function padLeft (str, multipleOfBits) { var missing if (multipleOfBits === 0 || multipleOfBits === 1) { return str } if (multipleOfBits && multipleOfBits > 1024) { throw new Error('Padding must be multiples of no larger than 1024 bits.') } multipleOfBits = multipleOfBits || config.bits if (str) { missing = str.length % multipleOfBits } if (missing) { return (preGenPadding + str).slice(-(multipleOfBits - missing + str.length)) } return str } function hex2bin (str) { var bin = '' var num = null var i = 0 for (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 = '' var num = null var i = 0 str = padLeft(str, 4) 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 } return hex } // Browser supports crypto.getRandomValues() function hasCryptoGetRandomValues () { if (crypto && typeof crypto === 'object' && (typeof crypto.getRandomValues === 'function' || typeof crypto.getRandomValues === 'object') && (typeof Uint32Array === 'function' || typeof Uint32Array === 'object')) { return true } return false } // Node.js support for crypto.randomBytes() function hasCryptoRandomBytes () { if (typeof crypto === 'object' && typeof crypto.randomBytes === 'function') { return true } return false } // Stanford Javascript Crypto Library Support function hasSJCL () { if (sjcl) { if (typeof sjcl === 'object' && typeof sjcl.random === 'object') { return true } } return false } // 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`. // `type` (Optional) : A string representing the CSPRNG that you want to // force to be loaded, overriding feature detection. Can be one of: // 'nodeCryptoRandomBytes' // 'browserCryptoGetRandomValues' // 'browserSJCLRandom' // function getRNG (type) { function construct (bits, arr, radix, size) { var i = 0 var len var str = '' var parsedInt if (arr) { len = arr.length - 1 } while (i < len || (str.length < bits)) { // convert any negative nums to positive with Math.abs() parsedInt = Math.abs(parseInt(arr[i], radix)) str = str + padLeft(parsedInt.toString(2), size) i++ } str = str.substr(-bits) // return null so this result can be re-processed if the result is all 0's. if ((str.match(/0/g) || []).length === str.length) { return null } return str } // Node.js : crypto.randomBytes() // Note : Node.js and crypto.randomBytes() uses the OpenSSL RAND_bytes() function for its CSPRNG. // Node.js will need to have been compiled with OpenSSL for this to work. // See : https://github.com/joyent/node/blob/d8baf8a2a4481940bfed0196308ae6189ca18eee/src/node_crypto.cc#L4696 // See : https://www.openssl.org/docs/crypto/rand.html function nodeCryptoRandomBytes (bits) { var buf var bytes var radix var size var str = null radix = 16 size = 4 bytes = Math.ceil(bits / 8) while (str === null) { buf = crypto.randomBytes(bytes) str = construct(bits, buf.toString('hex'), radix, size) } return str } // Browser : crypto.getRandomValues() // See : https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-Crypto // See : https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues // Supported Browsers : http://caniuse.com/#search=crypto.getRandomValues function browserCryptoGetRandomValues (bits) { var elems var radix var size var str = null radix = 10 size = 32 elems = Math.ceil(bits / 32) while (str === null) { str = construct(bits, crypto.getRandomValues(new Uint32Array(elems)), radix, size) } return str } // Browser SJCL : If the Stanford Javascript Crypto Library (SJCL) is loaded in the browser // then use it as a fallback CSPRNG when crypto.getRandomValues() is not available. // It may require some time and mouse movements to be fully seeded. Uses a modified version // of the Fortuna RNG. // See : https://bitwiseshiftleft.github.io/sjcl/ function browserSJCLRandom (bits) { var elems var radix var size var str = null radix = 10 size = 32 elems = Math.ceil(bits / 32) if (sjcl.random.isReady(sjclParanoia)) { str = construct(bits, sjcl.random.randomWords(elems, sjclParanoia), radix, size) } else { throw new Error('SJCL isn\'t finished seeding the RNG yet.') } return str } // ///////////////////////////////////////////////////////////// // WARNING : DO NOT USE. For testing purposes only. // ///////////////////////////////////////////////////////////// // This function will return repeatable non-random test bits. Can be used // for testing only. Node.js does not return proper random bytes // when run within a PhantomJS container. function testRandom (bits) { var elems = Math.ceil(bits / 32) var int = 123456789 var radix = 10 var size = 32 var str = null var arr = new Uint32Array(elems) // Fill every element of the Uint32Array with the same int. for (var i = 0; i < arr.length; i++) { arr[i] = int } while (str === null) { str = construct(bits, arr, radix, size) } return str } // Return a random generator function for browsers that support HTML5 // crypto.getRandomValues(), Node.js compiled with OpenSSL support. // or the Stanford Javascript Crypto Library Fortuna RNG. // WARNING : NEVER use testRandom outside of a testing context. Totally non-random! if (type && type === 'testRandom') { config.typeCSPRNG = type return testRandom } else if (type && type === 'nodeCryptoRandomBytes') { config.typeCSPRNG = type return nodeCryptoRandomBytes } else if (type && type === 'browserCryptoGetRandomValues') { config.typeCSPRNG = type return browserCryptoGetRandomValues } else if (type && type === 'browserSJCLRandom') { runCSPRNGTest = false config.typeCSPRNG = type return browserSJCLRandom } else if (hasCryptoRandomBytes()) { config.typeCSPRNG = 'nodeCryptoRandomBytes' return nodeCryptoRandomBytes } else if (hasCryptoGetRandomValues()) { config.typeCSPRNG = 'browserCryptoGetRandomValues' return browserCryptoGetRandomValues } else if (hasSJCL()) { runCSPRNGTest = false config.typeCSPRNG = 'browserSJCLRandom' return browserSJCLRandom } } // 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 splitNumStringToIntArray (str, padLength) { var parts = [] var i if (padLength) { str = padLeft(str, padLength) } for (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 } // 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] var fx = 0 var 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 var len var product var i var 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 = [] var coeffs = [secret] var i var 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 = [] var exps = [] var x = 1 var primitive var 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') { sjcl.random = new (sjcl.prng())() // 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 var 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 var j var len var len2 var result = '' var setBits var share var splitShare var x = [] var 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 var id var idLen var max var obj = {} var regexStr var 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 ' var 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 var max var out = '' var neededBytes var num var i var 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 var out = '' var i var 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 var subShares var x = new Array(numShares) var y = new Array(numShares) var i var j var 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 }))