j-bitcoin
Version:
Comprehensive JavaScript/TypeScript Bitcoin (BTC) wallet library with custodial and non-custodial support, hierarchical deterministic keys, threshold signatures, and advanced cryptographic features
267 lines (233 loc) • 9.3 kB
JavaScript
/**
* @fileoverview Multi-Party Computation operations for threshold cryptography
* @description Implements ADDSS, PROSS, and INVSS from Sections 2.2-2.4 of the nChain whitepaper.
* These operations allow computation on shared secrets without revealing them.
* @version 1.0.0
* @author yfbsei
* @license ISC
* @see Threshold-Signatures-whitepaper-nchain.pdf Sections 2.2, 2.3, 2.4
*/
import BN from 'bn.js';
import { Polynomial, CURVE_ORDER } from './polynomial.js';
/**
* Error class for MPC operations
*/
class MPCError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'MPCError';
this.code = code;
this.details = details;
}
}
/**
* Section 2.2: Addition of Shared Secrets (ADDSS)
*
* Given shares of two secrets a and b, computes a + b.
* Each participant calculates ν_i = a_i + b_i, then interpolates.
*
* @param {Array<{x: BN, y: BN}>} aShares - Shares of secret a
* @param {Array<{x: BN, y: BN}>} bShares - Shares of secret b
* @param {number} threshold - Required threshold (t+1)
* @returns {BN} The sum a + b
*/
function ADDSS(aShares, bShares, threshold) {
if (aShares.length < threshold || bShares.length < threshold) {
throw new MPCError(
`Need at least ${threshold} shares, got ${Math.min(aShares.length, bShares.length)}`,
'INSUFFICIENT_SHARES'
);
}
// Step 3: Each participant calculates additive share ν_i = a_i + b_i
const additiveShares = [];
for (let i = 0; i < Math.min(aShares.length, bShares.length); i++) {
const a_i = aShares[i].y instanceof BN ? aShares[i].y : new BN(aShares[i].y);
const b_i = bShares[i].y instanceof BN ? bShares[i].y : new BN(bShares[i].y);
const x = aShares[i].x instanceof BN ? aShares[i].x : new BN(aShares[i].x);
additiveShares.push({
x: x,
y: a_i.add(b_i).umod(CURVE_ORDER)
});
}
// Step 5: Interpolate over at least (t+1) shares to get ν = a + b
const sharesToUse = additiveShares.slice(0, threshold);
return Polynomial.interpolate(sharesToUse, new BN(0));
}
/**
* Calculate additive shares without interpolation
* Returns shares that can be used for further MPC operations
*
* @param {Array<{x: BN, y: BN}>} aShares - Shares of secret a
* @param {Array<{x: BN, y: BN}>} bShares - Shares of secret b
* @returns {Array<{x: BN, y: BN}>} Additive shares ν_i = a_i + b_i
*/
function computeAdditiveShares(aShares, bShares) {
const shares = [];
for (let i = 0; i < Math.min(aShares.length, bShares.length); i++) {
const a_i = aShares[i].y instanceof BN ? aShares[i].y : new BN(aShares[i].y);
const b_i = bShares[i].y instanceof BN ? bShares[i].y : new BN(bShares[i].y);
const x = aShares[i].x instanceof BN ? aShares[i].x : new BN(aShares[i].x);
shares.push({
x: x,
y: a_i.add(b_i).umod(CURVE_ORDER)
});
}
return shares;
}
/**
* Section 2.3: Product of Shared Secrets (PROSS)
*
* Given shares of two secrets a and b, computes a * b.
* Each participant calculates μ_i = a_i * b_i, then interpolates.
*
* NOTE: The product polynomial has order 2t, so 2t+1 shares are needed!
*
* @param {Array<{x: BN, y: BN}>} aShares - Shares of secret a (order t polynomial)
* @param {Array<{x: BN, y: BN}>} bShares - Shares of secret b (order t polynomial)
* @param {number} productThreshold - Required threshold for product (2t+1)
* @returns {BN} The product a * b
*/
function PROSS(aShares, bShares, productThreshold) {
if (aShares.length < productThreshold || bShares.length < productThreshold) {
throw new MPCError(
`PROSS needs at least ${productThreshold} shares (2t+1), got ${Math.min(aShares.length, bShares.length)}`,
'INSUFFICIENT_SHARES'
);
}
// Step 3: Each participant calculates multiplicative share μ_i = a_i * b_i
const multiplicativeShares = [];
for (let i = 0; i < productThreshold; i++) {
const a_i = aShares[i].y instanceof BN ? aShares[i].y : new BN(aShares[i].y);
const b_i = bShares[i].y instanceof BN ? bShares[i].y : new BN(bShares[i].y);
const x = aShares[i].x instanceof BN ? aShares[i].x : new BN(aShares[i].x);
multiplicativeShares.push({
x: x,
y: a_i.mul(b_i).umod(CURVE_ORDER)
});
}
// Step 5: Interpolate over 2t+1 shares to get μ = a * b
return Polynomial.interpolate(multiplicativeShares, new BN(0));
}
/**
* Calculate multiplicative shares without interpolation
*
* @param {Array<{x: BN, y: BN}>} aShares - Shares of secret a
* @param {Array<{x: BN, y: BN}>} bShares - Shares of secret b
* @returns {Array<{x: BN, y: BN}>} Multiplicative shares μ_i = a_i * b_i
*/
function computeMultiplicativeShares(aShares, bShares) {
const shares = [];
for (let i = 0; i < Math.min(aShares.length, bShares.length); i++) {
const a_i = aShares[i].y instanceof BN ? aShares[i].y : new BN(aShares[i].y);
const b_i = bShares[i].y instanceof BN ? bShares[i].y : new BN(bShares[i].y);
const x = aShares[i].x instanceof BN ? aShares[i].x : new BN(aShares[i].x);
shares.push({
x: x,
y: a_i.mul(b_i).umod(CURVE_ORDER)
});
}
return shares;
}
/**
* Section 2.4: Inverse of a Shared Secret (INVSS)
*
* Computes shares of a^-1 using a blinding value b.
*
* Process:
* 1. Calculate μ = a * b using PROSS
* 2. Calculate μ^-1 = (a * b)^-1 (this is known to all)
* 3. Each participant calculates a_i^-1 = μ^-1 * b_i
*
* The inverse shares can then be used in further calculations.
* Interpolating over them would give a^-1, but we typically use them
* in products like k^-1 * (e + a*r).
*
* @param {Array<{x: BN, y: BN}>} aShares - Shares of secret a
* @param {Array<{x: BN, y: BN}>} bShares - Shares of blinding value b
* @param {number} t - Threshold degree (polynomials are order t)
* @returns {{mu: BN, muInverse: BN, inverseShares: Array<{x: BN, y: BN}>}} INVSS result
*/
function INVSS(aShares, bShares, t) {
const productThreshold = 2 * t + 1;
if (aShares.length < productThreshold || bShares.length < productThreshold) {
throw new MPCError(
`INVSS needs at least ${productThreshold} shares (2t+1), got ${Math.min(aShares.length, bShares.length)}`,
'INSUFFICIENT_SHARES'
);
}
// Step 1: Calculate product μ = a * b using PROSS
const mu = PROSS(aShares, bShares, productThreshold);
// Step 2: Calculate modular inverse μ^-1 = (ab)^-1
const muInverse = mu.invm(CURVE_ORDER);
// Step 3: Each participant calculates inverse share a_i^-1 = μ^-1 * b_i
const inverseShares = [];
for (let i = 0; i < aShares.length; i++) {
const b_i = bShares[i].y instanceof BN ? bShares[i].y : new BN(bShares[i].y);
const x = aShares[i].x instanceof BN ? aShares[i].x : new BN(aShares[i].x);
// a_i^-1 = μ^-1 * b_i
inverseShares.push({
x: x,
y: muInverse.mul(b_i).umod(CURVE_ORDER)
});
}
return {
mu,
muInverse,
inverseShares
};
}
/**
* Generate ephemeral key inverse shares for threshold signing
*
* This implements Section 4.2 of the whitepaper:
* 1. Generate shared ephemeral key k using JVRSS
* 2. Generate blinding value b using JVRSS
* 3. Calculate k^-1 shares using INVSS
* 4. Calculate r = x mod n from k·G
*
* @param {number} n - Number of participants
* @param {number} t - Threshold degree
* @returns {{r: BN, inverseKShares: Array, kJVRSS: JVRSS, bJVRSS: JVRSS}} Ephemeral key data
*/
function generateEphemeralKeyShares(n, t) {
// Step 1: Generate ephemeral key k using JVRSS
const kJVRSS = new JVRSS(n, t);
kJVRSS.runProtocol();
const kShares = kJVRSS.getSharesForInterpolation();
// Generate blinding value b using JVRSS
const bJVRSS = new JVRSS(n, t);
bJVRSS.runProtocol();
const bShares = bJVRSS.getSharesForInterpolation();
// Step 2: Calculate k^-1 shares using INVSS
const { inverseShares: inverseKShares } = INVSS(kShares, bShares, t);
// Step 3: Calculate r from k·G using obfuscated coefficients
// (x, y) = Σ (k_i0 · G)
let kG = null;
for (let i = 1; i <= n; i++) {
const coeffs = kJVRSS.obfuscatedCoefficients.get(i);
const zerothCoeff = secp256k1.ProjectivePoint.fromHex(coeffs[0]);
if (kG === null) {
kG = zerothCoeff;
} else {
kG = kG.add(zerothCoeff);
}
}
// r = x mod n
const affine = kG.toAffine();
const r = new BN(affine.x.toString(16), 'hex').umod(CURVE_ORDER);
return {
r,
inverseKShares,
kJVRSS,
bJVRSS
};
}
export {
ADDSS,
PROSS,
INVSS,
computeAdditiveShares,
computeMultiplicativeShares,
generateEphemeralKeyShares,
MPCError
};