shamirs-secret-sharing
Version:
A simple implementation of Shamir's Secret Sharing configured to use a finite field in GF(2^8) with 128 bit padding
821 lines (812 loc) • 23.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from2, except, desc) => {
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
for (let key of __getOwnPropNames(from2))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// index.js
var shamirs_secret_sharing_exports = {};
__export(shamirs_secret_sharing_exports, {
Buffer: () => buffer_default,
VERSION: () => VERSION,
combine: () => combine_default,
constants: () => constants_default,
default: () => shamirs_secret_sharing_default,
split: () => split_default
});
module.exports = __toCommonJS(shamirs_secret_sharing_exports);
// constants.js
var PRIMITIVE_POLYNOMIAL = 29;
var BIT_PADDING = 128;
var BIT_COUNT = 8;
var BIT_SIZE = 2 ** BIT_COUNT;
var BYTES_PER_CHARACTER = 2;
var MAX_BYTES_PER_CHARACTER = 6;
var MAX_SHARES = BIT_SIZE - 1;
var UTF8_ENCODING = "utf8";
var BIN_ENCODING = "binary";
var HEX_ENCODING = "hex";
var constants_default = {
PRIMITIVE_POLYNOMIAL,
BIT_PADDING,
BIT_COUNT,
BIT_SIZE,
MAX_SHARES,
MAX_BYTES_PER_CHARACTER,
BYTES_PER_CHARACTER,
UTF8_ENCODING,
BIN_ENCODING,
HEX_ENCODING
};
// buffer.js
var textEncoder = new TextEncoder();
var textDecoder = new TextDecoder();
var ALPHA16_CHARS = "0123456789abcdef";
var ALPHA16_ARRAY_TABLE = new Array(256);
for (let i = 0; i < 16; ++i) {
const i16 = i * 16;
for (let j = 0; j < 16; ++j) {
ALPHA16_ARRAY_TABLE[i16 + j] = ALPHA16_CHARS[i] + ALPHA16_CHARS[j];
}
}
var RANDOM_BYTES_QUOTA = 64 * 1024;
var TypedArrayPrototypeSet = /* @__PURE__ */ new Set([
Int8Array.prototype,
Int16Array.prototype,
Int32Array.prototype,
BigInt64Array.prototype,
Uint8Array.prototype,
Uint16Array.prototype,
Uint32Array.prototype,
BigUint64Array.prototype,
Float32Array.prototype,
Float64Array.prototype
]);
function alloc(byteLength) {
if (byteLength < 0 || !Number.isFinite(byteLength) || !Number.isSafeInteger(byteLength)) {
throw new RangeError(
`The argument 'byteLength' is invalid: Received ${byteLength}`
);
}
return new Buffer2(byteLength);
}
function isBufferLike(input) {
if (!input) {
return false;
}
return input instanceof ArrayBuffer || ArrayBuffer.isView(input) || Array.isArray(input) || typeof input === "object" && "length" in input && typeof input.length === "number";
}
function isTypedArray(input, TypedArray = null) {
if (!isBufferLike(input)) {
return false;
}
if (typeof TypedArray === "function") {
if (TypedArrayPrototypeSet.has(TypedArray.prototype)) {
const prototype2 = Object.getPrototypeOf(input);
return prototype2 === TypedArray.prototype || TypedArray.prototype.isPrototypeOf(prototype2);
}
}
const prototype = Object.getPrototypeOf(input);
for (const TypedArrayPrototype of TypedArrayPrototypeSet) {
if (TypedArrayPrototype === prototype || TypedArrayPrototype.isPrototypeOf(prototype)) {
return true;
}
}
return false;
}
function isArrayBuffer(input) {
if (!input) {
return false;
}
const TypeClass = (
/** @type {object} */
input.constructor
);
return TypeClass === ArrayBuffer || ArrayBuffer.prototype.isPrototypeOf(Object.getPrototypeOf(input));
}
function getByteLength(input, encoding = "utf8") {
if (typeof input === "string") {
if (encoding === "hex") {
return input.length >>> 1;
} else if (encoding === "utf8") {
return textEncoder.encode(input).byteLength;
} else {
return input.length;
}
}
if (!isBufferLike(input)) {
return 0;
}
if ("byteLength" in /** @type {BufferLike} */
input) {
return input.byteLength;
}
if ("length" in input) {
for (
const element of
/** @type {Array} */
input
) {
if (!Number.isFinite(element) || !Number.isInteger(element) || !Number.isSafeInteger(element)) {
return 0;
}
}
return input.length;
}
return 0;
}
function getArrayBuffer(input) {
if (!isBufferLike(input)) {
return null;
}
if (isArrayBuffer(input)) {
return input;
}
if (isTypedArray(
/** @type {TypedArray} */
input
)) {
return (
/** @type {TypedArray} */
input.buffer
);
}
if (isArrayBuffer(input.buffer)) {
return (
/** @type {ArrayBuffer} */
input.buffer
);
}
return null;
}
function concat(...args) {
if (args.length === 0) {
return null;
}
if (args.length === 1 && Array.isArray(args[0])) {
return concat(...args[0]);
}
for (const arg of args) {
if (!isBufferLike(arg)) {
return null;
}
}
let view;
let buffer;
const first = args.shift();
if (first === void 0 || first === null) {
return null;
}
const TypeClass = first.constructor;
const totalSize = [first].concat(args).map((arg) => getByteLength(arg)).reduce((a, b) => a + b, 0);
if (
// handle `ArrayBuffer` or `ArrayBuffer` ancestor
TypeClass === ArrayBuffer || ArrayBuffer.prototype.isPrototypeOf(Object.getPrototypeOf(first))
) {
buffer = new /** @type {typeof ArrayBuffer} */
TypeClass(totalSize);
if (buffer.byteLength !== totalSize) {
throw new TypeError("Unable to correctly allocate output buffer");
}
view = new Uint8Array(buffer);
} else if (
// handle `Array` or `Array` ancestor
TypeClass === Array || Array.isArray(first) || Array.prototype.isPrototypeOf(Object.getPrototypeOf(first))
) {
buffer = new /** @type {typeof Array} */
TypeClass(totalSize);
if (getByteLength(buffer) !== totalSize) {
throw new TypeError("Unable to correctly allocate output buffer");
}
const arrayBuffer = getArrayBuffer(buffer);
view = isArrayBuffer(arrayBuffer) ? new Uint8Array(
/** @type {ArrayBuffer} */
arrayBuffer
) : Uint8Array.from(buffer);
} else if (
// handle `TypedArray` descendants
isTypedArray(first)
) {
buffer = /** @type {TypedArray} */
new /** @type {{ new (number) }} */
TypeClass(totalSize);
if (
/** @type {TypedArray} */
buffer.byteLength !== totalSize
) {
throw new TypeError("Unable to correctly allocate output buffer");
}
const arrayBuffer = getArrayBuffer(buffer);
view = new Uint8Array(
/** @type {ArrayBuffer} */
arrayBuffer
);
} else {
buffer = new /** @type {typeof Array} */
TypeClass(totalSize);
if (getByteLength(buffer) !== totalSize) {
throw new TypeError("Unable to correctly allocate output buffer");
}
const arrayBuffer = getArrayBuffer(buffer);
view = isArrayBuffer(arrayBuffer) ? new Uint8Array(
/** @type {ArrayBuffer} */
arrayBuffer
) : Uint8Array.from(buffer);
}
const buffers = [first].concat(args);
let offset = 0;
while (buffers.length) {
const arrayBuffer = getArrayBuffer(buffers.shift());
if (!arrayBuffer) {
throw new TypeError("Unable to determine ArrayBuffer in arguments");
}
const array = new Uint8Array(arrayBuffer);
view.set(array, offset);
offset += array.byteLength;
}
return buffer;
}
function create(input, byteOffset = input.byteOffset || 0, byteLength = getByteLength(input), encoding = "utf8") {
if (typeof input === "string") {
if (encoding === "hex") {
byteLength = getByteLength(input, "hex");
const buffer = new Buffer2(byteLength);
for (let i = 0; i < input.length; ++i) {
const offset = 2 * i;
const byte = parseInt(input.slice(offset, offset + 2), 16);
if (Number.isNaN(byte)) {
break;
}
buffer[i] = byte;
}
return buffer;
}
if (encoding === "base64") {
const string = globalThis.atob(input);
const buffer = new Buffer2(string.length);
for (let i = 0; i < string.length; ++i) {
buffer[i] = string.charCodeAt(i);
}
return buffer;
}
return create(textEncoder.encode(input));
}
if (isBufferLike(input)) {
const arrayBuffer = getArrayBuffer(input);
if (isArrayBuffer(arrayBuffer)) {
return new Buffer2(
/** @type {ArrayBuffer} */
arrayBuffer,
input.byteOffset || byteOffset,
byteLength
);
}
}
if (Array.isArray(input)) {
return new Buffer2(input);
}
return new Buffer2(0);
}
function from(input, byteOffset, byteLength, encoding = "utf8") {
if (typeof byteOffset === "string") {
encoding = byteOffset;
return create(input, 0, getByteLength(input), encoding);
}
return create(input, byteOffset || 0, byteLength || getByteLength(input), encoding);
}
function compare(a, b, encoding = "utf8") {
if (a instanceof Uint8Array || Uint8Array.prototype.isPrototypeOf(Object.getPrototypeOf(a))) {
a = from(
a,
/** @type {Uint8Array} */
a.byteOffset,
/** @type {Uint8Array} */
a.byteLength
);
}
if (b instanceof Uint8Array || Uint8Array.prototype.isPrototypeOf(Object.getPrototypeOf(b))) {
b = from(
b,
/** @type {Uint8Array} */
b.byteOffset,
/** @type {Uint8Array} */
b.byteLength
);
}
if (typeof a === "string") {
a = from(a, encoding);
}
if (typeof b === "string") {
b = from(b, encoding);
}
if (!isBufferLike(a) || !isBufferLike(b)) {
throw new TypeError(
'Input buffers must be "buffer like"'
);
}
if (a === b) {
return 0;
}
if (isArrayBuffer(a)) {
a = from(a);
}
if (isArrayBuffer(b)) {
b = from(b);
}
let x = (
/** @type {Uint8Array} */
a.byteLength
);
let y = (
/** @type {Uint8Array} */
b.byteLength
);
for (let i = 0, length = Math.min(x, y); i < length; ++i) {
if (a[i] !== b[i]) {
x = a[i];
y = b[i];
break;
}
}
if (x < y) {
return -1;
} else if (y < x) {
return 1;
}
return 0;
}
function toString(input, encoding = "utf8") {
const buffer = from(input);
if (encoding === "hex") {
const output = [];
for (let i = 0; i < buffer.byteLength; ++i) {
output.push(ALPHA16_ARRAY_TABLE[buffer[i]]);
}
return output.join("");
}
if (encoding === "base64") {
const output = [];
for (let i = 0; i < buffer.byteLength; ++i) {
output.push(String.fromCharCode(buffer[i]));
}
return globalThis.btoa(output.join(""));
}
return textDecoder.decode(buffer);
}
function randomBytes(byteLength) {
const buffers = [];
if (typeof globalThis.crypto?.getRandomValues !== "function") {
throw new TypeError(
"Missing globalThis.crypto.getRandomValues implementation"
);
}
if (byteLength <= 0 || !Number.isFinite(byteLength) || !Number.isSafeInteger(byteLength)) {
throw new RangeError(
`The argument 'byteLength' is invalid: Received ${byteLength}`
);
}
let byteLengthRemaining = byteLength;
do {
const byteLength2 = byteLengthRemaining > RANDOM_BYTES_QUOTA ? RANDOM_BYTES_QUOTA : byteLengthRemaining;
const bytes = globalThis.crypto.getRandomValues(new Int8Array(byteLength2));
const buffer = Buffer2.from(bytes);
buffers.push(buffer);
byteLengthRemaining = Math.max(0, byteLengthRemaining - RANDOM_BYTES_QUOTA);
} while (byteLengthRemaining > 0);
return Buffer2.concat(buffers);
}
var _Buffer = class extends Uint8Array {
static get Buffer() {
return _Buffer;
}
/**
* Predicate function to deterine if `input` is a `Buffer`
* @param {any} input
* @return {boolean}
*/
static isBuffer(input) {
return isTypedArray(input, this) || isTypedArray(input, Uint8Array);
}
/**
* Computed byte length
* @type {number}
*/
get length() {
return this.byteLength;
}
/**
* Converts this `Buffer` instance to a string with an optional
* encoding
* @param {string} [encoding]
*/
toString(encoding = "utf8") {
return toString(this, encoding);
}
/**
* Converts this `Buffer` class to a JSON object.
*/
toJSON() {
return {
type: "Buffer",
data: Array.from(this)
};
}
};
var Buffer2 = _Buffer;
__publicField(Buffer2, "alloc", alloc);
__publicField(Buffer2, "byteLength", getByteLength);
__publicField(Buffer2, "compare", compare);
__publicField(Buffer2, "concat", concat);
__publicField(Buffer2, "from", from);
__publicField(Buffer2, "isBufferLike", isBufferLike);
__publicField(Buffer2, "random", randomBytes);
TypedArrayPrototypeSet.add(Buffer2.prototype);
var buffer_default = Buffer2;
// table.js
var zeroes = new Array(4 * BIT_SIZE).join("0");
var logs = new Array(BIT_SIZE).fill(0);
var exps = new Array(BIT_SIZE).fill(0);
for (let i = 0, x = 1; i < BIT_SIZE; ++i) {
exps[i] = x;
logs[x] = i;
x = x << 1;
if (x >= BIT_SIZE) {
x = x ^ PRIMITIVE_POLYNOMIAL;
x = x & MAX_SHARES;
}
}
// codec.js
function pad(input, multiple = BIT_COUNT) {
const string = Buffer2.from(input).toString();
if (multiple === 0) {
return string;
} else if (!multiple) {
multiple = BIT_COUNT;
}
const missing = string ? string.length % multiple : 0;
if (missing) {
const offset = -1 * (multiple - missing + string.length);
return (zeroes + string).slice(offset);
}
return string;
}
function hex(input, encoding = UTF8_ENCODING) {
const padding = 2 * BYTES_PER_CHARACTER;
if (!encoding) {
encoding = UTF8_ENCODING;
}
if (typeof input === "string") {
return fromString(
/** @type {string} */
input
);
}
if (isBufferLike(input)) {
return fromBuffer(Buffer2.from(
/** @type {BufferLike} */
input
));
}
throw new TypeError("Expecting a string or buffer as input.");
function fromString(string) {
const chunks = (
/** @type {string[]} */
[]
);
if (UTF8_ENCODING === encoding) {
for (let i = 0; i < /** @type {string} */
string.length; ++i) {
const chunk = String.fromCharCode(string[i].toString(16));
const padded = pad(chunk, padding);
chunks.unshift(padded);
}
}
if (BIN_ENCODING === encoding) {
string = pad(input, 4);
for (let i = string.length; i >= 4; i -= 4) {
const bits = string.slice(i - 4, i);
const chunk = parseInt(bits, 2).toString(16);
chunks.unshift(chunk);
}
}
return chunks.join("");
}
function fromBuffer(buffer) {
const chunks = [];
for (let i = 0; i < buffer.length; ++i) {
const chunk = buffer[i].toString(16);
const padded = pad(chunk, padding);
chunks.unshift(padded);
}
return chunks.join("");
}
}
function bin(input, radix = 16) {
const chunks = [];
if (!radix) {
radix = 16;
}
const byteLength = Buffer2.byteLength(input);
for (let i = byteLength - 1; i >= 0; --i) {
let chunk;
if (isBufferLike(input)) {
chunk = input[i];
}
if (typeof input === "string") {
chunk = parseInt(input[i], radix);
}
if (Array.isArray(input)) {
chunk = input[i];
if (typeof chunk === "string") {
chunk = parseInt(chunk, radix);
}
}
if (chunk === void 0) {
throw new TypeError("Unsupported type for chunk in input.");
}
const padded = pad(chunk.toString(2), 4);
chunks.unshift(padded);
}
return chunks.join("");
}
function encode(id, data) {
id = typeof id === "number" ? id : (
/** @type {number} */
parseInt(Buffer2.from(id).toString(), 16)
);
const padding = (BIT_SIZE - 1).toString(16).length;
const header = Buffer2.concat([
// `BIT_COUNT` is stored as a base36 value, which in this case is the literal '8'
Buffer2.from(BIT_COUNT.toString(36).toUpperCase()),
// 8
Buffer2.from(pad(id.toString(16), padding))
]);
if (!isBufferLike(data)) {
data = Buffer2.from(data);
}
return Buffer2.concat([header, data]);
}
function decode(input, encoding = "utf8") {
const padding = 2 * BYTES_PER_CHARACTER;
const string = pad(Buffer2.from(input).toString(encoding), padding);
const offset = padding;
const chunks = [];
for (let i = 0; i < string.length; i += offset) {
const bits = string.slice(i, i + offset);
const chunk = parseInt(bits, 16);
chunks.unshift(chunk);
}
return Buffer2.from(chunks);
}
function split(input, padding = 0, radix = 2) {
const chunks = [];
const string = pad(Buffer2.from(input).toString(), padding);
let i = 0;
for (i = string.length; i > BIT_COUNT; i -= BIT_COUNT) {
const bits = string.slice(i - BIT_COUNT, i);
const chunk = parseInt(bits, radix);
chunks.push(chunk);
}
chunks.push(parseInt(string.slice(0, i), radix));
return chunks;
}
var codec_default = {
bin,
decode,
encode,
hex,
pad,
split
};
// combine.js
var MAX_BITS = BIT_SIZE - 1;
var Share = class {
id = 0;
bits = 0;
data = "";
/**
* Creates a `Share` object from a variety of input
* @param {ShareData|number|string} [id]
* @param {?number} [bits = 0]
* @param {?string} [data = null]
*/
static from(id = 0, bits = 0, data = null) {
if (id !== null && typeof id === "object") {
const share = (
/** @type {ShareData} */
id
);
return new this(share.id, share.bits || bits, share.data || data);
}
return new this(id || 0, bits || 0, data);
}
/**
* `Share` class constructor.
* @param {string|number} [id]
* @param {string|number} [bits]
* @param {?string} [data]
*/
constructor(id = 0, bits = 0, data = null) {
this.id = typeof id === "number" ? id : parseInt(id, 16);
this.bits = typeof bits === "number" ? bits : parseInt(bits, 36);
this.data = typeof data === "string" ? data : "";
}
};
function parse(input) {
const share = new Share();
const string = isBufferLike(input) ? Buffer2.from(input).toString("hex") : input;
const normalized = string[0] === "0" ? string.slice(1) : string;
share.bits = parseInt(normalized.slice(0, 1), 36);
const idLength = MAX_BITS.toString(16).length;
const regex = `^([a-kA-K3-9]{1})([a-fA-F0-9]{${idLength}})([a-fA-F0-9]+)$`;
const matches = new RegExp(regex).exec(normalized);
if (matches && matches.length) {
share.id = parseInt(matches[2], 16);
share.data = matches[3];
}
return share;
}
function lagrange(x, p) {
const n = MAX_SHARES;
let product = 0;
let sum = 0;
for (let i = 0; i < p[0].length; ++i) {
if (p[1][i]) {
product = logs[p[1][i]];
for (let j = 0; j < p[0].length; ++j) {
if (i !== j) {
if (x === p[0][j]) {
product = -1;
break;
}
const a = logs[x ^ p[0][j]] - logs[p[0][i] ^ p[0][j]];
product = (product + a + n) % n;
}
}
sum = -1 === sum ? sum : sum ^ exps[product];
}
}
return sum;
}
function combine(shares) {
const chunks = [];
const x = [];
const y = [];
const t = shares.length;
for (let i = 0; i < t; ++i) {
const share = parse(shares[i]);
if (x.indexOf(share.id) === -1) {
x.push(share.id);
const bin3 = codec_default.bin(share.data, 16);
const parts = codec_default.split(bin3, 0, 2);
for (let j = 0; j < parts.length; ++j) {
if (!y[j]) {
y[j] = [];
}
y[j][x.length - 1] = parts[j];
}
}
}
for (let i = 0; i < y.length; ++i) {
const p = lagrange(0, [x, y[i]]);
const padded = codec_default.pad(p.toString(2));
chunks.unshift(padded);
}
const string = chunks.join("");
const bin2 = string.slice(1 + string.indexOf("1"));
const hex2 = codec_default.hex(bin2, BIN_ENCODING);
const value = codec_default.decode(hex2);
return Buffer2.from(value);
}
var combine_default = combine;
// split.js
var scratch = new Array(2 * MAX_SHARES);
function horner(x, a) {
const n = MAX_SHARES;
const t = a.length - 1;
let b = 0;
for (let i = t; i >= 0; --i) {
b = 0 === b ? a[i] : exps[(logs[x] + logs[b]) % n] ^ a[i];
}
return b;
}
function computePoints(a0, options) {
const prng = options.random;
const a = [a0];
const p = [];
const t = options.threshold;
const n = options.shares;
for (let i = 1; i < t; ++i) {
a[i] = parseInt(prng(1).toString("hex"), 16);
}
for (let i = 1; i < 1 + n; ++i) {
p[i - 1] = {
x: i,
y: horner(i, a)
};
}
return p;
}
function split2(input, options) {
if (!input) {
throw new TypeError("An input secret must be provided");
}
const secret = buffer_default.from(input);
if (secret.byteLength === 0) {
throw new TypeError("Secret cannot be empty");
}
if (!options || typeof options !== "object") {
throw new TypeError("Expecting options to be an object.");
}
if ("shares" in options && typeof options.shares !== "number") {
throw new TypeError("Expecting shares to be a number.");
}
if ("threshold" in options && typeof options.threshold !== "number") {
throw new TypeError("Expecting threshold to be a number.");
}
if (!Number.isFinite(options.shares) || !Number.isInteger(options.shares) || !Number.isSafeInteger(options.shares)) {
throw new RangeError("Expecting shares to be a positive integer");
}
if (!Number.isFinite(options.threshold) || !Number.isInteger(options.threshold) || !Number.isSafeInteger(options.threshold)) {
throw new RangeError("Expecting threshold to be a positive integer");
}
if (options.shares <= 0 || options.shares > MAX_SHARES) {
throw new RangeError(`Shares must be 0 < shares <= ${MAX_SHARES}.`);
}
if (options.threshold <= 0 || options.threshold > options.shares) {
throw new RangeError(`Threshold must be 0 < threshold <= ${options.shares}.`);
}
if (options.random !== null && options.random !== void 0) {
throw new TypeError("Expecting random to be a function");
}
if (typeof options.random !== "function") {
options.random = buffer_default.random;
}
const hex2 = codec_default.hex(secret);
const bin2 = codec_default.bin(hex2, 16);
const parts = codec_default.split("1" + bin2, BIT_PADDING, 2);
for (let i = 0; i < parts.length; ++i) {
const p = computePoints(parts[i], {
shares: options.shares,
threshold: options.threshold,
random: options.random
});
for (let j = 0; j < options.shares; ++j) {
if (!scratch[j]) {
scratch[j] = p[j].x.toString(16);
}
const z = p[j].y.toString(2);
const y = scratch[j + MAX_SHARES] || "";
scratch[j + MAX_SHARES] = codec_default.pad(z) + y;
}
}
for (let i = 0; i < options.shares; ++i) {
const x = scratch[i];
const y = codec_default.hex(scratch[i + MAX_SHARES], BIN_ENCODING);
scratch[i] = codec_default.encode(x, y);
scratch[i] = buffer_default.from("0" + scratch[i], "hex");
}
const result = scratch.slice(0, options.shares);
scratch.fill(0);
return result;
}
var split_default = split2;
// index.js
var VERSION = "2";
var shamirs_secret_sharing_default = { Buffer: buffer_default, combine: combine_default, constants: constants_default, split: split_default };