Getting Started
In this guide, we will walk you through the process of generating a new distributed key and signing a payload with it using the Sodot Rust SDK.
Distributed Key Generation
First, You will generate a sharded key-pair that can be used for threshold signing. You will need your Relay API_KEY.
In this example, we will use a 2-of-3 threshold signing scenario. (Sodot MPC SDK allows any t-of-n threshold setting.)
important
This example code uses tokio as the async runtime, but you can use any other async runtime by adapting the code accordingly.
- ECDSA
- Ed25519
- BIP340
use sodot_mpc::{Ecdsa, KeygenId, 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());
// Your server side creates a room for 3 parties using its API_KEY
// Creating a room uuid should always happen on the server side using your API_KEY, so that the API_KEY is never exposed to the client side
let keygen_room_uuid = ecdsa
.create_room(NonZeroU16::new(N).unwrap(), API_KEY)
.await?;
// All parties call init_keygen to get (KeygenId, KeygenPrivateKey) as result
// The KeygenId is the public part that you should pass to all other parties, the KeygenPrivateKey is the private state you should keep until the actual keygen completes.
let (keygen_id, keygen_private_key) = ecdsa.init_keygen()?;
// All parties receive the keygenIds from all other parties
let keygen_ids = [
KeygenId::new("KeygenIdParty1".into()),
KeygenId::new("KeygenIdParty2".into()),
];
// All parties join the keygen room
let (public_key, secret_share) = ecdsa
.keygen(
&keygen_room_uuid,
N.try_into().expect("N is a valid NonZeroU16"),
T.try_into().expect("T is a valid NonZeroU16"),
&keygen_private_key,
&keygen_ids,
)
.await?;
// Save the secret share in your secure storage. it will allow you to participate in signing using it later on.
let serialized_secret_share = secret_share.as_str();
// Restore the secret share from the serialized string
let restored_share = SecretShare::<Ecdsa>::from(serialized_secret_share.to_string());
// Pick the derivation path of the public key you want to sign for
let derivation_path = [44, 60, 0, 0, 0];
// Get the public key for the derivation path
let derived_pubkey = ecdsa.derive_pubkey(&restored_share, &derivation_path)?;
println!(
"Compressed Derived Public Key: {:?}",
derived_pubkey.compressed()
);
Ok(())
}
use sodot_mpc::{Ed25519, KeygenId, 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());
// Your server side creates a room for 3 parties using its API_KEY
// Creating a room uuid should always happen on the server side using your API_KEY, so that the API_KEY is never exposed to the client side
let keygen_room_uuid = ed25519
.create_room(NonZeroU16::new(N).unwrap(), API_KEY)
.await?;
// All parties call init_keygen to get (KeygenId, KeygenPrivateKey) as result
// The KeygenId is the public part that you should pass to all other parties, the KeygenPrivateKey is the private state you should keep until the actual keygen completes.
let (keygen_id, keygen_private_key) = ed25519.init_keygen()?;
// All parties receive the keygenIds from all other parties
let keygen_ids = [
KeygenId::new("KeygenIdParty1".into()),
KeygenId::new("KeygenIdParty2".into()),
];
// All parties join the keygen room
let (public_key, secret_share) = ed25519
.keygen(
&keygen_room_uuid,
N.try_into().expect("N is a valid NonZeroU16"),
T.try_into().expect("T is a valid NonZeroU16"),
&keygen_private_key,
&keygen_ids,
)
.await?;
// Save the secret share in your secure storage. it will allow you to participate in signing using it later on.
let serialized_secret_share = secret_share.as_str();
// Restore the secret share from the serialized string
let restored_share = SecretShare::<Ed25519>::from(serialized_secret_share.to_string());
// Pick the derivation path of the public key you want to sign for
let derivation_path = [44, 60, 0, 0, 0];
// Get the public key for the derivation path
let derived_pubkey = ed25519.derive_pubkey(&restored_share, &derivation_path)?;
println!(
"Compressed Derived Public Key: {:?}",
derived_pubkey.into_bytes()
);
Ok(())
}
use sodot_mpc::{Bip340, KeygenId, SecretShare, bip340::Bip340Tweak};
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());
// Your server side creates a room for 3 parties using its API_KEY
// Creating a room uuid should always happen on the server side using your API_KEY, so that the API_KEY is never exposed to the client side
let keygen_room_uuid = bip340
.create_room(NonZeroU16::new(N).unwrap(), API_KEY)
.await?;
// All parties call init_keygen to get (KeygenId, KeygenPrivateKey) as result
// The KeygenId is the public part that you should pass to all other parties, the KeygenPrivateKey is the private state you should keep until the actual keygen completes.
let (keygen_id, keygen_private_key) = bip340.init_keygen()?;
// All parties receive the keygenIds from all other parties
let keygen_ids = [
KeygenId::new("KeygenIdParty1".into()),
KeygenId::new("KeygenIdParty2".into()),
];
// All parties join the keygen room
let (public_key, secret_share) = bip340
.keygen(
&keygen_room_uuid,
N.try_into().expect("N is a valid NonZeroU16"),
T.try_into().expect("T is a valid NonZeroU16"),
&keygen_private_key,
&keygen_ids,
)
.await?;
// Save the secret share in your secure storage. it will allow you to participate in signing using it later on.
let serialized_secret_share = secret_share.as_str();
// Restore the secret share from the serialized string
let restored_share = SecretShare::<Bip340>::from(serialized_secret_share.to_string());
// Pick the derivation path and BIP-340 tweak of the public key you want to sign for
let derivation_path = [44, 60, 0, 0, 0];
let tweak = Bip340Tweak::from([0; 32]);
// Get the public key for the derivation path
let derived_pubkey =
bip340.derive_tweak_pubkey(&restored_share, &derivation_path, Some(&tweak))?;
println!(
"Compressed Derived Public Key: {:?}",
derived_pubkey.as_bytes()
);
Ok(())
}
API reference
Full details can be found in the API Reference.
Behind the scenes this is the rough flow of communication that occurs:
Signing
Now that we have key shares on all the devices/servers of the potential signers we can sign by running:
- ECDSA
- Ed25519
- BIP340
use sodot_mpc::{Ecdsa, SecretShare, ecdsa::MessageHash};
use std::num::NonZeroU16;
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());
let secret_share = SecretShare::<Ecdsa>::from("serialized_share_from_keygen".to_string());
// To sign a message, create a signing room for T parties on the server side, using your API_KEY
let signing_room_uuid = ecdsa
.create_room(NonZeroU16::new(T).unwrap(), API_KEY)
.await?;
// Pick the derivation path of the public key you want to sign for
let derivation_path = [44, 60, 0, 0, 0];
// Get the public key for the derivation path
let derived_pubkey = ecdsa.derive_pubkey(&secret_share, &derivation_path)?;
// Choose a message to sign
let msg = b"Hello, world!";
// Hash the message
let message_hash = MessageHash::sha256(msg);
// 2 parties join the signing room
let signature = ecdsa
.sign(
&signing_room_uuid,
&secret_share,
&message_hash,
&derivation_path,
)
.await?;
// signature can be verified against the derived pubkey
println!("Signature: {:?}", signature);
Ok(())
}
use sodot_mpc::{Ed25519, SecretShare};
use std::num::NonZeroU16;
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());
let secret_share = SecretShare::<Ed25519>::from("serialized_share_from_keygen".to_string());
// To sign a message, create a signing room for T parties on the server side, using your API_KEY
let signing_room_uuid = ed25519
.create_room(NonZeroU16::new(T).unwrap(), API_KEY)
.await?;
// Pick the derivation path of the public key you want to sign for
let derivation_path = [44, 60, 0, 0, 0];
// Get the public key for the derivation path
let derived_pubkey = ed25519.derive_pubkey(&secret_share, &derivation_path)?;
// Choose a message to sign
let message = b"Hello, world!";
// 2 parties join the signing room
let signature = ed25519
.sign(&signing_room_uuid, &secret_share, message, &derivation_path)
.await?;
// signature can be verified against the derived pubkey
println!("Signature: {:?}", signature);
Ok(())
}
use sodot_mpc::{Bip340, SecretShare, bip340::Bip340Tweak};
use std::num::NonZeroU16;
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());
let secret_share = SecretShare::<Bip340>::from("serialized_share_from_keygen".to_string());
// To sign a message, create a signing room for T parties on the server side, using your API_KEY
let signing_room_uuid = bip340
.create_room(NonZeroU16::new(T).unwrap(), API_KEY)
.await?;
// Pick the derivation path of the public key you want to sign for
let derivation_path = [44, 60, 0, 0, 0];
let tweak = Bip340Tweak::from([1; 32]);
// Get the public key for the derivation path
let derived_pubkey =
bip340.derive_tweak_pubkey(&secret_share, &derivation_path, Some(&tweak))?;
// Choose a message to sign
let message = b"Hello, world!";
// 2 parties join the signing room
let signature = bip340
.sign(
&signing_room_uuid,
&secret_share,
message,
&derivation_path,
Some(&tweak),
)
.await?;
// signature can be verified against the derived pubkey
println!("Signature: {:?}", signature);
Ok(())
}