Getting Started
Generate a key and begin signing in under a minute with the Sodot Kotlin SDK.
Key Generation
First, we must generate a public key and key shares so that we can later sign messages.
You will need your API_KEY.
We show example code for a 2-of-3 threshold signing scenario.
(Note that Sodot MPC SDK allows any t-of-n threshold signing setting)
To generate a key we use the objects defined in the SDK:
BIP-340 (for Bitcoin Taproot), ECDSA, ED25519, SR25519, and Exportable ED25519 are all supported through the Kotlin SDK.
- ECDSA
- ED25519
- SR25519
- BIP340
- EXPORTABLE ED25519
import com.sodot.kotlin_sdk.Ecdsa
import kotlinx.coroutines.runBlocking
// 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
val N = 3
val T = 2
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the ECDSA scheme
val ecdsa = Ecdsa(HOST_URL)
// Create a room for key generation
val keygenRoomUuid = ecdsa.createRoom(HOST_URL, N, API_KEY)
// All parties call initKeygen to get initialization data that contains a public key and keypair
val keygenInitResult = ecdsa.initKeygen()
// All parties receive the keygenIds from all other parties
val keygenIds = arrayOf("keygenId1", "keygenId2", "keygenId3")
// All parties join the keygen room
val (pubkey, secretShare) = ecdsa.keygen(
keygenRoomUuid,
N,
T,
keygenInitResult.keypair,
keygenIds
)
// Pick the derivation path of the public key you want to sign for
val derivationPath = intArrayOf(44, 60, 0, 0, 0)
// Get the public key for the derivation path
val derivedPubkey = ecdsa.derivePubkey(secretShare, derivationPath)
// Store secretShare securely for later use in signing
}
import com.sodot.kotlin_sdk.Ed25519
import kotlinx.coroutines.runBlocking
// 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
val N = 3
val T = 2
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the Ed25519 scheme
val ed25519 = Ed25519(HOST_URL)
// Create a room for key generation
val keygenRoomUuid = ed25519.createRoom(HOST_URL, N, API_KEY)
// All parties call initKeygen to get initialization data that contains a public key and keypair
val keygenInitResult = ed25519.initKeygen()
// All parties receive the keygenIds from all other parties
val keygenIds = arrayOf("keygenId1", "keygenId2", "keygenId3")
// All parties join the keygen room
val (pubkey, secretShare) = ed25519.keygen(
keygenRoomUuid,
N,
T,
keygenInitResult.keypair,
keygenIds
)
// Pick the derivation path of the public key you want to sign for
val derivationPath = intArrayOf(44, 60, 0, 0, 0)
// Get the public key for the derivation path
val derivedPubkey = ed25519.derivePubkey(secretShare, derivationPath)
// Option 2: Derive public key using Spub for more flexibility
val spub = ed25519.getSpub(secretShare)
val pubkeyFromSpub = ed25519.derivePubkeyFromSpub(spub, derivationPath)
// Store secretShare securely for later use in signing
}
import com.sodot.kotlin_sdk.Sr25519
import kotlinx.coroutines.runBlocking
// 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
val N = 3
val T = 2
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the Sr25519 scheme
val sr25519 = Sr25519(HOST_URL)
// Create a room for key generation
val keygenRoomUuid = sr25519.createRoom(HOST_URL, N, API_KEY)
// All parties call initKeygen to get initialization data that contains a public key and keypair
val keygenInitResult = sr25519.initKeygen()
// All parties receive the keygenIds from all other parties
val keygenIds = arrayOf("keygenId1", "keygenId2", "keygenId3")
// All parties join the keygen room
val (pubkey, secretShare) = sr25519.keygen(
keygenRoomUuid,
N,
T,
keygenInitResult.keypair,
keygenIds
)
// Pick the derivation path of the public key you want to sign for
// Note: SR25519 uses string arrays for derivation paths, unlike numeric paths in other schemes
val derivationPath = arrayOf("this", "is", "a", "derivation", "path")
// Get the public key for the derivation path
val derivedPubkey = sr25519.derivePubkey(secretShare, derivationPath)
// Option 2: Derive public key using the base pubkey for more flexibility
val basePubkey = sr25519.getPubkey(secretShare)
val pubkeyFromBase = sr25519.derivePubkeyFromPubkey(basePubkey, derivationPath)
// Store secretShare securely for later use in signing
}
import com.sodot.kotlin_sdk.Bip340
import kotlinx.coroutines.runBlocking
// 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
val N = 3
val T = 2
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the BIP340 scheme
val bip340 = Bip340(HOST_URL)
// Create a room for key generation
val keygenRoomUuid = bip340.createRoom(HOST_URL, N, API_KEY)
// All parties call initKeygen to get initialization data that contains a public key and keypair
val keygenInitResult = bip340.initKeygen()
// All parties receive the keygenIds from all other parties
val keygenIds = arrayOf("keygenId1", "keygenId2", "keygenId3")
// All parties join the keygen room
val (pubkey, secretShare) = bip340.keygen(
keygenRoomUuid,
N,
T,
keygenInitResult.keypair,
keygenIds
)
// Pick the derivation path of the public key you want to sign for
val derivationPath = intArrayOf(44, 60, 0, 0, 0)
// For BIP340, you can optionally use a tweak
val tweak: ByteArray? = null // null for no tweak, or provide a 32-byte tweak
// Get the tweaked public key for the derivation path
val derivedPubkey = bip340.deriveTweakPubkey(secretShare, derivationPath, tweak)
// Option 2: Derive tweaked public key using Xpub for more flexibility
val xpub = bip340.getXpub(secretShare)
val pubkeyFromXpub = bip340.deriveTweakPubkeyFromXpub(xpub, derivationPath, tweak)
// Store secretShare securely for later use in signing
}
import com.sodot.kotlin_sdk.ExportableEd25519
import kotlinx.coroutines.runBlocking
// 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
val N = 3
val T = 2
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Create a room for key generation
val keygenRoomUuid = ExportableEd25519.createRoom(HOST_URL, N, API_KEY)
// All parties call initKeygen to get initialization data that contains a public key and keypair
val keygenInitResult = ExportableEd25519.initKeygen()
// All parties receive the keygenIds from all other parties
val keygenIds = arrayOf("keygenId1", "keygenId2", "keygenId3")
// Initialize the ExportableEd25519 scheme
val exportableEd25519 = ExportableEd25519(HOST_URL)
// For ExportableEd25519, there are two distinct key generation methods:
// 1. For the party initiating the key generation (Party 1):
val (pubkey1, secretShare1) = exportableEd25519.sampleKey(
keygenRoomUuid,
N,
T,
keygenInitResult.keypair,
keygenIds
)
// 2. For parties receiving the key (Parties 2 and 3):
val (pubkey2, secretShare2) = exportableEd25519.receiveKey(
keygenRoomUuid,
N,
T,
keygenInitResult.keypair,
keygenIds
)
// Get the public key from a secret share
val pubkeyFromShare = exportableEd25519.getPubkey(secretShare1)
// Store secretShare securely for later use in signing
}
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
- SR25519
- BIP340
- EXPORTABLE ED25519
import com.sodot.kotlin_sdk.Ecdsa
import com.sodot.kotlin_sdk.MessageHash
import kotlinx.coroutines.runBlocking
// To sign a message, create a signing room on the server side, using your API_KEY
val T = 2 // We only need T signers (of our N total parties)
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the ECDSA scheme
val ecdsa = Ecdsa(HOST_URL)
// Create a room for signing
val signingRoomUuid = ecdsa.createRoom(HOST_URL, T, API_KEY)
// Each participating party uses their secret share from key generation
val secretShare = "previously_stored_secret_share_from_keygen"
// The derivation path to use for signing (same as used to generate the pubkey)
val derivationPath = intArrayOf(44, 60, 0, 0, 0)
// Create a simple example message instead of random bytes
val messageBytes = "Example message to sign".toByteArray()
val messageHash = MessageHash.sha256(messageBytes)
// T parties join the signing room and perform the signing
val signature = ecdsa.sign(
signingRoomUuid,
secretShare,
messageHash,
derivationPath
)
}
import com.sodot.kotlin_sdk.Ed25519
import kotlinx.coroutines.runBlocking
// To sign a message, create a signing room on the server side, using your API_KEY
val T = 2 // We only need T signers (of our N total parties)
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the Ed25519 scheme
val ed25519 = Ed25519(HOST_URL)
// Create a room for signing
val signingRoomUuid = ed25519.createRoom(HOST_URL, T, API_KEY)
// Each participating party uses their secret share from key generation
val secretShare = "previously_stored_secret_share_from_keygen"
// The derivation path to use for signing (same as used to generate the pubkey)
val derivationPath = intArrayOf(44, 60, 0, 0, 0)
// The message to sign
val messageBytes = "Example message to sign".toByteArray()
// T parties join the signing room and perform the signing
val signature = ed25519.sign(
signingRoomUuid,
secretShare,
messageBytes,
derivationPath
)
// The signature can be verified against the public key
println("Signature: ${signature.hexEncode()}")
}
import com.sodot.kotlin_sdk.Sr25519
import kotlinx.coroutines.runBlocking
// To sign a message, create a signing room on the server side, using your API_KEY
val T = 2 // We only need T signers (of our N total parties)
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the Sr25519 scheme
val sr25519 = Sr25519(HOST_URL)
// Create a room for signing
val signingRoomUuid = sr25519.createRoom(HOST_URL, T, API_KEY)
// Each participating party uses their secret share from key generation
val secretShare = "previously_stored_secret_share_from_keygen"
// The derivation path to use for signing (same as used to generate the pubkey)
// Note: SR25519 uses string arrays for derivation paths, unlike numeric paths in other schemes
val derivationPath = arrayOf("this", "is", "a", "derivation", "path")
// The message to sign
val messageBytes = "Example message to sign".toByteArray()
// T parties join the signing room and perform the signing
val signature = sr25519.sign(
signingRoomUuid,
secretShare,
messageBytes,
derivationPath
)
// The signature can be verified against the public key
println("Signature: ${signature.hexEncode()}")
}
import com.sodot.kotlin_sdk.Bip340
import kotlinx.coroutines.runBlocking
// To sign a message, create a signing room on the server side, using your API_KEY
val T = 2 // We only need T signers (of our N total parties)
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the BIP340 scheme
val bip340 = Bip340(HOST_URL)
// Create a room for signing
val signingRoomUuid = bip340.createRoom(HOST_URL, T, API_KEY)
// Each participating party uses their secret share from key generation
val secretShare = "previously_stored_secret_share_from_keygen"
// The derivation path to use for signing (same as used to generate the pubkey)
val derivationPath = intArrayOf(44, 60, 0, 0, 0)
// For BIP340, you can optionally use a tweak (same as used for pubkey derivation)
val tweak: ByteArray? = null // null for no tweak, or provide a 32-byte tweak
// The message to sign
val messageBytes = "Example message to sign".toByteArray()
// T parties join the signing room and perform the signing
val signature = bip340.sign(
signingRoomUuid,
secretShare,
messageBytes,
derivationPath,
tweak
)
// The signature can be verified against the public key
println("Signature: ${signature.hexEncode()}")
}
import com.sodot.kotlin_sdk.ExportableEd25519
import kotlinx.coroutines.runBlocking
// To sign a message, create a signing room on the server side, using your API_KEY
val T = 2 // We only need T signers (of our N total parties)
val API_KEY = "MY_API_KEY"
val HOST_URL = "demo.sodot.dev" // Your Relay URL
runBlocking {
// Initialize the ExportableEd25519 scheme
val exportableEd25519 = ExportableEd25519(HOST_URL)
// Create a room for signing
val signingRoomUuid = exportableEd25519.createRoom(HOST_URL, T, API_KEY)
// Each participating party uses their secret share from key generation
val secretShare = "previously_stored_secret_share_from_keygen"
// The message to sign
val messageBytes = "Example message to sign".toByteArray()
// T parties join the signing room and perform the signing
// Note: ExportableEd25519 signing doesn't use derivation paths
val signature = exportableEd25519.sign(
signingRoomUuid,
secretShare,
messageBytes
)
// The signature can be verified against the public key
println("Signature: ${signature.hexEncode()}")
}
Additional Features
The Sodot Kotlin SDK provides additional advanced features:
Key Refresh
You can refresh key shares without changing the underlying key:
// Example for ECDSA
val ecdsa = Ecdsa(HOST_URL)
val (pubkey, refreshedSecretShare) = ecdsa.refresh(roomUuid, secretShare)
// Use refreshedSecretShare instead of the old secretShare
// Example for ExportableEd25519
val exportableEd25519 = ExportableEd25519(HOST_URL)
val (pubkey, refreshedSecretShare) = exportableEd25519.refresh(roomUuid, secretShare)
// Use refreshedSecretShare instead of the old secretShare
Resharing for New Thresholds
You can change the threshold parameters by resharing:
// Example for a new party joining BIP340 resharing
val bip340 = Bip340(HOST_URL)
val oldThreshold = 2
val newThreshold = 3
val (pubkey, secretShare) = bip340.reshareNewParty(
roomUuid,
oldThreshold,
newThreshold,
keygenInitResult.keypair,
keygenIds
)
Key Import
You can import existing private keys into the MPC framework:
// Example for importing an ECDSA key (as the importer)
val ecdsa = Ecdsa(HOST_URL)
val privateKey = byteArrayOf(/* your private key bytes */)
val (pubkey, secretShare) = ecdsa.importPrivateKeyImporter(
roomUuid,
threshold,
privateKey,
keygenInitResult.keypair,
keygenIds
)
// Example for importing an ExportableEd25519 key (as the importer)
val exportableEd25519 = ExportableEd25519(HOST_URL)
val ed25519PrivateKey = byteArrayOf(/* your private key bytes */)
val (pubkey, secretShare) = exportableEd25519.importPrivateKeyImporter(
roomUuid,
threshold,
ed25519PrivateKey,
keygenInitResult.keypair,
keygenIds
)
For full details on all available methods, refer to the SDK documentation for each cryptographic scheme object.