UNPKG

ara-crypto

Version:

Cryptographic functions used in Ara modules

226 lines (191 loc) 6.3 kB
/* eslint-disable no-inline-comments */ const isBuffer = require('is-buffer') const { ShareData } = require('./share') const { Secret } = require('./secret') const { Table } = require('./table') const { Codec } = require('./codec') /** * Shamir Secret Sharing state context. * @public * @class Context */ class Context { static defaults() { } /** * Context class constructor. * @public * @constructor * @param {Object} opts * @throws TypeError * @throws RangeError */ constructor(opts) { if (!opts || 'object' !== typeof opts) { throw new TypeError('Context: Expecting object.') } if ('number' !== typeof opts.bits) { throw new TypeError('Context: Expecting bits to be a number.') } if ('number' !== typeof opts.radix) { throw new TypeError('Context: Expecting radix to be a number.') } if ('number' != typeof opts.minBits) { throw new TypeError('Context: Expecting minBits to be a number.') } if ('number' != typeof opts.maxBits) { throw new TypeError('Context: Expecting maxBits to be a number.') } if ('number' != typeof opts.padding) { throw new TypeError('Context: Expecting padding to be a number.') } if ('number' !== typeof opts.bytesPerChar) { throw new TypeError('Context: Expecting bytesPerChar to be a number.') } if ('number' !== typeof opts.maxBytesPerChar) { throw new TypeError('Context: Expecting maxBytesPerChar to be a number.') } if (0 !== opts.bits % 1) { throw new TypeError('Context: Expecting bits to be an integer.') } if (0 !== opts.minBits % 1) { throw new TypeError('Context: Expecting minBits to be an integer.') } if (0 !== opts.maxBits % 1) { throw new TypeError('Context: Expecting maxBits to be an integer.') } if (0 !== opts.padding % 1) { throw new TypeError('Context: Expecting padding to be an integer.') } if (false === isBuffer(opts.entropy)) { throw new TypeError('Context: Expecting entropy to be a buffer.') } if ('function' !== typeof opts.prng) { throw new TypeError('Context: Expecting PRNG to be a function.') } this.bits = opts.bits this.size = 2 ** this.bits // `1 << k' where `k = bits' this.prng = opts.prng this.radix = opts.radix this.minBits = opts.minBits this.maxBits = opts.maxBits this.padding = opts.padding this.entropy = opts.entropy this.maxShares = this.size - 1 this.bytesPerChar = opts.bytesPerChar this.maxBytesPerChar = opts.maxBytesPerChar this.table = opts.table || new Table(this) this.codec = opts.codec || new Codec(this) } /** * Creates and returns a secret value from a string * or buffer. * @public * @param {String|Value} value * @param {?(String)} encoding * @return {Secret} */ secret(value, encoding) { return new Secret(this, value, encoding) } /** * Partitions a secret into a given number of shares * requiring some number threshold of shares to reconstruct * the secret. * @public * @param {Secret|Buffer|String} secret * @param {Object} opts * @param {Number} opts.shares * @param {Number} opts.threshold * @param {?(Number)} opts.padding * @return {Array<String>} * @throws TypeError */ // eslint-disable-next-line no-shadow shares(secret, opts) { if (!secret) { throw new TypeError('Context: Expecting secret to be a string or buffer.') } if (!opts || 'object' !== typeof opts) { throw new TypeError('Context: Expecting object.') } const { padding = this.padding } = opts // eslint-disable-next-line no-shadow const { shares, threshold } = opts const { maxShares, radix } = this if (!shares || 0 !== shares % 1 || shares > maxShares) { throw new TypeError('Context: Invalid shares count.') } if (!threshold || 0 !== threshold % 1 || threshold > maxShares) { throw new TypeError('Context: Invalid threshold count.') } if ('string' === typeof secret || isBuffer(secret)) { // eslint-disable-next-line no-param-reassign secret = this.secret(secret) } const bin = `1${this.codec.binary(secret.hex(), 16)}` const parts = this.codec.split(bin, padding, 2) const x = new Array(shares) const y = new Array(shares) for (let i = 0; i < parts.length; ++i) { const partitions = this.codec.partition(parts[i], { shares, threshold }) for (let j = 0; j < shares; ++j) { if (!x[j]) { x[j] = partitions[j].x.toString(radix) } const a = partitions[j].y.toString(2) const b = y[j] || '' y[j] = this.codec.pad(a) + b } } for (let i = 0; i < shares; ++i) { x[i] = this.codec.encode(x[i], this.codec.hex(y[i], 'binary')) x[i] = Buffer.from(0 + x[i], 'hex') // prefix with '0' to fit buffer } return x } /** * Recovers a secret for a set of shares usings Lagrange * interpolation * @public * @param {Array<String>} shares * @param {Object} opts * @return {Secret} * @throws TypeError */ // eslint-disable-next-line no-shadow recover(shares, opts) { const result = [] const x = [] const y = [] if (undefined !== opts && 'object' !== typeof opts) { throw new TypeError('Context: Expecting object.') } for (let i = 0; i < shares.length; ++i) { const share = new ShareData(this, shares[i]) if (false === x.includes(share.id)) { const bin = this.codec.binary(share.data, 16) x.push(share.id) const parts = this.codec.split(bin, 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 = this.codec.lagrange(0, { x, y: y[i] }) result.unshift(this.codec.pad(p.toString(2))) } const string = result.join('') const bin = string.slice(string.indexOf('1') + 1) const hex = this.codec.hex(bin, 'binary') const value = this.codec.decode(hex) // eslint-disable-next-line no-shadow const secret = new Secret(this, value) return secret } } module.exports = { Context }