new ThresholdSignature()
Threshold Signature Scheme implementation for distributed cryptography
This class implements a complete threshold signature scheme that allows any subset of participants (meeting the threshold) to collaboratively generate valid ECDSA signatures without ever reconstructing the private key. The scheme is based on Shamir's Secret Sharing and provides the following security guarantees:
Security Properties:
- Threshold Security: Requires exactly t participants to generate signatures
- Information-Theoretic Privacy: < t participants learn nothing about the private key
- Robustness: Works even if some participants are unavailable (up to n-t)
- Unforgeability: Signatures are cryptographically secure and unforgeable
Key Features:
- Distributed key generation without trusted dealer
- Threshold ECDSA signature generation
- Support for linear and multiplicative operations on shared secrets
- Compatible with standard ECDSA verification
- Efficient polynomial-based secret sharing
Use Cases:
- Multi-signature wallets and escrow services
- Corporate treasury management with distributed control
- Cryptocurrency exchanges with operator separation
- Secure key management for high-value assets
- Compliance requirements for multi-party authorization
Examples
// Create a 2-of-3 threshold signature scheme
const tss = new ThresholdSignature(3, 2);
// Generate a threshold signature
const signature = tss.sign("Transfer $1000 to Alice");
// Verify the signature
const isValid = ThresholdSignature.verify_threshold_signature(
tss.public_key,
signature.msgHash,
signature.sig
);
// Corporate treasury with 3-of-5 executive approval
const corporateTSS = new ThresholdSignature(5, 3);
const executiveShares = corporateTSS.shares;
// Distribute shares to 5 executives
// Any 3 can authorize transactions
const authSignature = corporateTSS.sign("Quarterly dividend payment");
// Escrow service with dispute resolution
const escrowTSS = new ThresholdSignature(3, 2);
const [buyerShare, sellerShare, arbiterShare] = escrowTSS.shares;
// Normal case: buyer + seller release funds
// Dispute case: buyer/seller + arbiter resolve
Members
(readonly) group_size :number
Total number of participants in the threshold scheme
Type:
- number
(readonly) polynomial_order :number
Polynomial degree (threshold - 1) for secret sharing
Type:
- number
(readonly) threshold :number
Minimum number of participants needed for cryptographic operations
Type:
- number
Methods
addss(a_sharesopt, b_sharesopt) → {BN}
Additive Secret Sharing (ADDSS) - combines two sets of shares additively
ADDSS enables secure addition of two shared secrets without revealing the individual secrets. Each participant adds their corresponding shares, and the result can be reconstructed to obtain the sum of the original secrets.
Mathematical Foundation:
- If secret A is shared as (a₁, a₂, ..., aₙ)
- And secret B is shared as (b₁, b₂, ..., bₙ)
- Then A + B is shared as (a₁+b₁, a₂+b₂, ..., aₙ+bₙ)
Applications:
- Combining multiple randomness sources
- Adding constants to shared secrets
- Building complex cryptographic protocols
- Secure multi-party computation primitives
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
a_shares |
Array.<BN> |
<optional> |
[] | First set of secret shares |
b_shares |
Array.<BN> |
<optional> |
[] | Second set of secret shares |
Throws:
-
If share arrays have different lengths
- Type
- Error
Returns:
The sum of the two original secrets
- Type
- BN
Examples
const tss = new ThresholdSignature(3, 2);
// Generate two sets of shares
const [shares1, _] = tss.jvrss();
const [shares2, __] = tss.jvrss();
// Add the shared secrets
const sum = tss.addss(shares1, shares2);
// Verify: sum should equal individual secret sum
const secret1 = tss.privite_key(shares1);
const secret2 = tss.privite_key(shares2);
const expectedSum = secret1.add(secret2).umod(N);
console.log(sum.eq(expectedSum)); // true
// Adding a constant to a shared secret
const constant = new BN(42);
const constantShares = Array(tss.group_size).fill(new BN(0));
constantShares[0] = constant; // Only first share gets the constant
const result = tss.addss(tss.shares, constantShares);
// result = original_secret + 42
invss(a_sharesopt) → {Array.<BN>}
Inverse Secret Sharing (INVSS) - computes modular inverse of shared secret
INVSS computes the modular inverse of a shared secret without revealing the secret. This is crucial for threshold ECDSA signatures where we need to compute k⁻¹ (inverse of the nonce) as part of the signature generation process.
Algorithm:
- Generate a fresh random secret b using JVRSS
- Compute c = a × b using PROSS (where a is the input secret)
- Reconstruct c (this reveals c but not a or b individually)
- Compute c⁻¹ using standard modular inverse
- Multiply b shares by c⁻¹ to get shares of a⁻¹
Security:
- The intermediate value c is revealed but provides no information about a
- The randomness b masks the original secret a
- Final result is properly shared according to the threshold scheme
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
a_shares |
Array.<BN> |
<optional> |
[] | Shares of the secret to invert |
Returns:
Shares of the modular inverse of the original secret
- Type
- Array.<BN>
Examples
const tss = new ThresholdSignature(5, 3);
// Compute inverse of shared secret
const inverseShares = tss.invss(tss.shares);
// Verify: secret × inverse = 1 (mod N)
const product = tss.pross(tss.shares, inverseShares);
console.log(product.eq(new BN(1))); // true
// Use in threshold signature (simplified)
const message = Buffer.from("Hello World!");
const msgHash = new BN(createHash('sha256').update(message).digest());
// Generate nonce shares
const [kShares, kPubKey] = tss.jvrss();
// Compute inverse of nonce
const kInvShares = tss.invss(kShares);
// This would be used in signature computation
// s = k⁻¹(hash + r × private_key)
jvrss() → {Array|Array.<BN>|Object}
Joint Verifiable Random Secret Sharing (JVRSS) protocol implementation
JVRSS is the core protocol for distributed key generation without a trusted dealer. It combines multiple random polynomials from all participants to create a shared secret that no single party knows or can control.
Protocol Steps:
- Polynomial Generation: Each participant conceptually generates a random polynomial
- Share Distribution: Each polynomial contributes to every participant's final share
- Linear Combination: Shares are combined additively to create the final distribution
- Public Key Derivation: The aggregate public key is computed from polynomial constants
Security Properties:
- No single participant controls the final secret
- The secret is uniformly random over the field
- Shares are properly distributed according to Shamir's scheme
- Public key is verifiable and corresponds to the shared secret
Returns:
-
Tuple containing shares array and aggregate public key
- Type
- Array
-
returns.0 - Array of secret shares for each participant
- Type
- Array.<BN>
-
returns.1 - Aggregate elliptic curve public key point
- Type
- Object
Examples
const tss = new ThresholdSignature(5, 3);
const [shares, publicKey] = tss.jvrss();
console.log(shares.length); // 5 shares
console.log(publicKey.constructor.name); // ProjectivePoint
// Shares are properly distributed
const points = tss.shares_to_points(shares);
const secret = Polynomial.interpolate_evaluate(points, 0);
const derivedPubKey = secp256k1.ProjectivePoint.fromPrivateKey(secret.toBuffer());
// Public keys should match
console.log(publicKey.equals(derivedPubKey)); // true
// Manual JVRSS execution for understanding
const groupSize = 3, threshold = 2;
const polynomials = Array(groupSize).fill(null)
.map(() => Polynomial.fromRandom(threshold - 1));
// Each participant's share is sum of evaluations
let manualShares = Array(groupSize).fill(new BN(0));
for (let i = 0; i < groupSize; i++) {
for (let j = 0; j < groupSize; j++) {
manualShares[j] = manualShares[j].add(polynomials[i].evaluate(j + 1));
}
}
// This produces the same result as jvrss()
privite_key(a_sharesopt) → {BN}
Reconstructs the private key from secret shares using polynomial interpolation
This method recovers the original private key from the distributed shares. It should be used with caution as it reconstructs the full private key, defeating the purpose of the threshold scheme. Typically used only for specific operations like computing WIF format or for emergency recovery.
Security Warning:
- Reconstructing the private key centralizes control
- Should only be done when absolutely necessary
- Consider using threshold operations instead when possible
- Ensure secure deletion of reconstructed key after use
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
a_shares |
Array.<BN> |
<optional> |
Secret shares to reconstruct from (defaults to this.shares) |
Returns:
The reconstructed private key as a BigNumber
- Type
- BN
Examples
const tss = new ThresholdSignature(3, 2);
// Reconstruct private key (use with caution!)
const privateKey = tss.privite_key();
// Verify it corresponds to the public key
const derivedPubKey = secp256k1.ProjectivePoint.fromPrivateKey(privateKey.toBuffer());
console.log(derivedPubKey.equals(tss.public_key)); // true
// Partial reconstruction with threshold shares only
const thresholdShares = tss.shares.slice(0, tss.threshold);
const partialKey = tss.privite_key(thresholdShares);
// Should equal full reconstruction
const fullKey = tss.privite_key();
console.log(partialKey.eq(fullKey)); // true
// Emergency recovery scenario
function emergencyRecovery(shareHolders) {
if (shareHolders.length < tss.threshold) {
throw new Error("Insufficient shares for recovery");
}
const recoveredKey = tss.privite_key(shareHolders.slice(0, tss.threshold));
// Use recovered key for emergency operations
// ... perform emergency actions ...
// Securely delete the key
recoveredKey.fill(0);
}
pross(a_sharesopt, b_sharesopt) → {BN}
Polynomial Reconstruction Secret Sharing (PROSS) - computes product of shared secrets
PROSS enables secure multiplication of two shared secrets. This is more complex than addition because the product of two degree-t polynomials yields a degree-2t polynomial, requiring more shares for reconstruction.
Mathematical Foundation:
- Product of degree-t polynomials has degree 2t
- Requires 2t+1 shares for reconstruction (vs t+1 for addition)
- Uses polynomial interpolation on the product values
- Result is the product of the original secrets
Security Note:
- Requires more participants for security than addition
- Product shares reveal more information than additive shares
- Should be used carefully in cryptographic protocols
Applications:
- Computing multiplicative inverses (used in INVSS)
- Secure polynomial evaluation
- Advanced threshold cryptographic protocols
- Zero-knowledge proof systems
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
a_shares |
Array.<BN> |
<optional> |
[] | First set of secret shares |
b_shares |
Array.<BN> |
<optional> |
[] | Second set of secret shares |
Returns:
The product of the two original secrets
- Type
- BN
Examples
const tss = new ThresholdSignature(5, 2); // Need larger group for PROSS
// Generate two secrets
const [shares1, _] = tss.jvrss();
const [shares2, __] = tss.jvrss();
// Compute product
const product = tss.pross(shares1, shares2);
// Verify result
const secret1 = tss.privite_key(shares1);
const secret2 = tss.privite_key(shares2);
const expectedProduct = secret1.mul(secret2).umod(N);
console.log(product.eq(expectedProduct)); // true
// Squaring a shared secret
const squared = tss.pross(tss.shares, tss.shares);
const originalSecret = tss.privite_key();
const expectedSquare = originalSecret.mul(originalSecret).umod(N);
console.log(squared.eq(expectedSquare)); // true
shares_to_points(sharesopt) → {SharePoints}
Converts share values to coordinate points for polynomial interpolation
Transforms an array of share values into the coordinate format required for Lagrange interpolation. Each share at index i becomes a point (i+1, share) since polynomial evaluation uses 1-based indexing (x=0 is reserved for secrets).
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
shares |
Array.<BN> |
<optional> |
[] | Array of BigNumber share values to convert |
Returns:
Array of [x, y] coordinate pairs for interpolation
- Type
- SharePoints
Examples
const tss = new ThresholdSignature(3, 2);
const points = tss.shares_to_points(tss.shares);
console.log(points);
// [[1, share1_value], [2, share2_value], [3, share3_value]]
// Use for secret reconstruction
const secret = Polynomial.interpolate_evaluate(points, 0);
// Partial reconstruction with threshold shares
const partialShares = tss.shares.slice(0, tss.threshold);
const thresholdPoints = tss.shares_to_points(partialShares);
const reconstructed = Polynomial.interpolate_evaluate(thresholdPoints, 0);
sign(message) → {ThresholdSignatureResult}
Generates a threshold signature for a given message
This is the core method that produces threshold ECDSA signatures. The signature is generated collaboratively using the threshold scheme without reconstructing the private key. The process follows the threshold ECDSA protocol:
Threshold ECDSA Algorithm:
- Nonce Generation: Create shared random nonce k using JVRSS
- R Value Computation: Compute R = k×G and extract r = R.x mod n
- Inverse Computation: Compute k⁻¹ using INVSS without revealing k
- Signature Shares: Each party computes their share of s = k⁻¹(hash + r×private)
- Reconstruction: Combine shares to get final signature (r, s)
- Validation: Ensure signature is valid and non-zero
Security Properties:
- Private key never reconstructed during signing
- Nonce is generated distributively and remains secret
- Resulting signature is indistinguishable from single-party ECDSA
- Compatible with standard ECDSA verification
Parameters:
| Name | Type | Description |
|---|---|---|
message |
string | Message to sign (will be SHA256 hashed) |
Returns:
Complete signature with metadata
Examples
const tss = new ThresholdSignature(3, 2);
// Generate threshold signature
const signature = tss.sign("Transfer $1000 to Alice");
console.log(signature.sig.r); // BigInt r value
console.log(signature.sig.s); // BigInt s value
console.log(signature.serialized_sig); // Base64 compact format
console.log(signature.recovery_id); // 0-3 for public key recovery
// Verify signature
const isValid = ThresholdSignature.verify_threshold_signature(
tss.public_key,
signature.msgHash,
signature.sig
);
console.log(isValid); // true
// Corporate authorization workflow
const corporateTSS = new ThresholdSignature(5, 3);
const authMessage = JSON.stringify({
action: "wire_transfer",
amount: 1000000,
recipient: "operations_account",
timestamp: Date.now()
});
const authorization = corporateTSS.sign(authMessage);
console.log("Authorization signature:", authorization.serialized_sig);
// Escrow release with buyer + seller
const escrowTSS = new ThresholdSignature(3, 2);
const releaseMessage = "Release escrow funds to seller";
const escrowSignature = escrowTSS.sign(releaseMessage);
// This signature can be verified by anyone
const verified = ThresholdSignature.verify_threshold_signature(
escrowTSS.public_key,
escrowSignature.msgHash,
escrowSignature.sig
);
(static) verify_threshold_signature(public_key, msgHash, sig) → {boolean}
Verifies a threshold signature against a public key and message hash
This static method verifies threshold signatures using standard ECDSA verification. Threshold signatures are indistinguishable from regular ECDSA signatures, so standard verification algorithms work without modification.
Verification Algorithm:
- Input Validation: Ensure signature components r and s are valid
- Hash Processing: Use the provided message hash (already computed)
- Inverse Computation: Compute w = s⁻¹ mod n
- Point Calculation: Compute u₁ = w×hash and u₂ = w×r
- Point Addition: Compute point = u₁×G + u₂×PublicKey
- Verification: Check if point.x ≡ r (mod n)
Compatibility:
- Works with any ECDSA signature, threshold or single-party
- Uses standard secp256k1 curve parameters
- Compatible with Bitcoin and Ethereum signature formats
- Can be used by third parties without threshold scheme knowledge
Parameters:
| Name | Type | Description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
public_key |
Object | Elliptic curve public key point |
|||||||||
msgHash |
Buffer | SHA256 hash of the original message |
|||||||||
sig |
Object | Signature object with r and s components Properties
|
Returns:
True if signature is valid, false otherwise
- Type
- boolean
Examples
// Verify a threshold signature
const tss = new ThresholdSignature(3, 2);
const signature = tss.sign("Hello World!");
const isValid = ThresholdSignature.verify_threshold_signature(
tss.public_key,
signature.msgHash,
signature.sig
);
console.log(isValid); // true
// Third-party verification (doesn't need threshold scheme)
function verifyTransaction(publicKey, messageHash, signature) {
return ThresholdSignature.verify_threshold_signature(
publicKey,
messageHash,
signature
);
}
// Works with any ECDSA signature
const valid = verifyTransaction(
somePublicKey,
someMessageHash,
someSignature
);
// Batch verification for multiple signatures
function verifyBatch(signatures) {
return signatures.every(({ publicKey, msgHash, sig }) =>
ThresholdSignature.verify_threshold_signature(publicKey, msgHash, sig)
);
}
// Integration with Bitcoin transaction verification
function verifyBitcoinTransaction(transaction, publicKey) {
const messageHash = computeTransactionHash(transaction);
const signature = extractSignature(transaction);
return ThresholdSignature.verify_threshold_signature(
publicKey,
messageHash,
signature
);
}