PROTOCOL
ProtocolSwift
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 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 random session ID for a new synchronisation session
let syncSessionId = UUID().uuidString
// Perform a ECDSA-over-secp256k1 synchronisation procedure for a key with path "m/44'/0'/0'/0'/0'"
let publicKey = await syncDistributedEcdsaKey(driver, secretId, syncSessionId, .secp256k1 , 0, 0);
// At this point synchronisation data is saved to the storage so one may access it to get a compound public key
let _publicKey = await getEcdsaPublicKey(driver, secretId, syncSessionId);
// Which is obviously the same key that was returned from the synchronisation procedure itself
XCTAssertEqual(publicKey, _publicKey)
// Generate random session ID for a new signing session
const signSessionId = UUID().uuidString
// Message to be signed
let message = "7hJ9yN2OdIQo2kJu0arZgw2EKnMX5FGtQ6jlCWuLXCM="
// Generate a ECDSA signature
let signature = await signEcdsaMessage(driver, secretId, syncSessionId, signSessionId, message, signatureToken)
// Verify the resulting signature against a compound public key
print([
"curve": EcdsaCurve.secp256k1,
"publicKey": publicKey,
"message": message,
"signature": [
"r": signature.r,
"s": signature.s,
"recovery": signature.recovery
] as [String : Any]
] as [String : Any])
Methods
syncDistributedEcdsaKey(secretId:syncSessionId:curve:derivationCoin:derivationAccount:)
func syncDistributedEcdsaKey(secretId: String, syncSessionId: String, curve: EcdsaCurve, derivationCoin: UInt32, derivationAccount: UInt32) async throws -> String
Perform distributed key synchronisation 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 synchronisation is performed over these fragments in order to get a compound public key for that pair.
Despite each session is bearing a unique set of data, the resulting key is deterministic in regards to further synchronisations, 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, a new synchronisation session data is saved to a persistent storage, accessible by the provided session ID.
From this point one may freely call getEcdsaPublicKey(secretId:syncSessionId:)
or signEcdsaMessage(secretId:syncSessionId:signSessionId:message:signatureToken:)
as long as synchronisation data (and secret) is accessible in the storage.
Example
// Assume an already generated secret and established connection
// Generate random session ID for a new synchronisation session
let syncSessionId = UUID().uuidString
// Perform a ECDSA-over-secp256k1 synchronisation procedure for a key with path `m/44'/0'/0'/0'/0'
let publicKey = await syncDistributedEcdsaKey(secretId, syncSessionId, curve, derivationCoin, derivationAccount)
// At this point synchronisation data is saved to the storage so one may access it to get a compound public key
let _publicKey = await getEcdsaPublicKey(secretId, syncSessionId)
// Which is obviously the same key that was returned from the synchronisation procedure itself
XCTAssertEqual(publicKey, _publicKey)
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret to be used for synchronisation |
syncSessionId | ID (UUID) of this synchronisation procedure |
curve | elliptic curve to use (only .secp256k1 is supported at the moment) |
derivationCoin | “coin” property of HD key derivation (BIP-44) |
derivationAccount | “account” property of HD key derivation (BIP-44) |
Returns
String
compound public key (which may also be accessed via getEcdsaPublicKey(secretId:syncSessionId:)
later)
getEcdsaPublicKey(secretId:syncSessionId:)
Get a compound public key from an already performed synchronisation session
Example
// Assume an already performed synchronisation procedure
let publicKey = await getEcdsaPublicKey(secretId: secretId, syncSessionId: syncSessionId)
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret used for synchronisation |
syncSessionId | ID (UUID) of a synchronisation procedure |
Returns
String
compound public key, synchronised during that session
removeDistributedEcdsaKey(secretId:syncSessionId:)
Remove an already performed synchronisation session from storage
Example
// Assume an already performed synchronisation procedure
await removeDistributedEcdsaKey(secretId: secretId, syncSessionId: syncSessionId)
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret used for synchronisation |
syncSessionId | ID (UUID) of a synchronisation procedure |
Returns
void
signEcdsaMessage(secretId:syncSessionId:signSessionId:message:signatureToken:)
func signEcdsaMessage(secretId: String, syncSessionId: String, signSessionId: String, message: String, signatureToken: String) async throws -> EcdsaSignature
Generate distributed ECDSA signature for a given message
This method requires an already performed synchronisation procedure and does not store anythig in the permanent storage. Messages of a length, different from 32 bytes are padded/truncated according to ECDSA algorithm.
Example
// Assume an already performed synchronisation procedure and established connection
// Generate random session ID for a new signing session
let signSessionId = UUID().uuidString
// Message to be signed
let message = "7hJ9yN2OdIQo2kJu0arZgw2EKnMX5FGtQ6jlCWuLXCM="
// Generate a ECDSA signature
let signature = await signEcdsaMessage(driver, secretId, syncSessionId, signSessionId, message, signatureToken)
// Verify the resulting signature against a compound public key
print([
"curve": EcdsaCurve.secp256k1,
"publicKey": publicKey,
"message": message,
"signature": [
"r": signature.r,
"s": signature.s,
"recovery": signature.recovery
] as [String : Any]
] as [String : Any])
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret used for synchronisation |
syncSessionId | ID (UUID) of a corresponding synchronisation procedure |
signSessionId | ID (UUID) of this signing procedure |
message | message (32 bytes, base64 encoded) to be signed |
signatureToken | - |
Returns
EcdsaSignature
ECDSA signature in standard format
syncDistributedEddsaKey(secretId:syncSessionId:curve:derivationCoin:derivationAccount:)
func syncDistributedEddsaKey(secretId: String, syncSessionId: String, curve: EddsaCurve, derivationCoin: UInt32, derivationAccount: UInt32) async throws -> String
Perform distributed key synchronisation 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 synchronisation is performed over these fragments in order to get a compound public key for that pair.
Despite each session is bearing a unique set of data, the resulting key is deterministic in regards to further synchronisations, 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, a new synchronisation session data is saved to a persistent storage, accessible by the provided session ID.
From this point one may freely call getEddsaPublicKey(secretId:syncSessionId:)
or signEddsaMessage(secretId:syncSessionId:signSessionId:message:signatureToken:)
as long as synchronisation data (and secret) is accessible in the storage.
Example
// Assume an already generated secret and established connection
// Generate random session ID for a new synchronisation session
let syncSessionId = UUID().uuidString
// Perform a EDDSA-over-secp256k1 synchronisation procedure for a key with path "m/44'/0'/0'/0'/0'"
let publicKey = await syncDistributedEddsaKey(secretId, syncSessionId, curve, derivationCoin, derivationAccount)
// At this point synchronisation data is saved to the storage so one may access it to get a compound public key
let _publicKey = await getEddsaPublicKey(driver, secretId, syncSessionId)
// Which is obviously the same key that was returned from the synchronisation procedure itself
XCTAssertEqual(publicKey, _publicKey)
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret to be used for synchronisation |
syncSessionId | ID (UUID) of this synchronisation procedure |
curve | elliptic curve to use (only .ed25519 is supported at the moment) |
derivationCoin | “coin” property of HD key derivation (BIP-44) |
derivationAccount | “account” property of HD key derivation (BIP-44) |
Returns
String
compound public key (which may also be accessed via getEddsaPublicKey(secretId:syncSessionId:)
later)
getEddsaPublicKey(secretId:syncSessionId:)
Get a compound public key from an already performed synchronisation session
Example
// Assume an already performed synchronisation procedure
let publicKey = await getEddsaPublicKey(secretId: secretId, syncSessionId: syncSessionId)
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret used for synchronisation |
syncSessionId | ID (UUID) of a synchronisation procedure |
Returns
String
compound public key, synchronised during that session
removeDistributedEddsaKey(secretId:syncSessionId:)
Remove an already performed synchronisation session from storage
Example
// Assume an already performed synchronisation procedure
await removeDistributedEddsaKey(secretId: secretId, syncSessionId: syncSessionId)
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret used for synchronisation |
syncSessionId | ID (UUID) of a synchronisation procedure |
Returns
void
signEddsaMessage(secretId:syncSessionId:signSessionId:message:signatureToken:)
func signEddsaMessage(secretId: String, syncSessionId: String, signSessionId: String, message: String, signatureToken: String) async throws -> EddsaSignature
Generate distributed EDDSA signature for a given message
This method requires an already performed synchronisation procedure and does not store anythig in the permanent storage. Messages of a length, different from 32 bytes are padded/truncated according to EDDSA algorithm.
Example
// Assume an already performed synchronisation procedure and established connection
// Generate random session ID for a new signing session
let signSessionId = UUID().uuidString
// Message to be signed
let message = "7hJ9yN2OdIQo2kJu0arZgw2EKnMX5FGtQ6jlCWuLXCM="
// Generate a EDDSA signature
let signature = await signEddsaMessage(driver, secretId, syncSessionId, signSessionId, message, signatureToken)
// Verify the resulting signature against a compound public key
print([
"curve": EddsaCurve.ed25519,
"publicKey": publicKey,
"message": message,
"signature": [
"R": signature.R,
"s": signature.s
] as [String : Any]
] as [String : Any])
Parameters
Name | Description |
---|---|
secretId | ID (UUID) of a secret used for synchronisation |
syncSessionId | ID (UUID) of a corresponding synchronisation procedure |
signSessionId | ID (UUID) of this signing procedure |
message | message (32 bytes, base64 encoded) to be signed |
signatureToken | - |
Returns
EddsaSignature
EDDSA signature in standard format
on(validateMessage:)
Subscribe to server-side events
Server-side events are not limited to actual server, but may also be used to proces requests on clients, the deciding factor here is who initiates the procedure.
Parameters
Name | Description |
---|---|
validateMessage | external transaction validator |
Returns
void
off()
Unsubscribe from server-side events
Server-side events are not limited to actual server, but may also be used to proces requests on clients, the deciding factor here is who initiates the procedure.
Returns
void