Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ultraviolet
GitHub Repository: ultraviolet/bitaddress.org
Path: blob/master/src/secrets.js
248 views
1
// secrets.js - by Alexander Stetsyuk - released under MIT License
2
(function(exports, global){
3
var defaults = {
4
bits: 8, // default number of bits
5
radix: 16, // work with HEX by default
6
minBits: 3,
7
maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS!
8
9
bytesPerChar: 2,
10
maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53)
11
12
// Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30
13
// The index of each term in the array corresponds to the n for that polynomial
14
// i.e. to get the polynomial for n=16, use primitivePolynomials[16]
15
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],
16
17
// warning for insecure PRNG
18
warning: 'WARNING:\nA secure random number generator was not found.\nUsing Math.random(), which is NOT cryptographically strong!'
19
};
20
21
// Protected settings object
22
var config = {};
23
24
/** @expose **/
25
exports.getConfig = function(){
26
return {
27
'bits': config.bits,
28
'unsafePRNG': config.unsafePRNG
29
};
30
};
31
32
function init(bits){
33
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
34
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
35
}
36
37
config.radix = defaults.radix;
38
config.bits = bits || defaults.bits;
39
config.size = Math.pow(2, config.bits);
40
config.max = config.size - 1;
41
42
// Construct the exp and log tables for multiplication.
43
var logs = [], exps = [], x = 1, primitive = defaults.primitivePolynomials[config.bits];
44
for(var i=0; i<config.size; i++){
45
exps[i] = x;
46
logs[x] = i;
47
x <<= 1;
48
if(x >= config.size){
49
x ^= primitive;
50
x &= config.max;
51
}
52
}
53
54
config.logs = logs;
55
config.exps = exps;
56
};
57
58
/** @expose **/
59
exports.init = init;
60
61
function isInited(){
62
if(!config.bits || !config.size || !config.max || !config.logs || !config.exps || config.logs.length !== config.size || config.exps.length !== config.size){
63
return false;
64
}
65
return true;
66
};
67
68
// Returns a pseudo-random number generator of the form function(bits){}
69
// which should output a random string of 1's and 0's of length `bits`
70
function getRNG(){
71
var randomBits, crypto;
72
73
function construct(bits, arr, radix, size){
74
var str = '',
75
i = 0,
76
len = arr.length-1;
77
while( i<len || (str.length < bits) ){
78
str += padLeft(parseInt(arr[i], radix).toString(2), size);
79
i++;
80
}
81
str = str.substr(-bits);
82
if( (str.match(/0/g)||[]).length === str.length){ // all zeros?
83
return null;
84
}else{
85
return str;
86
}
87
}
88
89
// node.js crypto.randomBytes()
90
if(typeof require === 'function' && (crypto=require('crypto')) && (randomBits=crypto['randomBytes'])){
91
return function(bits){
92
var bytes = Math.ceil(bits/8),
93
str = null;
94
95
while( str === null ){
96
str = construct(bits, randomBits(bytes).toString('hex'), 16, 4);
97
}
98
return str;
99
}
100
}
101
102
// browsers with window.crypto.getRandomValues()
103
if(global['crypto'] && typeof global['crypto']['getRandomValues'] === 'function' && typeof global['Uint32Array'] === 'function'){
104
crypto = global['crypto'];
105
return function(bits){
106
var elems = Math.ceil(bits/32),
107
str = null,
108
arr = new global['Uint32Array'](elems);
109
110
while( str === null ){
111
crypto['getRandomValues'](arr);
112
str = construct(bits, arr, 10, 32);
113
}
114
115
return str;
116
}
117
}
118
119
// A totally insecure RNG!!! (except in Safari)
120
// Will produce a warning every time it is called.
121
config.unsafePRNG = true;
122
warn();
123
124
var bitsPerNum = 32;
125
var max = Math.pow(2,bitsPerNum)-1;
126
return function(bits){
127
var elems = Math.ceil(bits/bitsPerNum);
128
var arr = [], str=null;
129
while(str===null){
130
for(var i=0; i<elems; i++){
131
arr[i] = Math.floor(Math.random() * max + 1);
132
}
133
str = construct(bits, arr, 10, bitsPerNum);
134
}
135
return str;
136
};
137
};
138
139
// Warn about using insecure rng.
140
// Called when Math.random() is being used.
141
function warn(){
142
global['console']['warn'](defaults.warning);
143
if(typeof global['alert'] === 'function' && config.alert){
144
global['alert'](defaults.warning);
145
}
146
}
147
148
// Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG()
149
/** @expose **/
150
exports.setRNG = function(rng, alert){
151
if(!isInited()){
152
this.init();
153
}
154
config.unsafePRNG=false;
155
rng = rng || getRNG();
156
157
// test the RNG (5 times)
158
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){
159
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.")
160
}else{
161
config.rng = rng;
162
}
163
config.alert = !!alert;
164
165
return !!config.unsafePRNG;
166
};
167
168
function isSetRNG(){
169
return typeof config.rng === 'function';
170
};
171
172
// Generates a random bits-length number string using the PRNG
173
/** @expose **/
174
exports.random = function(bits){
175
if(!isSetRNG()){
176
this.setRNG();
177
}
178
179
if(typeof bits !== 'number' || bits%1 !== 0 || bits < 2){
180
throw new Error('Number of bits must be an integer greater than 1.')
181
}
182
183
if(config.unsafePRNG){
184
warn();
185
}
186
return bin2hex(config.rng(bits));
187
}
188
189
// Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16)
190
// into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`),
191
// requiring `threshold` number of shares to reconstruct the secret.
192
// Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing.
193
/** @expose **/
194
exports.share = function(secret, numShares, threshold, padLength, withoutPrefix){
195
if(!isInited()){
196
this.init();
197
}
198
if(!isSetRNG()){
199
this.setRNG();
200
}
201
202
padLength = padLength || 0;
203
204
if(typeof secret !== 'string'){
205
throw new Error('Secret must be a string.');
206
}
207
if(typeof numShares !== 'number' || numShares%1 !== 0 || numShares < 2){
208
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.')
209
}
210
if(numShares > config.max){
211
var neededBits = Math.ceil(Math.log(numShares +1)/Math.LN2);
212
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.')
213
}
214
if(typeof threshold !== 'number' || threshold%1 !== 0 || threshold < 2){
215
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.');
216
}
217
if(threshold > config.max){
218
var neededBits = Math.ceil(Math.log(threshold +1)/Math.LN2);
219
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.');
220
}
221
if(typeof padLength !== 'number' || padLength%1 !== 0 ){
222
throw new Error('Zero-pad length must be an integer greater than 1.');
223
}
224
225
if(config.unsafePRNG){
226
warn();
227
}
228
229
secret = '1' + hex2bin(secret); // append a 1 so that we can preserve the correct number of leading zeros in our secret
230
secret = split(secret, padLength);
231
var x = new Array(numShares), y = new Array(numShares);
232
for(var i=0, len = secret.length; i<len; i++){
233
var subShares = this._getShares(secret[i], numShares, threshold);
234
for(var j=0; j<numShares; j++){
235
x[j] = x[j] || subShares[j].x.toString(config.radix);
236
y[j] = padLeft(subShares[j].y.toString(2)) + (y[j] ? y[j] : '');
237
}
238
}
239
var padding = config.max.toString(config.radix).length;
240
if(withoutPrefix){
241
for(var i=0; i<numShares; i++){
242
x[i] = bin2hex(y[i]);
243
}
244
}else{
245
for(var i=0; i<numShares; i++){
246
x[i] = config.bits.toString(36).toUpperCase() + padLeft(x[i],padding) + bin2hex(y[i]);
247
}
248
}
249
250
return x;
251
};
252
253
// This is the basic polynomial generation and evaluation function
254
// for a `config.bits`-length secret (NOT an arbitrary length)
255
// Note: no error-checking at this stage! If `secrets` is NOT
256
// a NUMBER less than 2^bits-1, the output will be incorrect!
257
/** @expose **/
258
exports._getShares = function(secret, numShares, threshold){
259
var shares = [];
260
var coeffs = [secret];
261
262
for(var i=1; i<threshold; i++){
263
coeffs[i] = parseInt(config.rng(config.bits),2);
264
}
265
for(var i=1, len = numShares+1; i<len; i++){
266
shares[i-1] = {
267
x: i,
268
y: horner(i, coeffs)
269
}
270
}
271
return shares;
272
};
273
274
// Polynomial evaluation at `x` using Horner's Method
275
// TODO: this can possibly be sped up using other methods
276
// NOTE: fx=fx * x + coeff[i] -> exp(log(fx) + log(x)) + coeff[i],
277
// so if fx===0, just set fx to coeff[i] because
278
// using the exp/log form will result in incorrect value
279
function horner(x, coeffs){
280
var logx = config.logs[x];
281
var fx = 0;
282
for(var i=coeffs.length-1; i>=0; i--){
283
if(fx === 0){
284
fx = coeffs[i];
285
continue;
286
}
287
fx = config.exps[ (logx + config.logs[fx]) % config.max ] ^ coeffs[i];
288
}
289
return fx;
290
};
291
292
function inArray(arr,val){
293
for(var i = 0,len=arr.length; i < len; i++) {
294
if(arr[i] === val){
295
return true;
296
}
297
}
298
return false;
299
};
300
301
function processShare(share){
302
303
var bits = parseInt(share[0], 36);
304
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
305
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
306
}
307
308
var max = Math.pow(2, bits) - 1;
309
var idLength = max.toString(config.radix).length;
310
311
var id = parseInt(share.substr(1, idLength), config.radix);
312
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
313
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
314
}
315
share = share.substr(idLength + 1);
316
if(!share.length){
317
throw new Error('Invalid share: zero-length share.')
318
}
319
return {
320
'bits': bits,
321
'id': id,
322
'value': share
323
};
324
};
325
326
/** @expose **/
327
exports._processShare = processShare;
328
329
// Protected method that evaluates the Lagrange interpolation
330
// polynomial at x=`at` for individual config.bits-length
331
// segments of each share in the `shares` Array.
332
// Each share is expressed in base `inputRadix`. The output
333
// is expressed in base `outputRadix'
334
function combine(at, shares){
335
var setBits, share, x = [], y = [], result = '', idx;
336
337
for(var i=0, len = shares.length; i<len; i++){
338
share = processShare(shares[i]);
339
if(typeof setBits === 'undefined'){
340
setBits = share['bits'];
341
}else if(share['bits'] !== setBits){
342
throw new Error('Mismatched shares: Different bit settings.')
343
}
344
345
if(config.bits !== setBits){
346
init(setBits);
347
}
348
349
if(inArray(x, share['id'])){ // repeated x value?
350
continue;
351
}
352
353
idx = x.push(share['id']) - 1;
354
share = split(hex2bin(share['value']));
355
for(var j=0, len2 = share.length; j<len2; j++){
356
y[j] = y[j] || [];
357
y[j][idx] = share[j];
358
}
359
}
360
361
for(var i=0, len=y.length; i<len; i++){
362
result = padLeft(lagrange(at, x, y[i]).toString(2)) + result;
363
}
364
365
if(at===0){// reconstructing the secret
366
var idx = result.indexOf('1'); //find the first 1
367
return bin2hex(result.slice(idx+1));
368
}else{// generating a new share
369
return bin2hex(result);
370
}
371
};
372
373
// Combine `shares` Array into the original secret
374
/** @expose **/
375
exports.combine = function(shares){
376
return combine(0, shares);
377
};
378
379
// Generate a new share with id `id` (a number between 1 and 2^bits-1)
380
// `id` can be a Number or a String in the default radix (16)
381
/** @expose **/
382
exports.newShare = function(id, shares){
383
if(typeof id === 'string'){
384
id = parseInt(id, config.radix);
385
}
386
387
var share = processShare(shares[0]);
388
var max = Math.pow(2, share['bits']) - 1;
389
390
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
391
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
392
}
393
394
var padding = max.toString(config.radix).length;
395
return config.bits.toString(36).toUpperCase() + padLeft(id.toString(config.radix), padding) + combine(id, shares);
396
};
397
398
// Evaluate the Lagrange interpolation polynomial at x = `at`
399
// using x and y Arrays that are of the same length, with
400
// corresponding elements constituting points on the polynomial.
401
function lagrange(at, x, y){
402
var sum = 0,
403
product,
404
i, j;
405
406
for(var i=0, len = x.length; i<len; i++){
407
if(!y[i]){
408
continue;
409
}
410
411
product = config.logs[y[i]];
412
for(var j=0; j<len; j++){
413
if(i === j){ continue; }
414
if(at === x[j]){ // happens when computing a share that is in the list of shares used to compute it
415
product = -1; // fix for a zero product term, after which the sum should be sum^0 = sum, not sum^1
416
break;
417
}
418
product = ( product + config.logs[at ^ x[j]] - config.logs[x[i] ^ x[j]] + config.max/* to make sure it's not negative */ ) % config.max;
419
}
420
421
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
422
}
423
return sum;
424
};
425
426
/** @expose **/
427
exports._lagrange = lagrange;
428
429
// Splits a number string `bits`-length segments, after first
430
// optionally zero-padding it to a length that is a multiple of `padLength.
431
// Returns array of integers (each less than 2^bits-1), with each element
432
// representing a `bits`-length segment of the input string from right to left,
433
// i.e. parts[0] represents the right-most `bits`-length segment of the input string.
434
function split(str, padLength){
435
if(padLength){
436
str = padLeft(str, padLength)
437
}
438
var parts = [];
439
for(var i=str.length; i>config.bits; i-=config.bits){
440
parts.push(parseInt(str.slice(i-config.bits, i), 2));
441
}
442
parts.push(parseInt(str.slice(0, i), 2));
443
return parts;
444
};
445
446
// Pads a string `str` with zeros on the left so that its length is a multiple of `bits`
447
function padLeft(str, bits){
448
bits = bits || config.bits
449
var missing = str.length % bits;
450
return (missing ? new Array(bits - missing + 1).join('0') : '') + str;
451
};
452
453
function hex2bin(str){
454
var bin = '', num;
455
for(var i=str.length - 1; i>=0; i--){
456
num = parseInt(str[i], 16)
457
if(isNaN(num)){
458
throw new Error('Invalid hex character.')
459
}
460
bin = padLeft(num.toString(2), 4) + bin;
461
}
462
return bin;
463
}
464
465
function bin2hex(str){
466
var hex = '', num;
467
str = padLeft(str, 4);
468
for(var i=str.length; i>=4; i-=4){
469
num = parseInt(str.slice(i-4, i), 2);
470
if(isNaN(num)){
471
throw new Error('Invalid binary character.')
472
}
473
hex = num.toString(16) + hex;
474
}
475
return hex;
476
}
477
478
// Converts a given UTF16 character string to the HEX representation.
479
// Each character of the input string is represented by
480
// `bytesPerChar` bytes in the output string.
481
/** @expose **/
482
exports.str2hex = function(str, bytesPerChar){
483
if(typeof str !== 'string'){
484
throw new Error('Input must be a character string.');
485
}
486
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
487
488
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
489
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
490
}
491
492
var hexChars = 2*bytesPerChar;
493
var max = Math.pow(16, hexChars) - 1;
494
var out = '', num;
495
for(var i=0, len=str.length; i<len; i++){
496
num = str[i].charCodeAt();
497
if(isNaN(num)){
498
throw new Error('Invalid character: ' + str[i]);
499
}else if(num > max){
500
var neededBytes = Math.ceil(Math.log(num+1)/Math.log(256));
501
throw new Error('Invalid character code (' + num +'). Maximum allowable is 256^bytes-1 (' + max + '). To convert this character, use at least ' + neededBytes + ' bytes.')
502
}else{
503
out = padLeft(num.toString(16), hexChars) + out;
504
}
505
}
506
return out;
507
};
508
509
// Converts a given HEX number string to a UTF16 character string.
510
/** @expose **/
511
exports.hex2str = function(str, bytesPerChar){
512
if(typeof str !== 'string'){
513
throw new Error('Input must be a hexadecimal string.');
514
}
515
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
516
517
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
518
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
519
}
520
521
var hexChars = 2*bytesPerChar;
522
var out = '';
523
str = padLeft(str, hexChars);
524
for(var i=0, len = str.length; i<len; i+=hexChars){
525
out = String.fromCharCode(parseInt(str.slice(i, i+hexChars),16)) + out;
526
}
527
return out;
528
};
529
530
// by default, initialize without an RNG
531
exports.init();
532
})(typeof module !== 'undefined' && module['exports'] ? module['exports'] : (window['secrets'] = {}), typeof GLOBAL !== 'undefined' ? GLOBAL : window );
533