Skip to main content
Version: 1.6

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.

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(())
}
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:

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(())
}