UNPKG

j-bitcoin

Version:

Comprehensive JavaScript cryptocurrency wallet library for Bitcoin (BTC), Bitcoin Cash (BCH), and Bitcoin SV (BSV) with custodial and non-custodial wallet support, threshold signatures, and multiple address formats

275 lines (254 loc) 11.9 kB
/** * @fileoverview Polynomial operations for cryptographic secret sharing schemes * * This module implements polynomial arithmetic over finite fields, specifically * designed for use in Shamir's Secret Sharing and threshold signature schemes. * All operations are performed modulo the secp256k1 curve order. * * @see {@link https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing|Shamir's Secret Sharing} * @see {@link https://en.wikipedia.org/wiki/Lagrange_polynomial|Lagrange Interpolation} * @author yfbsei * @version 1.0.0 */ import { randomBytes } from 'node:crypto'; import BN from 'bn.js'; // secp256k1 curve order for modular arithmetic const N = new BN("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", "hex"); /** * @typedef {Array<Array<number>>} InterpolationPoints * @description Array of [x, y] coordinate pairs for polynomial interpolation * @example [[1, 123], [2, 456], [3, 789]] */ /** * Polynomial class for finite field arithmetic over secp256k1 curve order * * Provides polynomial operations essential for cryptographic secret sharing: * - Random polynomial generation for secret distribution * - Polynomial evaluation at specific points (share generation) * - Lagrange interpolation for secret reconstruction * - Polynomial arithmetic (addition, multiplication) * * All coefficients are BigNumbers reduced modulo the secp256k1 curve order, * ensuring compatibility with elliptic curve cryptographic operations. * * @class Polynomial * @example * // Create a random degree-2 polynomial for 3-of-5 threshold scheme * const poly = Polynomial.fromRandom(2); * * // Evaluate at points 1,2,3,4,5 to generate shares * const shares = [1,2,3,4,5].map(x => [x, poly.evaluate(x)]); * * // Reconstruct secret using any 3 shares * const secret = Polynomial.interpolate_evaluate(shares.slice(0,3), 0); */ class Polynomial { /** * Creates a polynomial with given coefficients * * The polynomial is represented as: f(x) = a₀ + a₁x + a₂x² + ... + aₙxⁿ * where coefficients[0] = a₀ (constant term), coefficients[1] = a₁, etc. * * @param {BN[]} coefficients - Array of BigNumber coefficients from constant to highest degree * @example * // Create polynomial f(x) = 5 + 3x + 2x² * const coeffs = [new BN(5), new BN(3), new BN(2)]; * const poly = new Polynomial(coeffs); * console.log(poly.order); // 2 (degree) */ constructor(coefficients) { /** * Polynomial degree (highest power of x) * @type {number} * @readonly */ this.order = coefficients.length - 1; /** * Array of polynomial coefficients as BigNumbers * @type {BN[]} * @readonly */ this.coefficients = coefficients; } /** * Generates a random polynomial of specified degree using cryptographically secure randomness * * Each coefficient is generated using 32 bytes of secure random data, * ensuring unpredictability suitable for cryptographic applications. * The constant term (coefficients[0]) becomes the secret to be shared. * * @static * @param {number} [order=2] - Degree of the polynomial to generate * @returns {Polynomial} New polynomial with random coefficients * @example * // Generate random polynomial for 2-of-3 threshold (degree = threshold - 1) * const poly = Polynomial.fromRandom(2); * * // Generate shares by evaluating at points 1, 2, 3 * const share1 = poly.evaluate(1); * const share2 = poly.evaluate(2); * const share3 = poly.evaluate(3); * * // Any 2 shares can reconstruct the secret (coefficients[0]) */ static fromRandom(order = 2) { const coefficients = new Array(order + 1).fill(null).map(_ => new BN(randomBytes(32))); return new Polynomial(coefficients); } /** * Reconstructs a secret using Lagrange interpolation from coordinate points * * Implements Lagrange interpolation to evaluate a polynomial at point x * given sufficient coordinate pairs. This is the core operation for * reconstructing secrets in Shamir's Secret Sharing. * * The algorithm computes: f(x) = Σᵢ yᵢ * Lᵢ(x) * where Lᵢ(x) = Πⱼ≠ᵢ (x - xⱼ) / (xᵢ - xⱼ) * * @static * @param {InterpolationPoints} [points=[[1, 2], [1,2]]] - Array of [x, y] coordinate pairs * @param {number} [x=2] - Point at which to evaluate the interpolated polynomial * @returns {BN} The interpolated value f(x) modulo curve order * @example * // Reconstruct secret from threshold shares * const shares = [[1, new BN("123")], [2, new BN("456")], [3, new BN("789")]]; * const secret = Polynomial.interpolate_evaluate(shares, 0); // Evaluate at x=0 * * // Verify polynomial evaluation at known point * const poly = Polynomial.fromRandom(2); * const testPoints = [[1, poly.evaluate(1)], [2, poly.evaluate(2)], [3, poly.evaluate(3)]]; * const reconstructed = Polynomial.interpolate_evaluate(testPoints, 5); * const direct = poly.evaluate(5); * console.log(reconstructed.eq(direct)); // true */ static interpolate_evaluate(points = [[1, 2], [1, 2]], x = 2) { let lagrange = new Array(points.length).fill(null); let denominator_product = 1; // Compute Lagrange basis polynomials for (let i = 0; i < points.length; i++) { let [numerator, denominator] = [1, 1]; // Compute Lᵢ(x) = Πⱼ≠ᵢ (x - xⱼ) / (xᵢ - xⱼ) for (let j = 0; j < points.length; j++) { if (j !== i) { numerator *= (x - points[j][0]); // (x - xⱼ) denominator *= (points[i][0] - points[j][0]); // (xᵢ - xⱼ) } } // Store [yᵢ * numerator, denominator] for later processing lagrange[i] = [new BN(points[i][1]).muln(numerator), denominator]; denominator_product *= denominator; } // Compute final result: Σᵢ yᵢ * Lᵢ(x) const numerator_sum = lagrange.reduce((total, val) => total.add(val[0].muln(denominator_product).divRound(new BN(val[1]))), new BN(0)); return numerator_sum.divRound(new BN(denominator_product)).umod(N); } /** * Evaluates the polynomial at a given point using Horner's method * * Efficiently computes f(x) = a₀ + a₁x + a₂x² + ... + aₙxⁿ * using Horner's method: f(x) = a₀ + x(a₁ + x(a₂ + x(a₃ + ...))) * * This method is used to generate shares in secret sharing schemes * by evaluating the polynomial at participant indices. * * @param {number} [x=2] - Point at which to evaluate the polynomial * @returns {BN} The polynomial value f(x) modulo curve order * @example * // Generate shares for a 3-of-5 threshold scheme * const secret = new BN("deadbeefcafe", 'hex'); * const coeffs = [secret, new BN(randomBytes(32)), new BN(randomBytes(32))]; * const poly = new Polynomial(coeffs); * * // Generate 5 shares * const shares = []; * for (let i = 1; i <= 5; i++) { * shares.push([i, poly.evaluate(i)]); * } * * // Any 3 shares can reconstruct the secret * const reconstructed = Polynomial.interpolate_evaluate(shares.slice(0, 3), 0); * console.log(reconstructed.eq(secret)); // true */ evaluate(x = 2) { return this.coefficients.reduce((total, val) => { total[1].iadd(val.muln(total[0])); // y = y + (coefficient * x^power) total[0] *= x; // Increment power of x return [total[0], total[1]]; }, [1, new BN(0)])[1].umod(N); } /** * Adds two polynomials coefficient-wise * * Performs polynomial addition: (f + g)(x) = f(x) + g(x) * The resulting polynomial has degree max(deg(f), deg(g)) * * This operation is useful in cryptographic protocols that require * linear combinations of shared secrets. * * @param {Polynomial} [other={order: 1, coefficients: [1, 2, 3]}] - Polynomial to add * @returns {Polynomial} New polynomial representing the sum * @example * // Add two random polynomials * const poly1 = Polynomial.fromRandom(2); // f(x) = a₀ + a₁x + a₂x² * const poly2 = Polynomial.fromRandom(2); // g(x) = b₀ + b₁x + b₂x² * const sum = poly1.add(poly2); // h(x) = (a₀+b₀) + (a₁+b₁)x + (a₂+b₂)x² * * // Verify addition property: h(5) = f(5) + g(5) * const x = 5; * const sumAtX = sum.evaluate(x); * const directSum = poly1.evaluate(x).add(poly2.evaluate(x)).umod(N); * console.log(sumAtX.eq(directSum)); // true */ add(other = { order: 1, coefficients: [1, 2, 3] }) { // Determine which polynomial has more coefficients const longest = (this.order > other.order) ? this.coefficients : other.coefficients; const shortest = (other.order < this.order) ? other.coefficients : this.coefficients; // Add corresponding coefficients const coefficients = new Array(shortest.length) .fill(null) .map((_, i) => shortest[i].add(longest[i])) // Add overlapping coefficients .concat(longest.slice(shortest.length)) // Append remaining coefficients .map(val => val.umod(N)); // Reduce modulo curve order return new Polynomial(coefficients); } /** * Multiplies two polynomials using convolution * * Performs polynomial multiplication: (f * g)(x) = f(x) * g(x) * The resulting polynomial has degree deg(f) + deg(g) * * Uses the standard convolution algorithm where each coefficient of the result * is the sum of products of coefficients whose indices sum to that position. * * @param {Polynomial} [other={order: 1, coefficients: [1, 2, 3]}] - Polynomial to multiply * @returns {Polynomial} New polynomial representing the product * @example * // Multiply two polynomials: (2 + 3x) * (1 + 4x) = 2 + 11x + 12x² * const poly1 = new Polynomial([new BN(2), new BN(3)]); // 2 + 3x * const poly2 = new Polynomial([new BN(1), new BN(4)]); // 1 + 4x * const product = poly1.multiply(poly2); // 2 + 11x + 12x² * * // Verify: coefficients should be [2, 11, 12] * console.log(product.coefficients[0].toNumber()); // 2 * console.log(product.coefficients[1].toNumber()); // 11 * console.log(product.coefficients[2].toNumber()); // 12 */ multiply(other = { order: 1, coefficients: [1, 2, 3] }) { // Initialize result coefficients array with zeros let coefficients = new Array(this.order + other.order + 1).fill(new BN(0)); // Compute convolution: c[i+j] += a[i] * b[j] for (let i = 0; i < this.coefficients.length; i++) { for (let j = 0; j < other.coefficients.length; j++) { coefficients[i + j] = coefficients[i + j].add( this.coefficients[i].mul(other.coefficients[j]) ); } } // Reduce all coefficients modulo curve order coefficients = coefficients.map(val => val.umod(N)); return new Polynomial(coefficients); } } export default Polynomial;