Class: Protocol
Secure MPC signature protocol for ECDSA and EDDSA systems
Builds upon Crypto and Transport Drivers, thus completely encapsulating data cryptography and message transmission within the MPC algorithms. For now, only the 2-out-of-2 signature scheme is supported internally, however, the interface should still remain the same for more schemes with more parties.
Example
// Assuming an already existing secret bound to secretId
// Generate a random session ID for a new synchronization session
const syncSessionId = uuid(randomBytes);
// Perform an ECDSA-over-secp256k1 synchronization procedure for a key with path ```m/44'/0'/0'/0'/0'```
const publicKey = await syncDistributedEcdsaKey(protocol, secretId, syncSessionId, 'secp256k1', 0, 0);
// At this point, synchronization data is saved to the storage, so one may access it to get a compound public key
const _publicKey = await getEcdsaPublicKey(protocol, secretId, syncSessionId);
// Which is obviously the same key that was returned from the synchronization procedure itself
expect(publicKey).toBe(_publicKey);
// Generate a random session ID for a new signing session
const signSessionId = uuid(randomBytes);
// Message to be signed
const message = '7hJ9yN2OdIQo2kJu0arZgw2EKnMX5FGtQ6jlCWuLXCM='
// Get signingToken
const signingToken = await getSigningToken(message)
// Generate an ECDSA signature
const signature = await signEcdsaMessage(protocol, secretId, syncSessionId, signSessionId, message, signingToken);
// Verify the resulting signature against a compound public key with an external library (elliptic.js here)
expect(new ec('secp256k1').verify(
Buffer.from(message, 'base64'),
{
s: Buffer.from(signature.s, 'base64'),
r: Buffer.from(signature.r, 'base64'),
recoveryParam: signature.recovery,
},
Buffer.from(publicKey, 'base64'),
)).toBeTruthy();
Implements
Constructors
constructor
• new Protocol(crypto
, transport
, timeout?
)
Protocol Constructor
Parameters
Name | Type | Description |
---|---|---|
crypto |
Crypto |
software or hardware crypto module to perform computations with |
transport |
TransportDriver |
message passing implementation, be it socket, HTTP(S) or other means of communication |
timeout? |
number |
(optional) per-message request timeout, after which a procedure is dropped |
Properties
crypto
• crypto: Crypto
software or hardware crypto module to perform computations with
transport
• transport: TransportDriver
message passing implementation, be it socket, HTTP(S) or other means of communication
Methods
getEcdsaPublicKey
▸ getEcdsaPublicKey(secretId
, syncSessionId
): Promise
<string
>
Get a compound public key from an already performed synchronization session
Example
// Assume an already performed synchronization procedure
const publicKey = await getEcdsaPublicKey(protocol, secretId, syncSessionId);
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret used for synchronization |
syncSessionId |
string |
ID (UUID) of a synchronization procedure |
Returns
Promise
<string
>
compound public key, synchronized during that session
Implementation of
getEddsaPublicKey
▸ getEddsaPublicKey(secretId
, syncSessionId
): Promise
<string
>
Get a compound public key from an already performed synchronization session
Example
// Assume an already performed synchronization procedure
const publicKey = await getEddsaPublicKey(protocol, secretId, syncSessionId);
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret used for synchronization |
syncSessionId |
string |
ID (UUID) of a synchronization procedure |
Returns
Promise
<string
>
compound public key, synchronized during that session
Implementation of
off
▸ off(): void
Unsubscribe from server-side events
Server-side events are not limited to an actual server, but may also be used to process requests on clients, the deciding factor here is who initiates the procedure.
Returns
void
Implementation of
on
▸ on(): void
Subscribe to server-side events
Server-side events are not limited to an actual server, but may also be used to process requests on clients, the deciding factor here is who initiates the procedure.
Returns
void
Implementation of
removeDistributedEcdsaKey
▸ removeDistributedEcdsaKey(secretId
, syncSessionId
): Promise
<void
>
Remove an already performed synchronization session from storage
Example
// Assume an already performed synchronization procedure
await removeDistributedEcdsaKey(protocol, secretId, syncSessionId);
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret used for synchronization |
syncSessionId |
string |
ID (UUID) of a synchronization procedure |
Returns
Promise
<void
>
Implementation of
Protocol.removeDistributedEcdsaKey
removeDistributedEddsaKey
▸ removeDistributedEddsaKey(secretId
, syncSessionId
): Promise
<void
>
Remove an already performed synchronization session from storage
Example
// Assume an already performed synchronization procedure
await removeDistributedEddsaKey(protocol, secretId, syncSessionId);
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret used for synchronization |
syncSessionId |
string |
ID (UUID) of a synchronization procedure |
Returns
Promise
<void
>
Implementation of
Protocol.removeDistributedEddsaKey
signEcdsaMessage
▸ signEcdsaMessage(secretId
, syncSessionId
, signSessionId
, message
, signingToken
): Promise
<EcdsaSignature
>
Generate a distributed ECDSA signature for a given message
This method requires an already performed synchronization procedure and does not store anything in permanent storage. Messages of a length different from 32 bytes are padded/truncated according to the ECDSA algorithm.
Example
// Assume an already performed synchronization procedure and an established connection
// Generate a random session ID for a new signing session
const signSessionId = uuid(randomBytes);
// Message to be signed
const message = '7hJ9yN2OdIQo2kJu0arZgw2EKnMX5FGtQ6jlCWuLXCM='
// Get signingToken
const signingToken = await getSigningToken(message)
// Generate an ECDSA signature
const signature = await signEcdsaMessage(protocol, secretId, syncSessionId, signSessionId, message, signingToken);
// Verify the resulting signature against a compound public key with an external library (elliptic.js here)
expect(new ec('secp256k1').verify(
Buffer.from(message, 'base64'),
{
s: Buffer.from(signature.s, 'base64'),
r: Buffer.from(signature.r, 'base64'),
recoveryParam: signature.recovery,
},
Buffer.from(publicKey, 'base64'),
)).toBeTruthy();
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret used for synchronization |
syncSessionId |
string |
ID (UUID) of a corresponding synchronization procedure |
signSessionId |
string |
ID (UUID) of this signing procedure |
message |
string |
message (32 bytes, base64 encoded) to be signed |
Returns
Promise
<EcdsaSignature
>
ECDSA signature in standard format
Implementation of
signEddsaMessage
▸ signEddsaMessage(secretId
, syncSessionId
, signSessionId
, message
, signingToken
): Promise
<EddsaSignature
>
Generate a distributed EDDSA signature for a given message
This method requires an already performed synchronization procedure and does not store anything in permanent storage. Messages of a length different from 32 bytes are padded/truncated according to the EDDSA algorithm.
Example
// Assume an already performed synchronization procedure and an established connection
// Generate a random session ID for a new signing session
const signSessionId = uuid(randomBytes);
// Message to be signed
const message = '7hJ9yN2OdIQo2kJu0arZgw2EKnMX5FGtQ6jlCWuLXCM='
// Get signingToken
const signingToken = await getSigningToken(message)
// Generate an EDDSA signature
const signature = await signEddsaMessage(protocol, secretId, syncSessionId, signSessionId, message, signingToken);
// Verify the resulting signature against a compound public key with an external library (elliptic.js here)
expect(new ed('ed25519').verify(
Buffer.from(message, 'base64').toString('hex'),
Buffer.concat([
Buffer.from(encodeEddsaPoint('ed25519', signature.R), 'base64'),
Buffer.from(encodeEddsaBN('ed25519', signature.s), 'base64'),
]).toString('hex'),
Buffer.from(encodeEddsaPoint('ed25519', publicKey), 'base64').toString('hex'),
)).toBeTruthy();
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret used for synchronization |
syncSessionId |
string |
ID (UUID) of a corresponding synchronization procedure |
signSessionId |
string |
ID (UUID) of this signing procedure |
message |
string |
message (32 bytes, base64 encoded) to be signed |
Returns
Promise
<EddsaSignature
>
EDDSA signature in standard format
Implementation of
syncDistributedEcdsaKey
▸ syncDistributedEcdsaKey(secretId
, syncSessionId
, curve
, derivationCoin
, derivationAccount
): Promise
<string
>
Perform a distributed key synchronization procedure
Each secret is independently used to generate distributed key fragments (BIP-44 with path m/44'/(coin)'/(account)'/0'/0'
),
and then MPC key synchronization is performed over these fragments in order to get a compound public key for that pair.
Despite each session bearing a unique set of data, the resulting key is deterministic in regards to further synchronizations, e.g., each set of secrets, coin, and account always result in the same compound public key (and ephemeral private keys).
As a result of this operation, new synchronization session data is saved to persistent storage, accessible by the provided session ID. From this point, one may freely call getEcdsaPublicKey or signEcdsaMessage as long as synchronization data (and secret) is accessible in the storage.
Example
// Assume an already generated secret and an established connection
// Generate a random session ID for a new synchronization session
const syncSessionId = uuid(randomBytes);
// Perform an ECDSA-over-secp256k1 synchronization procedure for a key with path ```m/44'/0'/0'/0'/0'```
const publicKey = await syncDistributedEcdsaKey(protocol, secretId, syncSessionId, 'secp256k1', 0, 0);
// At this point, synchronization data is saved to the storage, so one may access it to get a compound public key
const _publicKey = await getEcdsaPublicKey(protocol, secretId, syncSessionId);
// Which is obviously the same key that was returned from the synchronization procedure itself
expect(publicKey).toBe(_publicKey);
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret to be used for synchronization |
syncSessionId |
string |
ID (UUID) of this synchronization procedure |
curve |
"secp256k1" |
elliptic curve to use (only secp256k1 is supported at the moment) |
derivationCoin |
number |
coin property of HD key derivation (BIP-44) |
derivationAccount |
number |
account property of HD key derivation (BIP-44) |
Returns
Promise
<string
>
compound public key (which may also be accessed via getEcdsaPublicKey later)
Implementation of
Protocol.syncDistributedEcdsaKey
syncDistributedEddsaKey
▸ syncDistributedEddsaKey(secretId
, syncSessionId
, curve
, derivationCoin
, derivationAccount
): Promise
<string
>
Perform a distributed key synchronization procedure
Each secret is independently used to generate distributed key fragments (BIP-44 with path m/44'/(coin)'/(account)'/0'/0'
),
and then MPC key synchronization is performed over these fragments in order to get a compound public key for that pair.
Despite each session bearing a unique set of data, the resulting key is deterministic in regards to further synchronizations, e.g., each set of secrets, coin, and account always result in the same compound public key (and ephemeral private keys).
As a result of this operation, new synchronization session data is saved to persistent storage, accessible by the provided session ID. From this point, one may freely call getEddsaPublicKey or signEddsaMessage as long as synchronization data (and secret) is accessible in the storage.
Example
// Assume an already generated secret and an established connection
// Generate a random session ID for a new synchronization session
const syncSessionId = uuid(randomBytes);
// Perform an EDDSA-over-secp256k1 synchronization procedure for a key with path ```m/44'/0'/0'/0'/0'```
const publicKey = await syncDistributedEddsaKey(protocol, secretId, syncSessionId, 'ed25519', 0, 0);
// At this point, synchronization data is saved to the storage, so one may access it to get a compound public key
const _publicKey = await getEddsaPublicKey(protocol, secretId, syncSessionId);
// Which is obviously the same key that was returned from the synchronization procedure itself
expect(publicKey).toBe(_publicKey);
Parameters
Name | Type | Description |
---|---|---|
secretId |
string |
ID (UUID) of a secret to be used for synchronization |
syncSessionId |
string |
ID (UUID) of this synchronization procedure |
curve |
"ed25519" |
- |
derivationCoin |
number |
coin property of HD key derivation (BIP-44) |
derivationAccount |
number |
account property of HD key derivation (BIP-44) |
Returns
Promise
<string
>
compound public key (which may also be accessed via getEddsaPublicKey later)