Key Lifecycle
Once we have already generated our key shares, we can refresh and also export them. We show how to do this below.
Key Refresh
Key Refresh (sometimes referred to as "Key Rotation") is a protocol that allows the devices to compute entirely new secret shares of the same public key.
Refreshing the key material frequently is considered a best practice for enhanced security, as it adds an element of time to the security setting. Since even if one secret share was compromised, then after Key Refresh the compromised secret share will no longer be useful. Below is an example of how to use Key Refresh:
- ECDSA
- Ed25519
- BIP340
use sodot_mpc::{Ecdsa, SecretShare};
use std::num::NonZeroU16;
const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ecdsa = Ecdsa::new(HOST_URL.to_string());
// An original_secret_share was previously generated using keygen
let original_secret_share =
SecretShare::<Ecdsa>::from("serialized_share_from_keygen".to_string());
// We now refresh the secret key material of our public key
// Your server creates a room for N parties
let refresh_room_uuid = ecdsa
.create_room(NonZeroU16::new(N).unwrap(), API_KEY)
.await?;
// All parties now join the refresh room using their current secret key material
let (refreshed_pubkey, refreshed_secret_share) = ecdsa
.refresh(&refresh_room_uuid, &original_secret_share)
.await?;
// Note: refreshed_pubkey == original pubkey
// refreshed_secret_share can now be used for signing under the same T threshold, as well as be refreshed again
// the original secret share can be destroyed after making sure that the refresh was successful **for all parties**
println!(
"Refresh completed. Public key unchanged: {:?}",
refreshed_pubkey.compressed()
);
Ok(())
}
use sodot_mpc::{Ed25519, SecretShare};
use std::num::NonZeroU16;
const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ed25519 = Ed25519::new(HOST_URL.to_string());
// An original_secret_share was previously generated using keygen
let original_secret_share =
SecretShare::<Ed25519>::from("serialized_share_from_keygen".to_string());
// We now refresh the secret key material of our public key
// Your server creates a room for N parties
let refresh_room_uuid = ed25519
.create_room(NonZeroU16::new(N).unwrap(), API_KEY)
.await?;
// All parties now join the refresh room using their current secret key material
let (refreshed_pubkey, refreshed_secret_share) = ed25519
.refresh(&refresh_room_uuid, &original_secret_share)
.await?;
// Note: refreshed_pubkey == original pubkey
// refreshed_secret_share can now be used for signing under the same T threshold, as well as be refreshed again
// the original secret share can be destroyed after making sure that the refresh was successful **for all parties**
println!(
"Refresh completed. Public key unchanged: {:?}",
refreshed_pubkey.into_bytes()
);
Ok(())
}
use sodot_mpc::{Bip340, SecretShare};
use std::num::NonZeroU16;
const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let bip340 = Bip340::new(HOST_URL.to_string());
// An original_secret_share was previously generated using keygen
let original_secret_share =
SecretShare::<Bip340>::from("serialized_share_from_keygen".to_string());
// We now refresh the secret key material of our public key
// Your server creates a room for N parties
let refresh_room_uuid = bip340
.create_room(NonZeroU16::new(N).unwrap(), API_KEY)
.await?;
// All parties now join the refresh room using their current secret key material
let (refreshed_pubkey, refreshed_secret_share) = bip340
.refresh(&refresh_room_uuid, &original_secret_share)
.await?;
// Note: refreshed_pubkey == original pubkey
// refreshed_secret_share can now be used for signing under the same T threshold, as well as be refreshed again
// the original secret share can be destroyed after making sure that the refresh was successful **for all parties**
println!(
"Refresh completed. Public key unchanged: {:?}",
refreshed_pubkey.as_bytes()
);
Ok(())
}
Key Export
In the case that we want to stop using the MPC SDK for a specific set of keys, it is possible to export the full private key out of the system to be used elsewhere.
Note that by running Key Export we break the security model defined by splitting the private key into key shares. This operation is supported strictly in order to prevent vendor-lock and provide an exit strategy from using the SDK.
Keys that have undergone Key Export should never again be used as key shares in the context of the SDK.
Note that by design only one device will receive the full private key, this enables key export in cases where it only makes sense for one device to view the private key (such as a client-server model).
Below is an example of how to use Key Export:
- ECDSA
- Ed25519
- BIP340
use sodot_mpc::{Ecdsa, SecretShare};
const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ecdsa = Ecdsa::new(HOST_URL.to_string());
// An original_secret_share was previously generated using keygen
let secret_share = SecretShare::<Ecdsa>::from("serialized_share_from_keygen".to_string());
// Exporting the secret key material
// Your server creates a room for T parties (2 in this case)
let export_room_uuid = ecdsa
.create_room(T.try_into().expect("T is a valid NonZeroU16"), API_KEY)
.await?;
// Only one of the parties will receive the full private key, this party will retrieve its exportID:
let export_to = ecdsa.export_id(&secret_share)?;
// It will the propagate this exportID to the other parties
// Threshold (t) parties will now need to join the export room
let exported_private_key = ecdsa
.export_full_private_key(&export_room_uuid, &secret_share, &export_to)
.await?;
// exported_private_key is 'None' except for the party with export_to as its export_id
// For the exporting party it will be a normal base58 string xpriv
// It is most likely that importing the key elsewhere will only be supported for specific derivation paths
// Hence, it is possible to get the derived private key as well as public key for any derivation path
let derivation_path = [44, 60, 0, 0, 0];
let derived_private_key = ecdsa.derive_private_key_from_xpriv(
&exported_private_key.expect("export_to party will get the private key"),
&derivation_path,
)?;
// derived_private_key is the private key for derived pubkey and can now be imported into any non-MPC based ECDSA signing software
Ok(())
}
use sodot_mpc::{Ed25519, SecretShare};
const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ed25519 = Ed25519::new(HOST_URL.to_string());
// An original_secret_share was previously generated using keygen
let secret_share = SecretShare::<Ed25519>::from("serialized_share_from_keygen".to_string());
// Exporting the secret key material
// Your server creates a room for T parties (2 in this case)
let export_room_uuid = ed25519
.create_room(T.try_into().expect("T is a valid NonZeroU16"), API_KEY)
.await?;
// Only one of the parties will receive the full private key, this party will retrieve its exportID:
let export_to = ed25519.export_id(&secret_share)?;
// It will the propagate this exportID to the other parties
// Threshold (t) parties will now need to join the export room
let exported_private_key = ed25519
.export_full_private_key(&export_room_uuid, &secret_share, &export_to)
.await?;
// exported_private_key is 'None' except for the party with export_to as its export_id
// For the exporting party it will be an extended private key in a custom format (spriv)
// It is most likely that importing the key elsewhere will only be supported for specific derivation paths
// Hence, it is possible to get the derived private key as well as public key for any derivation path
let derivation_path = [44, 60, 0, 0, 0];
let derived_private_key = ed25519.derive_private_key_from_spriv(
&exported_private_key.expect("export_to party will get the private key"),
&derivation_path,
)?;
// derived_private_key is the private key for derived pubkey and can now be imported into any non-MPC based Ed25519 signing software
Ok(())
}
use sodot_mpc::{Bip340, SecretShare};
const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let bip340 = Bip340::new(HOST_URL.to_string());
// An original_secret_share was previously generated using keygen
let secret_share = SecretShare::<Bip340>::from("serialized_share_from_keygen".to_string());
// Exporting the secret key material
// Your server creates a room for T parties (2 in this case)
let export_room_uuid = bip340
.create_room(T.try_into().expect("T is a valid NonZeroU16"), API_KEY)
.await?;
// Only one of the parties will receive the full private key, this party will retrieve its exportID:
let export_to = bip340.export_id(&secret_share)?;
// It will the propagate this exportID to the other parties
// Threshold (t) parties will now need to join the export room
let exported_private_key = bip340
.export_full_private_key(&export_room_uuid, &secret_share, &export_to)
.await?;
// exported_private_key is 'None' except for the party with export_to as its export_id
// For the exporting party it will be an extended private key in a xpriv format
// It is most likely that importing the key elsewhere will only be supported for specific derivation paths
// Hence, it is possible to get the derived private key as well as public key for any derivation path
let derivation_path = [44, 60, 0, 0, 0];
let derived_private_key = bip340.derive_private_key_from_xpriv(
&exported_private_key.expect("export_to party will get the private key"),
&derivation_path,
)?;
// derived_private_key is the private key for derived pubkey and can now be imported into any non-MPC based BIP-340 signing software
Ok(())
}