mirror of
https://github.com/iancoleman/shamir.git
synced 2025-11-18 00:50:59 +00:00
First commit
This commit is contained in:
commit
508a061fa9
9 changed files with 15060 additions and 0 deletions
48
compile.py
Normal file
48
compile.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# This script generates the shamir-standalone.html file.
|
||||||
|
|
||||||
|
# It removes script and style tags and replaces with the file content.
|
||||||
|
|
||||||
|
f = open('src/index.html')
|
||||||
|
page = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
# Script tags
|
||||||
|
|
||||||
|
scriptsFinder = re.compile("""<script src="(.*)"></script>""")
|
||||||
|
scripts = scriptsFinder.findall(page)
|
||||||
|
|
||||||
|
for script in scripts:
|
||||||
|
filename = os.path.join("src", script)
|
||||||
|
s = open(filename)
|
||||||
|
scriptContent = "<script>%s</script>" % s.read()
|
||||||
|
s.close()
|
||||||
|
scriptTag = """<script src="%s"></script>""" % script
|
||||||
|
page = page.replace(scriptTag, scriptContent)
|
||||||
|
|
||||||
|
|
||||||
|
# Style tags
|
||||||
|
|
||||||
|
stylesFinder = re.compile("""<link rel="stylesheet" href="(.*)">""")
|
||||||
|
styles = stylesFinder.findall(page)
|
||||||
|
|
||||||
|
for style in styles:
|
||||||
|
filename = os.path.join("src", style)
|
||||||
|
s = open(filename)
|
||||||
|
styleContent = "<style>%s</style>" % s.read()
|
||||||
|
s.close()
|
||||||
|
styleTag = """<link rel="stylesheet" href="%s">""" % style
|
||||||
|
page = page.replace(styleTag, styleContent)
|
||||||
|
|
||||||
|
|
||||||
|
# Write the standalone file
|
||||||
|
|
||||||
|
f = open('shamir-standalone.html', 'w')
|
||||||
|
f.write(page)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
print "%s - DONE" % datetime.datetime.now()
|
||||||
7
license
Normal file
7
license
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright (c) 2016 Ian Coleman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
7502
shamir-standalone.html
Normal file
7502
shamir-standalone.html
Normal file
File diff suppressed because it is too large
Load diff
30
src/css/app.css
Normal file
30
src/css/app.css
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
input[type="number"] {
|
||||||
|
display: inline-block;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
background: inherit;
|
||||||
|
border: inherit;
|
||||||
|
word-break: inherit;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generated {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.part {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.part:nth-of-type(2n+1) {
|
||||||
|
background-color: #F4F4F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.part:nth-of-type(2n+0) {
|
||||||
|
background-color: #E9E8E6;
|
||||||
|
}
|
||||||
6760
src/css/bootstrap.css
vendored
Normal file
6760
src/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
74
src/index.html
Normal file
74
src/index.html
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Shamir Secret Sharing Scheme</title>
|
||||||
|
<link rel="stylesheet" href="css/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="css/app.css">
|
||||||
|
<meta content="Shamir Secret Sharing Scheme tool" name="description"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
|
<meta content="Ian Coleman" name="author" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>Shamir Secret Sharing Scheme</h1>
|
||||||
|
<p>Split your secret into parts which can be combined back into the original secret using some or all of the parts.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<h2>Split</h2>
|
||||||
|
<div>
|
||||||
|
Require
|
||||||
|
<input class="required form-control" type="number" value="3" min="2" max="255">
|
||||||
|
parts from
|
||||||
|
<input class="total form-control" type="number" value="5" min="2" max="255">
|
||||||
|
to reconstruct the following secret
|
||||||
|
</div>
|
||||||
|
<textarea class="secret form-control" rows=10 placeholder="Enter your secret here"></textarea>
|
||||||
|
<h2>Usage</h2>
|
||||||
|
<p>Double click each part below to select the content for that part. Copy and paste the content for each part into <span class="distributesize">5</span> individual files on your computer.</p>
|
||||||
|
<p>Distribute one file to each person in your group.</p>
|
||||||
|
<p>If <span class="recreatesize">3</span> of those people can combine the contents of their file using this page, they can view the secret.</p>
|
||||||
|
<p>Remember to delete the parts from your computer once you're finished. If you use a rubbish bin for deleted files, also remove them from the rubbish bin.</p>
|
||||||
|
<p class="error text-danger"></p>
|
||||||
|
<h2>Parts</h2>
|
||||||
|
<ol class="generated">
|
||||||
|
<li>Enter your secret above.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<h2>Combine</h2>
|
||||||
|
<p class="form-control-static">
|
||||||
|
Enter the secrets, one per line
|
||||||
|
</p>
|
||||||
|
<textarea class="parts form-control" rows=10></textarea>
|
||||||
|
<h2>Result</h2>
|
||||||
|
<pre class="combined">Enter your parts above.</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="sources">
|
||||||
|
<h2>Sources</h2>
|
||||||
|
<p><a href="https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing">Wikipedia entry</a> for shamir secret sharing scheme.</p>
|
||||||
|
<p>This project is 100% open-source code.</p>
|
||||||
|
<p><a href="https://github.com/iancoleman/shamir">Project source code</a></p>
|
||||||
|
<p><a href="https://github.com/amper5and/secrets.js">SSSS javascript library by amper5and</a></p>
|
||||||
|
<p><a href="https://getbootstrap.com/">Bootstrap stylesheet</a></p>
|
||||||
|
<h2>Offline Usage</h2>
|
||||||
|
<p>This tool has been designed to be used offline.</p>
|
||||||
|
<p>In your browser, select file save-as, and save this page as a file.</p>
|
||||||
|
<p>Double-click that file to open it in a browser on any offline computer.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="js/secrets.js"></script>
|
||||||
|
<script src="js/selector.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
104
src/js/app.js
Normal file
104
src/js/app.js
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Coordinates the interaction of elements on the page
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var DOM = {};
|
||||||
|
DOM.required = $(".required");
|
||||||
|
DOM.total = $(".total");
|
||||||
|
DOM.secret = $(".secret");
|
||||||
|
DOM.distributesize = $(".distributesize");
|
||||||
|
DOM.recreatesize = $(".recreatesize");
|
||||||
|
DOM.error = $(".error");
|
||||||
|
DOM.generated = $(".generated");
|
||||||
|
DOM.parts = $(".parts");
|
||||||
|
DOM.combined = $(".combined");
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
// Events
|
||||||
|
DOM.required.addEventListener("input", generateParts);
|
||||||
|
DOM.total.addEventListener("input", generateParts);
|
||||||
|
DOM.secret.addEventListener("input", generateParts);
|
||||||
|
DOM.parts.addEventListener("input", combineParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateParts() {
|
||||||
|
// Clear old generated
|
||||||
|
DOM.generated.innerHTML = "";
|
||||||
|
// Get the input values
|
||||||
|
var secret = DOM.secret.value;
|
||||||
|
var secretHex = secrets.str2hex(secret);
|
||||||
|
var total = parseFloat(DOM.total.value);
|
||||||
|
var required = parseFloat(DOM.required.value);
|
||||||
|
// validate the input
|
||||||
|
if (total < 2) {
|
||||||
|
DOM.error.textContent = "Total must be at least 1";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (total > 255) {
|
||||||
|
DOM.error.textContent = "Total must be at most 255";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (required < 2) {
|
||||||
|
DOM.error.textContent = "Required must be at least 1";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (required > 255) {
|
||||||
|
DOM.error.textContent = "Required must be at most 255";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (isNaN(total)) {
|
||||||
|
DOM.error.textContent = "Invalid value for total";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (isNaN(required)) {
|
||||||
|
DOM.error.textContent = "Invalid value for required";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (required > total) {
|
||||||
|
DOM.error.textContent = "Required must be less than total";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (secret.length == 0) {
|
||||||
|
DOM.error.textContent = "Secret is blank";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DOM.error.textContent = "";
|
||||||
|
}
|
||||||
|
// Generate the parts to share
|
||||||
|
var minPad = 1024; // see https://github.com/amper5and/secrets.js#note-on-security
|
||||||
|
var shares = secrets.share(secretHex, total, required, minPad);
|
||||||
|
// Display the parts
|
||||||
|
for (var i=0; i<shares.length; i++) {
|
||||||
|
var share = shares[i];
|
||||||
|
var li = document.createElement("li");
|
||||||
|
li.classList.add("part");
|
||||||
|
li.textContent = share;
|
||||||
|
DOM.generated.appendChild(li);
|
||||||
|
}
|
||||||
|
// Update the plain-language info
|
||||||
|
DOM.distributesize.textContent = total;
|
||||||
|
DOM.recreatesize.textContent = required;
|
||||||
|
}
|
||||||
|
|
||||||
|
function combineParts() {
|
||||||
|
// Clear old text
|
||||||
|
DOM.combined.textContent = "";
|
||||||
|
// Get the parts entered by the user
|
||||||
|
var partsStr = DOM.parts.value;
|
||||||
|
// Validate and sanitize the input
|
||||||
|
var parts = partsStr.trim().split(/\s+/);
|
||||||
|
// Combine the parts
|
||||||
|
try {
|
||||||
|
var combinedHex = secrets.combine(parts);
|
||||||
|
var combined = secrets.hex2str(combinedHex);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
DOM.combined.textContent = e.message;
|
||||||
|
}
|
||||||
|
// Display the combined parts
|
||||||
|
DOM.combined.textContent = combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
})();
|
||||||
532
src/js/secrets.js
Normal file
532
src/js/secrets.js
Normal file
|
|
@ -0,0 +1,532 @@
|
||||||
|
// secrets.js - by Alexander Stetsyuk - released under MIT License
|
||||||
|
(function(exports, global){
|
||||||
|
var defaults = {
|
||||||
|
bits: 8, // default number of bits
|
||||||
|
radix: 16, // work with HEX by default
|
||||||
|
minBits: 3,
|
||||||
|
maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS!
|
||||||
|
|
||||||
|
bytesPerChar: 2,
|
||||||
|
maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53)
|
||||||
|
|
||||||
|
// Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30
|
||||||
|
// The index of each term in the array corresponds to the n for that polynomial
|
||||||
|
// i.e. to get the polynomial for n=16, use primitivePolynomials[16]
|
||||||
|
primitivePolynomials: [null,null,1,3,3,5,3,3,29,17,9,5,83,27,43,3,45,9,39,39,9,5,3,33,27,9,71,39,9,5,83],
|
||||||
|
|
||||||
|
// warning for insecure PRNG
|
||||||
|
warning: 'WARNING:\nA secure random number generator was not found.\nUsing Math.random(), which is NOT cryptographically strong!'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Protected settings object
|
||||||
|
var config = {};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports.getConfig = function(){
|
||||||
|
return {
|
||||||
|
'bits': config.bits,
|
||||||
|
'unsafePRNG': config.unsafePRNG
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function init(bits){
|
||||||
|
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
|
||||||
|
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
config.radix = defaults.radix;
|
||||||
|
config.bits = bits || defaults.bits;
|
||||||
|
config.size = Math.pow(2, config.bits);
|
||||||
|
config.max = config.size - 1;
|
||||||
|
|
||||||
|
// Construct the exp and log tables for multiplication.
|
||||||
|
var logs = [], exps = [], x = 1, primitive = defaults.primitivePolynomials[config.bits];
|
||||||
|
for(var i=0; i<config.size; i++){
|
||||||
|
exps[i] = x;
|
||||||
|
logs[x] = i;
|
||||||
|
x <<= 1;
|
||||||
|
if(x >= config.size){
|
||||||
|
x ^= primitive;
|
||||||
|
x &= config.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.logs = logs;
|
||||||
|
config.exps = exps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports.init = init;
|
||||||
|
|
||||||
|
function isInited(){
|
||||||
|
if(!config.bits || !config.size || !config.max || !config.logs || !config.exps || config.logs.length !== config.size || config.exps.length !== config.size){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a pseudo-random number generator of the form function(bits){}
|
||||||
|
// which should output a random string of 1's and 0's of length `bits`
|
||||||
|
function getRNG(){
|
||||||
|
var randomBits, crypto;
|
||||||
|
|
||||||
|
function construct(bits, arr, radix, size){
|
||||||
|
var str = '',
|
||||||
|
i = 0,
|
||||||
|
len = arr.length-1;
|
||||||
|
while( i<len || (str.length < bits) ){
|
||||||
|
str += padLeft(parseInt(arr[i], radix).toString(2), size);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
str = str.substr(-bits);
|
||||||
|
if( (str.match(/0/g)||[]).length === str.length){ // all zeros?
|
||||||
|
return null;
|
||||||
|
}else{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// node.js crypto.randomBytes()
|
||||||
|
if(typeof require === 'function' && (crypto=require('crypto')) && (randomBits=crypto['randomBytes'])){
|
||||||
|
return function(bits){
|
||||||
|
var bytes = Math.ceil(bits/8),
|
||||||
|
str = null;
|
||||||
|
|
||||||
|
while( str === null ){
|
||||||
|
str = construct(bits, randomBits(bytes).toString('hex'), 16, 4);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// browsers with window.crypto.getRandomValues()
|
||||||
|
if(global['crypto'] && typeof global['crypto']['getRandomValues'] === 'function' && typeof global['Uint32Array'] === 'function'){
|
||||||
|
crypto = global['crypto'];
|
||||||
|
return function(bits){
|
||||||
|
var elems = Math.ceil(bits/32),
|
||||||
|
str = null,
|
||||||
|
arr = new global['Uint32Array'](elems);
|
||||||
|
|
||||||
|
while( str === null ){
|
||||||
|
crypto['getRandomValues'](arr);
|
||||||
|
str = construct(bits, arr, 10, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A totally insecure RNG!!! (except in Safari)
|
||||||
|
// Will produce a warning every time it is called.
|
||||||
|
config.unsafePRNG = true;
|
||||||
|
warn();
|
||||||
|
|
||||||
|
var bitsPerNum = 32;
|
||||||
|
var max = Math.pow(2,bitsPerNum)-1;
|
||||||
|
return function(bits){
|
||||||
|
var elems = Math.ceil(bits/bitsPerNum);
|
||||||
|
var arr = [], str=null;
|
||||||
|
while(str===null){
|
||||||
|
for(var i=0; i<elems; i++){
|
||||||
|
arr[i] = Math.floor(Math.random() * max + 1);
|
||||||
|
}
|
||||||
|
str = construct(bits, arr, 10, bitsPerNum);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Warn about using insecure rng.
|
||||||
|
// Called when Math.random() is being used.
|
||||||
|
function warn(){
|
||||||
|
global['console']['warn'](defaults.warning);
|
||||||
|
if(typeof global['alert'] === 'function' && config.alert){
|
||||||
|
global['alert'](defaults.warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG()
|
||||||
|
/** @expose **/
|
||||||
|
exports.setRNG = function(rng, alert){
|
||||||
|
if(!isInited()){
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
config.unsafePRNG=false;
|
||||||
|
rng = rng || getRNG();
|
||||||
|
|
||||||
|
// test the RNG (5 times)
|
||||||
|
if(typeof rng !== 'function' || typeof rng(config.bits) !== 'string' || !parseInt(rng(config.bits),2) || rng(config.bits).length > config.bits || rng(config.bits).length < config.bits){
|
||||||
|
throw new Error("Random number generator is invalid. Supply an RNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's.")
|
||||||
|
}else{
|
||||||
|
config.rng = rng;
|
||||||
|
}
|
||||||
|
config.alert = !!alert;
|
||||||
|
|
||||||
|
return !!config.unsafePRNG;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSetRNG(){
|
||||||
|
return typeof config.rng === 'function';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generates a random bits-length number string using the PRNG
|
||||||
|
/** @expose **/
|
||||||
|
exports.random = function(bits){
|
||||||
|
if(!isSetRNG()){
|
||||||
|
this.setRNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof bits !== 'number' || bits%1 !== 0 || bits < 2){
|
||||||
|
throw new Error('Number of bits must be an integer greater than 1.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.unsafePRNG){
|
||||||
|
warn();
|
||||||
|
}
|
||||||
|
return bin2hex(config.rng(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16)
|
||||||
|
// into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`),
|
||||||
|
// requiring `threshold` number of shares to reconstruct the secret.
|
||||||
|
// Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing.
|
||||||
|
/** @expose **/
|
||||||
|
exports.share = function(secret, numShares, threshold, padLength, withoutPrefix){
|
||||||
|
if(!isInited()){
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
if(!isSetRNG()){
|
||||||
|
this.setRNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
padLength = padLength || 0;
|
||||||
|
|
||||||
|
if(typeof secret !== 'string'){
|
||||||
|
throw new Error('Secret must be a string.');
|
||||||
|
}
|
||||||
|
if(typeof numShares !== 'number' || numShares%1 !== 0 || numShares < 2){
|
||||||
|
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.')
|
||||||
|
}
|
||||||
|
if(numShares > config.max){
|
||||||
|
var neededBits = Math.ceil(Math.log(numShares +1)/Math.LN2);
|
||||||
|
throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To create ' + numShares + ' shares, use at least ' + neededBits + ' bits.')
|
||||||
|
}
|
||||||
|
if(typeof threshold !== 'number' || threshold%1 !== 0 || threshold < 2){
|
||||||
|
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive.');
|
||||||
|
}
|
||||||
|
if(threshold > config.max){
|
||||||
|
var neededBits = Math.ceil(Math.log(threshold +1)/Math.LN2);
|
||||||
|
throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + config.max + '), inclusive. To use a threshold of ' + threshold + ', use at least ' + neededBits + ' bits.');
|
||||||
|
}
|
||||||
|
if(typeof padLength !== 'number' || padLength%1 !== 0 ){
|
||||||
|
throw new Error('Zero-pad length must be an integer greater than 1.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.unsafePRNG){
|
||||||
|
warn();
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = '1' + hex2bin(secret); // append a 1 so that we can preserve the correct number of leading zeros in our secret
|
||||||
|
secret = split(secret, padLength);
|
||||||
|
var x = new Array(numShares), y = new Array(numShares);
|
||||||
|
for(var i=0, len = secret.length; i<len; i++){
|
||||||
|
var subShares = this._getShares(secret[i], numShares, threshold);
|
||||||
|
for(var j=0; j<numShares; j++){
|
||||||
|
x[j] = x[j] || subShares[j].x.toString(config.radix);
|
||||||
|
y[j] = padLeft(subShares[j].y.toString(2)) + (y[j] ? y[j] : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var padding = config.max.toString(config.radix).length;
|
||||||
|
if(withoutPrefix){
|
||||||
|
for(var i=0; i<numShares; i++){
|
||||||
|
x[i] = bin2hex(y[i]);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
for(var i=0; i<numShares; i++){
|
||||||
|
x[i] = config.bits.toString(36).toUpperCase() + padLeft(x[i],padding) + bin2hex(y[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the basic polynomial generation and evaluation function
|
||||||
|
// for a `config.bits`-length secret (NOT an arbitrary length)
|
||||||
|
// Note: no error-checking at this stage! If `secrets` is NOT
|
||||||
|
// a NUMBER less than 2^bits-1, the output will be incorrect!
|
||||||
|
/** @expose **/
|
||||||
|
exports._getShares = function(secret, numShares, threshold){
|
||||||
|
var shares = [];
|
||||||
|
var coeffs = [secret];
|
||||||
|
|
||||||
|
for(var i=1; i<threshold; i++){
|
||||||
|
coeffs[i] = parseInt(config.rng(config.bits),2);
|
||||||
|
}
|
||||||
|
for(var i=1, len = numShares+1; i<len; i++){
|
||||||
|
shares[i-1] = {
|
||||||
|
x: i,
|
||||||
|
y: horner(i, coeffs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shares;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Polynomial evaluation at `x` using Horner's Method
|
||||||
|
// TODO: this can possibly be sped up using other methods
|
||||||
|
// NOTE: fx=fx * x + coeff[i] -> exp(log(fx) + log(x)) + coeff[i],
|
||||||
|
// so if fx===0, just set fx to coeff[i] because
|
||||||
|
// using the exp/log form will result in incorrect value
|
||||||
|
function horner(x, coeffs){
|
||||||
|
var logx = config.logs[x];
|
||||||
|
var fx = 0;
|
||||||
|
for(var i=coeffs.length-1; i>=0; i--){
|
||||||
|
if(fx === 0){
|
||||||
|
fx = coeffs[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fx = config.exps[ (logx + config.logs[fx]) % config.max ] ^ coeffs[i];
|
||||||
|
}
|
||||||
|
return fx;
|
||||||
|
};
|
||||||
|
|
||||||
|
function inArray(arr,val){
|
||||||
|
for(var i = 0,len=arr.length; i < len; i++) {
|
||||||
|
if(arr[i] === val){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function processShare(share){
|
||||||
|
|
||||||
|
var bits = parseInt(share[0], 36);
|
||||||
|
if(bits && (typeof bits !== 'number' || bits%1 !== 0 || bits<defaults.minBits || bits>defaults.maxBits)){
|
||||||
|
throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + defaults.maxBits + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var max = Math.pow(2, bits) - 1;
|
||||||
|
var idLength = max.toString(config.radix).length;
|
||||||
|
|
||||||
|
var id = parseInt(share.substr(1, idLength), config.radix);
|
||||||
|
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
|
||||||
|
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
|
||||||
|
}
|
||||||
|
share = share.substr(idLength + 1);
|
||||||
|
if(!share.length){
|
||||||
|
throw new Error('Invalid share: zero-length share.')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'bits': bits,
|
||||||
|
'id': id,
|
||||||
|
'value': share
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports._processShare = processShare;
|
||||||
|
|
||||||
|
// Protected method that evaluates the Lagrange interpolation
|
||||||
|
// polynomial at x=`at` for individual config.bits-length
|
||||||
|
// segments of each share in the `shares` Array.
|
||||||
|
// Each share is expressed in base `inputRadix`. The output
|
||||||
|
// is expressed in base `outputRadix'
|
||||||
|
function combine(at, shares){
|
||||||
|
var setBits, share, x = [], y = [], result = '', idx;
|
||||||
|
|
||||||
|
for(var i=0, len = shares.length; i<len; i++){
|
||||||
|
share = processShare(shares[i]);
|
||||||
|
if(typeof setBits === 'undefined'){
|
||||||
|
setBits = share['bits'];
|
||||||
|
}else if(share['bits'] !== setBits){
|
||||||
|
throw new Error('Mismatched shares: Different bit settings.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.bits !== setBits){
|
||||||
|
init(setBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inArray(x, share['id'])){ // repeated x value?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = x.push(share['id']) - 1;
|
||||||
|
share = split(hex2bin(share['value']));
|
||||||
|
for(var j=0, len2 = share.length; j<len2; j++){
|
||||||
|
y[j] = y[j] || [];
|
||||||
|
y[j][idx] = share[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i=0, len=y.length; i<len; i++){
|
||||||
|
result = padLeft(lagrange(at, x, y[i]).toString(2)) + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(at===0){// reconstructing the secret
|
||||||
|
var idx = result.indexOf('1'); //find the first 1
|
||||||
|
return bin2hex(result.slice(idx+1));
|
||||||
|
}else{// generating a new share
|
||||||
|
return bin2hex(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine `shares` Array into the original secret
|
||||||
|
/** @expose **/
|
||||||
|
exports.combine = function(shares){
|
||||||
|
return combine(0, shares);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate a new share with id `id` (a number between 1 and 2^bits-1)
|
||||||
|
// `id` can be a Number or a String in the default radix (16)
|
||||||
|
/** @expose **/
|
||||||
|
exports.newShare = function(id, shares){
|
||||||
|
if(typeof id === 'string'){
|
||||||
|
id = parseInt(id, config.radix);
|
||||||
|
}
|
||||||
|
|
||||||
|
var share = processShare(shares[0]);
|
||||||
|
var max = Math.pow(2, share['bits']) - 1;
|
||||||
|
|
||||||
|
if(typeof id !== 'number' || id%1 !== 0 || id<1 || id>max){
|
||||||
|
throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = max.toString(config.radix).length;
|
||||||
|
return config.bits.toString(36).toUpperCase() + padLeft(id.toString(config.radix), padding) + combine(id, shares);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Evaluate the Lagrange interpolation polynomial at x = `at`
|
||||||
|
// using x and y Arrays that are of the same length, with
|
||||||
|
// corresponding elements constituting points on the polynomial.
|
||||||
|
function lagrange(at, x, y){
|
||||||
|
var sum = 0,
|
||||||
|
product,
|
||||||
|
i, j;
|
||||||
|
|
||||||
|
for(var i=0, len = x.length; i<len; i++){
|
||||||
|
if(!y[i]){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
product = config.logs[y[i]];
|
||||||
|
for(var j=0; j<len; j++){
|
||||||
|
if(i === j){ continue; }
|
||||||
|
if(at === x[j]){ // happens when computing a share that is in the list of shares used to compute it
|
||||||
|
product = -1; // fix for a zero product term, after which the sum should be sum^0 = sum, not sum^1
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
product = ( product + config.logs[at ^ x[j]] - config.logs[x[i] ^ x[j]] + config.max/* to make sure it's not negative */ ) % config.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = product === -1 ? sum : sum ^ config.exps[product]; // though exps[-1]= undefined and undefined ^ anything = anything in chrome, this behavior may not hold everywhere, so do the check
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @expose **/
|
||||||
|
exports._lagrange = lagrange;
|
||||||
|
|
||||||
|
// Splits a number string `bits`-length segments, after first
|
||||||
|
// optionally zero-padding it to a length that is a multiple of `padLength.
|
||||||
|
// Returns array of integers (each less than 2^bits-1), with each element
|
||||||
|
// representing a `bits`-length segment of the input string from right to left,
|
||||||
|
// i.e. parts[0] represents the right-most `bits`-length segment of the input string.
|
||||||
|
function split(str, padLength){
|
||||||
|
if(padLength){
|
||||||
|
str = padLeft(str, padLength)
|
||||||
|
}
|
||||||
|
var parts = [];
|
||||||
|
for(var i=str.length; i>config.bits; i-=config.bits){
|
||||||
|
parts.push(parseInt(str.slice(i-config.bits, i), 2));
|
||||||
|
}
|
||||||
|
parts.push(parseInt(str.slice(0, i), 2));
|
||||||
|
return parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pads a string `str` with zeros on the left so that its length is a multiple of `bits`
|
||||||
|
function padLeft(str, bits){
|
||||||
|
bits = bits || config.bits
|
||||||
|
var missing = str.length % bits;
|
||||||
|
return (missing ? new Array(bits - missing + 1).join('0') : '') + str;
|
||||||
|
};
|
||||||
|
|
||||||
|
function hex2bin(str){
|
||||||
|
var bin = '', num;
|
||||||
|
for(var i=str.length - 1; i>=0; i--){
|
||||||
|
num = parseInt(str[i], 16)
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid hex character.')
|
||||||
|
}
|
||||||
|
bin = padLeft(num.toString(2), 4) + bin;
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bin2hex(str){
|
||||||
|
var hex = '', num;
|
||||||
|
str = padLeft(str, 4);
|
||||||
|
for(var i=str.length; i>=4; i-=4){
|
||||||
|
num = parseInt(str.slice(i-4, i), 2);
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid binary character.')
|
||||||
|
}
|
||||||
|
hex = num.toString(16) + hex;
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a given UTF16 character string to the HEX representation.
|
||||||
|
// Each character of the input string is represented by
|
||||||
|
// `bytesPerChar` bytes in the output string.
|
||||||
|
/** @expose **/
|
||||||
|
exports.str2hex = function(str, bytesPerChar){
|
||||||
|
if(typeof str !== 'string'){
|
||||||
|
throw new Error('Input must be a character string.');
|
||||||
|
}
|
||||||
|
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
|
||||||
|
|
||||||
|
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
|
||||||
|
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var hexChars = 2*bytesPerChar;
|
||||||
|
var max = Math.pow(16, hexChars) - 1;
|
||||||
|
var out = '', num;
|
||||||
|
for(var i=0, len=str.length; i<len; i++){
|
||||||
|
num = str[i].charCodeAt();
|
||||||
|
if(isNaN(num)){
|
||||||
|
throw new Error('Invalid character: ' + str[i]);
|
||||||
|
}else if(num > max){
|
||||||
|
var neededBytes = Math.ceil(Math.log(num+1)/Math.log(256));
|
||||||
|
throw new Error('Invalid character code (' + num +'). Maximum allowable is 256^bytes-1 (' + max + '). To convert this character, use at least ' + neededBytes + ' bytes.')
|
||||||
|
}else{
|
||||||
|
out = padLeft(num.toString(16), hexChars) + out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converts a given HEX number string to a UTF16 character string.
|
||||||
|
/** @expose **/
|
||||||
|
exports.hex2str = function(str, bytesPerChar){
|
||||||
|
if(typeof str !== 'string'){
|
||||||
|
throw new Error('Input must be a hexadecimal string.');
|
||||||
|
}
|
||||||
|
bytesPerChar = bytesPerChar || defaults.bytesPerChar;
|
||||||
|
|
||||||
|
if(typeof bytesPerChar !== 'number' || bytesPerChar%1 !== 0 || bytesPerChar<1 || bytesPerChar > defaults.maxBytesPerChar){
|
||||||
|
throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + ', inclusive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
var hexChars = 2*bytesPerChar;
|
||||||
|
var out = '';
|
||||||
|
str = padLeft(str, hexChars);
|
||||||
|
for(var i=0, len = str.length; i<len; i+=hexChars){
|
||||||
|
out = String.fromCharCode(parseInt(str.slice(i, i+hexChars),16)) + out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// by default, initialize without an RNG
|
||||||
|
exports.init();
|
||||||
|
})(typeof module !== 'undefined' && module['exports'] ? module['exports'] : (window['secrets'] = {}), typeof GLOBAL !== 'undefined' ? GLOBAL : window );
|
||||||
3
src/js/selector.js
Normal file
3
src/js/selector.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
$ = function(selector) {
|
||||||
|
return document.querySelectorAll(selector)[0];
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue