UNPKG

shamir

Version:

A JavaScript implementation of Shamir's Secret Sharing algorithm over GF(256).

101 lines (90 loc) 3.44 kB
/* * Copyright © 2019 Simon Massey (massey1905@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const GF256 = require('./GF256.js'); /** * Splits the given secret into {@code n} parts, of which any {@code k} or more can be combined to * recover the original secret. * @param {function int -> Uint8Array} randomBytes Takes a length and returns a random * Uint8Array of that length * @param {Number} n the number of parts to produce (must be {@code >1}) * @param {Number} k the threshold of joinable parts (must be {@code <= n}) * @param {array[Uint8Array]} secret The secret to split as an array of bytes * @return {Object.<string, Uint8Array>} an map of {@code n} parts that are arrays of bytes of the * secret length */ function split(randomBytes, n, k, secret) { if (k <= 1) throw new Error('K must be > 1'); if (n < k) throw new Error('N must be >= K'); if (n > 255) throw new Error('N must be <= 255'); const values = new Array(n) .fill(0) .map(() => new Uint8Array(secret.length).fill(0)); // eslint-disable-next-line no-plusplus for (let i = 0; i < secret.length; i++) { const p = GF256.generate(randomBytes, k - 1, secret[i]); // eslint-disable-next-line no-plusplus for (let x = 1; x <= n; x++) { values[x - 1][i] = GF256.eval(p, x); } } const parts = {}; // eslint-disable-next-line no-plusplus for (let i = 0; i < values.length; i++) { const part = `${i + 1}`; parts[part] = values[i]; } return parts; } exports.split = split; /** * Joins the given parts to recover the original secret. * * <p><b>N.B.:</b> There is no way to determine whether or not the returned value is actually the * original secret. If the parts are incorrect, or are under the threshold value used to split the * secret, a random value will be returned. * * @param {Object.<string, Uint8Array>} parts an map of {@code n} parts that are arrays of bytes * of the secret length * @return {Uint8Array} the original secret * */ function join(parts) { if (Object.keys(parts).length === 0) throw new Error('No parts provided'); const lengths = Object.values(parts).map(x => x.length); const max = Math.max.apply(null, lengths); const min = Math.min.apply(null, lengths); if (max !== min) { throw new Error(`Varying lengths of part values. Min ${min}, Max ${max}`); } const secret = new Uint8Array(max); // eslint-disable-next-line no-plusplus for (let i = 0; i < secret.length; i++) { const keys = Object.keys(parts); const points = new Array(keys.length) .fill(0) .map(() => new Uint8Array(2).fill(0)); // eslint-disable-next-line no-plusplus for (let j = 0; j < keys.length; j++) { const key = keys[j]; const k = Number(key); points[j][0] = k; points[j][1] = parts[key][i]; } secret[i] = GF256.interpolate(points); } return secret; } exports.join = join;