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
268 lines (232 loc) • 8.31 kB
JavaScript
/**
* @fileoverview Joint Verifiable Random Secret Sharing (JVRSS) implementation
* @description Implements the JVRSS protocol from Section 2.1 of the nChain whitepaper
* for distributed generation of shared secrets with Feldman verification.
* @version 1.0.0
* @author yfbsei
* @license ISC
* @see Threshold-Signatures-whitepaper-nchain.pdf Section 2.1
*/
import { secp256k1 } from '@noble/curves/secp256k1';
import BN from 'bn.js';
import { Participant } from './participant.js';
import { Polynomial, CURVE_ORDER } from './polynomial.js';
/** secp256k1 generator point G */
const G = secp256k1.ProjectivePoint.BASE;
/**
* Error class for JVRSS operations
*/
class JVRSSError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = 'JVRSSError';
this.code = code;
this.details = details;
}
}
/**
* JVRSS class orchestrating the distributed key generation protocol
*/
class JVRSS {
/**
* Create a JVRSS instance
* @param {number} n - Total number of participants (N)
* @param {number} t - Threshold degree (t+1 shares needed to reconstruct)
*/
constructor(n, t) {
if (n < 2) {
throw new JVRSSError('Need at least 2 participants', 'INSUFFICIENT_PARTICIPANTS');
}
if (t < 1) {
throw new JVRSSError('Threshold must be at least 1', 'INVALID_THRESHOLD');
}
if (t >= n) {
throw new JVRSSError('Threshold must be less than number of participants', 'THRESHOLD_TOO_HIGH');
}
this.n = n;
this.t = t;
this.participants = [];
this.obfuscatedCoefficients = new Map();
this.sharedPublicKey = null;
// Create participants
for (let i = 1; i <= n; i++) {
this.participants.push(new Participant(i, t));
}
}
/**
* Get participant by index (1-indexed)
* @param {number} index - Participant index
* @returns {Participant} The participant
*/
getParticipant(index) {
return this.participants[index - 1];
}
/**
* Step 1: Each participant generates their private polynomial
*/
generatePolynomials() {
for (const participant of this.participants) {
participant.generatePolynomial();
}
}
/**
* Step 2: Distribute polynomial points securely between participants
* f_i(j) is sent from participant i to participant j
*/
distributePolynomialPoints() {
for (const sender of this.participants) {
for (const receiver of this.participants) {
if (sender.index === receiver.index) continue;
// Sender evaluates f_i(j) and sends to receiver
const point = sender.getPolynomialPointFor(receiver.index);
receiver.receivePolynomialPoint(point.fromIndex, point.value);
}
}
}
/**
* Step 3: Each participant calculates their key share
* a_i = Σ f_j(i) for all j
*/
calculateShares() {
for (const participant of this.participants) {
participant.calculateKeyShare(this.n);
}
}
/**
* Step 4: Each participant broadcasts obfuscated coefficients
* Broadcasts a_ik · G for verification
*/
broadcastObfuscatedCoefficients() {
for (const participant of this.participants) {
const coeffs = participant.getObfuscatedCoefficients();
this.obfuscatedCoefficients.set(participant.index, coeffs);
}
}
/**
* Step 5: Each participant verifies received polynomial points
* @returns {{valid: boolean, invalidPairs: Array}} Verification result
*/
verifyAllShares() {
const invalidPairs = [];
for (const verifier of this.participants) {
for (const sender of this.participants) {
if (verifier.index === sender.index) continue;
const pointValue = verifier.receivedPoints.get(sender.index);
const obfuscatedCoeffs = this.obfuscatedCoefficients.get(sender.index);
if (!verifier.verifyPolynomialPoint(sender.index, pointValue, obfuscatedCoeffs)) {
invalidPairs.push({
verifier: verifier.index,
sender: sender.index
});
}
}
}
return {
valid: invalidPairs.length === 0,
invalidPairs
};
}
/**
* Calculate the shared public key using Method 2
* a · G = Σ (a_i0 · G)
* @returns {Buffer} Shared public key (compressed)
*/
calculateSharedPublicKey() {
// Sum the zeroth-order obfuscated coefficients
let sharedPoint = null;
for (let i = 1; i <= this.n; i++) {
const coeffs = this.obfuscatedCoefficients.get(i);
const zerothCoeff = secp256k1.ProjectivePoint.fromHex(coeffs[0]);
if (sharedPoint === null) {
sharedPoint = zerothCoeff;
} else {
sharedPoint = sharedPoint.add(zerothCoeff);
}
}
this.sharedPublicKey = Buffer.from(sharedPoint.toRawBytes(true));
return this.sharedPublicKey;
}
/**
* Run the complete JVRSS protocol
* @returns {{shares: Array, publicKey: Buffer, verified: boolean}} Protocol result
*/
runProtocol() {
// Step 1: Generate polynomials
this.generatePolynomials();
// Step 2: Distribute polynomial points
this.distributePolynomialPoints();
// Step 3: Calculate key shares
this.calculateShares();
// Step 4: Broadcast obfuscated coefficients
this.broadcastObfuscatedCoefficients();
// Step 5: Verify all shares
const verification = this.verifyAllShares();
if (!verification.valid) {
throw new JVRSSError(
'Share verification failed',
'VERIFICATION_FAILED',
{ invalidPairs: verification.invalidPairs }
);
}
// Calculate shared public key
const publicKey = this.calculateSharedPublicKey();
// Collect shares
const shares = this.participants.map(p => ({
index: p.index,
keyShare: p.getKeyShare(),
publicKeyShare: p.getKeySharePublicKey()
}));
return {
shares,
publicKey,
verified: true
};
}
/**
* Get shares for interpolation (useful for product/inverse calculations)
* @returns {Array<{x: BN, y: BN}>} Array of shares in interpolation format
*/
getSharesForInterpolation() {
return this.participants.map(p => ({
x: new BN(p.index),
y: p.getKeyShare()
}));
}
/**
* Reconstruct the secret (for testing/verification only)
* WARNING: In production, the secret should never be reconstructed!
* @returns {BN} The reconstructed shared secret
*/
reconstructSecret() {
const shares = this.getSharesForInterpolation();
// Only need t+1 shares
const minShares = shares.slice(0, this.t + 1);
return Polynomial.reconstructSecret(minShares);
}
/**
* Clear all sensitive data
*/
clear() {
for (const participant of this.participants) {
participant.clear();
}
this.obfuscatedCoefficients.clear();
this.sharedPublicKey = null;
}
}
/**
* Run a standalone JVRSS protocol and return results
* Convenience function for generating a shared secret
* @param {number} n - Number of participants
* @param {number} t - Threshold
* @returns {Object} JVRSS result with shares and public key
*/
function runJVRSS(n, t) {
const jvrss = new JVRSS(n, t);
return {
...jvrss.runProtocol(),
jvrss
};
}
export { JVRSS, JVRSSError, runJVRSS };
export default JVRSS;