
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
multi-party-eddsa
Advanced tools
A high-level TypeScript client library for implementing threshold EdDSA (Ed25519) signatures using Multi-Party Computation (MPC). This library enables distributed key generation and threshold signing where multiple parties must collaborate to create a signature, without any single party ever having access to the complete private key.
This package lives inside the tss-eddsa Turborepo and is the client foundation for Zengox’s MPC-backed EdDSA signing flows. For more end-to-end flows, explore the examples workspace.
npm install multi-party-eddsa
Or using Bun:
bun add multi-party-eddsa
multi-party-eddsa-node) - automatically includedimport { MPCService, CoordinatorService } from 'multi-party-eddsa';
// Step 1: Initialize services for each party
const party0 = new MPCService('party-0');
const party1 = new MPCService('party-1');
const party2 = new MPCService('party-2');
// Step 2: Create coordinator to orchestrate the protocol
const coordinator = new CoordinatorService();
// Step 3: Start key generation (threshold=2, totalParties=3)
coordinator.startKeyGeneration(2, 3);
// Step 4: Register parties
const init0 = party0.register();
const init1 = party1.register();
const init2 = party2.register();
coordinator.registerParty('party-0', init0.publicKey);
coordinator.registerParty('party-1', init1.publicKey);
coordinator.registerParty('party-2', init2.publicKey);
// Step 5: Generate commitments
const commit0 = party0.generateCommitment();
const commit1 = party1.generateCommitment();
const commit2 = party2.generateCommitment();
const commitData = coordinator.collectCommitments([
{ partyId: 'party-0', ...commit0 },
{ partyId: 'party-1', ...commit1 },
{ partyId: 'party-2', ...commit2 },
]);
// Step 6: Distribute shares and construct keypairs
// ... (see full example below)
// Step 7: Sign a message
const message = Buffer.from('Hello, Threshold Signatures!');
const signingSession = coordinator.startSigning(message, ['party-0', 'party-1']);
// ... (complete signing flow)
// Step 8: Get final signature
const result = coordinator.collectLocalSignatures([...]);
console.log('Signature:', result.signature);
console.log('Valid:', result.isValid);
t out of n parties to signThe library follows a distributed architecture:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Party 0 │ │ Party 1 │ │ Party 2 │
│ MPCService │ │ MPCService │ │ MPCService │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌────────▼────────┐
│ Coordinator │
│ Service │
└─────────────────┘
Service that runs on each party's server to handle local MPC operations.
new MPCService(partyId: string)
Parameters:
partyId (string): Unique identifier for this party (e.g., "party-0")Example:
const service = new MPCService("party-0");
register()Registers the party and generates initial public key for key generation.
Returns:
{
publicKey: SerializableBigInt;
}
Example:
const result = service.register();
console.log("Public Key:", result.publicKey);
generateCommitment()Generates a commitment for the key generation phase. Commitments ensure parties cannot cheat during share distribution.
Returns:
{
commitment: SerializableBigInt,
blindFactor: SerializableBigInt
}
Example:
const result = service.generateCommitment();
// Send commitment and blindFactor to coordinator
distributeShares(threshold, shareCount, blindFactors, publicKeys, commitments, partyIndex)Distributes secret shares to all parties using Verifiable Secret Sharing (VSS).
Parameters:
threshold (number): Minimum parties needed to signshareCount (number): Total number of partiesblindFactors (SerializableBigInt[]): Blind factors from all partiespublicKeys (PublicKey[]): Public keys from all partiescommitments (Commitment[]): Commitments from all partiespartyIndex (number): This party's index (0-based)Returns:
{
vss: VSSScheme,
secretShares: SecretShare[]
}
Example:
const result = service.distributeShares(
2, // threshold
3, // total parties
blindFactors,
publicKeys,
commitments,
0 // this party's index
);
constructKeypair(threshold, shareCount, publicKeys, allSecretShares, allVssSchemes, partyIndex)Constructs the shared keypair from collected shares.
Parameters:
threshold (number): Minimum parties needed to signshareCount (number): Total number of partiespublicKeys (PublicKey[]): Public keys from all partiesallSecretShares (SecretShare[][]): Secret shares from all partiesallVssSchemes (VSSScheme[]): VSS schemes from all partiespartyIndex (number): This party's indexReturns:
{
sharedKey: SharedKey;
}
Example:
const result = service.constructKeypair(
2, // threshold
3, // total parties
publicKeys,
allSecretShares,
allVssSchemes,
0 // this party's index
);
startEphemeralKeyGeneration(message, partyIndex)Starts ephemeral key generation for signing. Ephemeral keys are one-time keys used only for a single signature.
Parameters:
message (Buffer): Message to signpartyIndex (number): This party's indexReturns:
{
ephR: SerializableBigInt,
ephKeyId: string,
commitment: SerializableBigInt,
blindFactor: SerializableBigInt
}
Example:
const message = Buffer.from("Hello, World!");
const result = service.startEphemeralKeyGeneration(message, 0);
distributeEphemeralShares(ephKeyId, threshold, shareCount, ephBlindFactors, ephRPoints, ephCommitments, signingParties)Distributes ephemeral shares for signing.
Parameters:
ephKeyId (string): Ephemeral key ID from startEphemeralKeyGenerationthreshold (number): Minimum parties needed to signshareCount (number): Total number of partiesephBlindFactors (SerializableBigInt[]): Ephemeral blind factorsephRPoints (SerializableBigInt[]): Ephemeral R pointsephCommitments (Commitment[]): Ephemeral commitmentssigningParties (number[]): Indices of parties participating in signingReturns:
{
vss: VSSScheme,
secretShares: SecretShare[]
}
constructEphemeralKeypair(ephKeyId, threshold, shareCount, ephRPoints, allEphSecretShares, allEphVssSchemes, partyIndex, signingParties)Constructs ephemeral keypair for signing.
Parameters:
ephKeyId (string): Ephemeral key IDthreshold (number): Minimum parties needed to signshareCount (number): Total number of partiesephRPoints (SerializableBigInt[]): Ephemeral R pointsallEphSecretShares (SecretShare[][]): All ephemeral secret sharesallEphVssSchemes (VSSScheme[]): All ephemeral VSS schemespartyIndex (number): This party's indexsigningParties (number[]): Indices of signing partiesReturns:
{
ephSharedKey: EphemeralSharedKey;
}
computeLocalSignature(message, ephSharedKey)Computes local signature component. These are aggregated by the coordinator to form the final signature.
Parameters:
message (Buffer): Message to signephSharedKey (EphemeralSharedKey): Ephemeral shared key from constructEphemeralKeypairReturns:
{
localSig: LocalSignature;
}
Example:
const result = service.computeLocalSignature(message, ephSharedKey);
// Send localSig to coordinator for aggregation
Service that orchestrates the MPC protocol across all parties.
new CoordinatorService();
Example:
const coordinator = new CoordinatorService();
startKeyGeneration(threshold, totalParties)Starts a key generation session.
Parameters:
threshold (number): Minimum parties needed to sign (must be >= 2)totalParties (number): Total number of parties (must be >= threshold)Example:
coordinator.startKeyGeneration(2, 3); // 2-of-3 threshold
registerParty(partyId, publicKey)Registers a party in the key generation session.
Parameters:
partyId (string): Unique identifier for the partypublicKey (PublicKey): Public key from party's register() callReturns:
{
partyId: string,
partyIndex: number
}
Example:
const result = coordinator.registerParty("party-0", publicKey);
console.log("Party index:", result.partyIndex);
collectCommitments(partyCommitments)Collects commitments from all parties.
Parameters:
partyCommitments (Array): Array of commitment objects from each partyReturns:
{
threshold: number,
shareCount: number,
blindFactors: SerializableBigInt[],
publicKeys: PublicKey[],
commitments: Commitment[],
parties: Party[]
}
Example:
const result = coordinator.collectCommitments([
{
partyId: "party-0",
commitment: commit0.commitment,
blindFactor: commit0.blindFactor,
},
{
partyId: "party-1",
commitment: commit1.commitment,
blindFactor: commit1.blindFactor,
},
{
partyId: "party-2",
commitment: commit2.commitment,
blindFactor: commit2.blindFactor,
},
]);
collectShares(partyShares)Collects secret shares from all parties.
Parameters:
partyShares (Array): Array of share objects from each partyReturns:
{
[partyId: string]: {
threshold: number,
shareCount: number,
publicKeys: PublicKey[],
allSecretShares: SecretShare[][],
allVssSchemes: VSSScheme[],
partyIndex: number
}
}
Example:
const result = coordinator.collectShares([
{ partyId: "party-0", vss: vss0, secretShares: shares0 },
{ partyId: "party-1", vss: vss1, secretShares: shares1 },
{ partyId: "party-2", vss: vss2, secretShares: shares2 },
]);
collectSharedKeys(partySharedKeys)Collects shared keys and computes aggregate public key.
Parameters:
partySharedKeys (Array): Array of shared key objects from each partyReturns:
{
aggregatePublicKey: PublicKey;
}
Example:
const result = coordinator.collectSharedKeys([
{ partyId: "party-0", sharedKey: key0.sharedKey },
{ partyId: "party-1", sharedKey: key1.sharedKey },
{ partyId: "party-2", sharedKey: key2.sharedKey },
]);
console.log("Aggregate Public Key:", result.aggregatePublicKey);
startSigning(message, signingParties)Starts a signing session.
Parameters:
message (Buffer): Message to signsigningParties (string[]): Array of party IDs that will participate in signingReturns:
{
signingParties: number[]
}
Example:
const message = Buffer.from("Hello, World!");
const session = coordinator.startSigning(message, ["party-0", "party-1"]);
collectEphemeralKeysAndCommitments(partyEphData)Collects ephemeral keys and commitments from signing parties.
Parameters:
partyEphData (Array): Array of ephemeral key data from each signing partyReturns:
{
ephBlindFactors: SerializableBigInt[],
ephRPoints: SerializableBigInt[],
ephCommitments: Commitment[]
}
collectEphemeralShares(partyEphShares)Collects ephemeral shares from signing parties.
Parameters:
partyEphShares (Array): Array of ephemeral share data from each signing partyReturns:
{
[partyId: string]: {
allEphSecretShares: SecretShare[][],
allEphVssSchemes: VSSScheme[],
partyIndex: number
}
}
collectLocalSignatures(partyLocalSigs)Collects local signatures and aggregates them into the final signature.
Parameters:
partyLocalSigs (Array): Array of local signature objects from each signing partyReturns:
{
signature: Signature,
isValid: boolean
}
Example:
const result = coordinator.collectLocalSignatures([
{ partyId: "party-0", localSig: localSig0 },
{ partyId: "party-1", localSig: localSig1 },
]);
if (result.isValid) {
console.log("Signature is valid!");
console.log("R:", result.signature.r);
console.log("s:", result.signature.s);
}
# Build TypeScript to JavaScript
bun run build
# Watch mode for development
bun run watch
# Clean build artifacts
bun run clean
# Run all tests
bun run test
# Run tests in watch mode
bun run test:watch
# Run tests with coverage
bun run test:coverage
# Lint code
bun run lint
# Type check without emitting files
bun run check-types
The library is written in TypeScript and includes comprehensive type definitions:
import {
MPCService,
CoordinatorService,
MPCClient,
PublicKey,
Signature,
SharedKey,
EphemeralSharedKey,
LocalSignature,
SerializableBigInt,
// ... and more
} from "multi-party-eddsa";
The library includes custom error classes for different failure scenarios:
import {
ValidationError,
PartyError,
StateError,
ProtocolError,
} from "multi-party-eddsa";
try {
// ... MPC operations
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation errors
} else if (error instanceof PartyError) {
// Handle party-related errors
} else if (error instanceof StateError) {
// Handle state errors
} else if (error instanceof ProtocolError) {
// Handle protocol errors
}
}
See the Solana Transaction POC example for a complete example of signing Solana transactions with MPC signatures.
Solana uses Ed25519 signatures in a 64-byte format: R (32 bytes) + s (32 bytes)
import { MPCService, CoordinatorService } from "multi-party-eddsa";
import { Transaction, PublicKey } from "@solana/web3.js";
// After getting MPC signature from coordinator.collectLocalSignatures()
const mpcSignature = result.signature;
// Extract R and s components
const rBytes = Buffer.from(mpcSignature.r.bytes);
const sBytes = Buffer.from(mpcSignature.s.bytes);
// Create 64-byte Solana signature
const solanaSignature = Buffer.concat([rBytes, sBytes]);
// Add to transaction
const senderPubkey = new PublicKey(aggregatePublicKeyBytes);
transaction.addSignature(senderPubkey, solanaSignature);
Threshold Requirements:
threshold parties must participate in signingPerformance:
State Management:
Important: EdDSA signing for Solana has specific compatibility constraints:
Transaction Type Compatibility:
Transaction class)VersionedTransaction class)Signature Scheme:
Transaction Instructions:
eddsa-client/
├── src/
│ ├── client/ # Low-level MPC client
│ │ └── mpc_client.ts
│ ├── services/ # MPCService and CoordinatorService
│ │ ├── mpc_service.ts
│ │ └── coordinator_service.ts
│ ├── utils/ # Utility functions
│ │ ├── serialization.ts
│ │ └── validation.ts
│ ├── types/ # TypeScript type definitions
│ │ └── index.ts
│ ├── errors/ # Custom error classes
│ │ └── index.ts
│ └── index.ts # Main entry point
├── tests/ # Test files
│ ├── mpc_service.test.ts
│ ├── coordinator_service.test.ts
│ └── mpc_integration.test.ts
├── dist/ # Compiled JavaScript (generated)
├── package.json
├── tsconfig.json
└── README.md
Contributions are welcome! Please ensure:
bun run testbun run check-typesbun run lintMIT
FAQs
Multi-Party EdDSA threshold signature library
The npm package multi-party-eddsa receives a total of 0 weekly downloads. As such, multi-party-eddsa popularity was classified as not popular.
We found that multi-party-eddsa demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.