All versions / React Native SDK v1.5

Getting Started

Generate a key and begin signing in under a minute.

Key Generation

First we must generate a public key and some 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 simply run:

Feature BIP-340 (for Bitcoin Taproot) is also supported. More details can be found [here]/rn/api-ref/classes/BIP340).

ECDSA RN:

import { StatefulEcdsa } from '@sodot/sodot-react-native-sdk';

// 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
const N = 3;
const T = 2;
const ecdsa = new StatefulEcdsa(); // Note that a stateless Ecdsa class is also available with the same API as the Node and Web SDKs
const API_KEY = 'MY_API_KEY';
const keygenRoomUuid = await ecdsa.createRoom(N, API_KEY);
// All parties call initKeygen to get a keygenId
const keygenId = await ecdsa.initKeygen('myKeyName');
// All parties receive the keygenIds from all other parties
const keygenIds = [keygenId1, keygenId2];
// All parties join the keygen room
const publicKey = await ecdsa.keygen(keygenRoomUuid, N, T, 'myKeyName', keygenIds);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const derivedPubkey = await ecdsa.derivePubkey('myKeyName', derivationPath);

API reference Full details can be found here.

Ed25519 RN:

import { StatefulEd25519 } from '@sodot/sodot-react-native-sdk';

// 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
const N = 3;
const T = 2;
const ed25519 = new StatefulEd25519(); // Note that a stateless Ed25519 class is also available with the same API as the Node and Web SDKs
const API_KEY = 'MY_API_KEY';
const keygenRoomUuid = await ed25519.createRoom(N, API_KEY);
// All parties call initKeygen to get a keygenId
const keygenId = await ed25519.initKeygen('myKeyName');
// All parties receive the keygenIds from all other parties
const keygenIds = [keygenId1, keygenId2];
// All parties join the keygen room
const publicKey = await ed25519.keygen(keygenRoomUuid, N, T, 'myKeyName', keygenIds);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const derivedPubkey = await ed25519.derivePubkey('myKeyName', derivationPath);

API reference Full details can be found here.

Behind the scenes this is the rough flow of communication that occurs:

sequenceDiagram
    actor Alice
    actor Bob
    actor Charlie
    participant App as App Server
    rect rgb(200,200,230,.3)
    Note left of Alice: Keygen Setup
    App->>Relay: Create room for 3 participants
    Relay->>App: room_id
    App->>Alice: room_id
    App->>Bob: room_id
    App->>Charlie: room_id
    par Alice handshakes w/ Bob
    Note over Alice,Bob: Propagated externally to the SDK 
    Alice-->>Bob: keygen_id
    Bob-->>Alice: keygen_id
    and Alice handshakes w/ Charlie
    Note over Alice,Charlie: Propagated externally to the SDK
    Alice-->>Charlie: keygen_id
    Charlie-->>Alice: keygen_id
    and Bob handshakes w/ Charlie
    Note over Bob,Charlie: Propagated externally to the SDK
    Bob-->>Charlie: keygen_id
    Charlie-->>Bob: keygen_id
    end
    Alice->>Relay: Connect to room_id
    Bob->>Relay: Connect to room_id
    Charlie->>Relay: Connect to room_id
    end
    rect rgb(200,230,200,.3)
    Note left of Alice: Key Generation
    Note over Alice,Charlie: Run Distributed Key Generation
    activate Alice
    activate Bob
    activate Charlie
    Alice->>Relay: Relayed Communication
    Relay->>Alice: 
    Bob->>Relay: 
    Relay->>Bob: 
    Charlie->>Relay: 
    Relay->>Charlie: 
    Note over Alice: Alice has a Key Share
    deactivate Alice
    Note over Bob: Bob has a Key Share
    deactivate Bob
    Note over Charlie: Charlie has a Key Share
    deactivate Charlie
    end

Signing

Now that we have key shares on all the devices/servers of the potential signers we can sign by running:

ECDSA RN:

import { StatefulEcdsa, MessageHash } from '@sodot/sodot-react-native-sdk';

// To sign a message, create a signing room on the server side, using your API_KEY
const N = 3;
const T = 2;
const ecdsa = new StatefulEcdsa(); // Note that a stateless Ecdsa class is also available with the same API as the Node and Web SDKs
const API_KEY = 'MY_API_KEY;
const signingRoomUuid = await ecdsa.createRoom(T, API_KEY);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const derivedPubkey = await ecdsa.derivePubkey('myKeyName', derivationPath);
// Hash the message
const messageHash = MessageHash.sha256('my message');
// 2 parties join the signing room
const signature = await ecdsa.sign(signingRoomUuid, 'myKeyName', messageHash, derivationPath);
// This signature can now be verified against derivedPubkey

API reference Full details can be found here.

Ed25519 RN:

import { StatefulEd25519 } from '@sodot/sodot-react-native-sdk';

// To sign a message, create a signing room on the server side, using your API_KEY
const N = 3;
const T = 2;
const ed25519 = new StatefulEd25519(); // Note that a stateless Ed25519 class is also available with the same API as the Node and Web SDKs
const API_KEY = 'MY_API_KEY';
const signingRoomUuid = await ed25519.createRoom(T, API_KEY);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const derivedPubkey = await ed25519.derivePubkey('myKeyName', derivationPath);
// The message as a hex string
const message = 'cafecafe';
// 2 parties join the signing room
const signature = await ecdsa.sign(signingRoomUuid, 'myKeyName', message, derivationPath);
// This signature can now be verified against derivedPubkey

API reference Full details can be found here.

Behind the scenes this is the rough flow of communication that occurs, note that since only 2 signers are needed, Alice (chosen as a non-signer in this example) doesn't participate at all in the protocol:

sequenceDiagram
    actor Alice
    actor Bob
    actor Charlie
    participant App as App Server
    rect rgb(200,200,230,.3)
    Note left of Alice: Room Setup
    App->>Relay: Create room for 2 participants
    Relay->>App: room_id
    App->>Bob: room_id
    App->>Charlie: room_id
    par Bob invites Charlie
    Note over Bob,Charlie: Propagated externally to the SDK 
    Bob-->>Charlie: msg, key_name
    Note over Charlie: Charlie decides whether they wish to sign msg
    end
    Bob->>Relay: Connect to room_id
    Charlie->>Relay: Connect to room_id
    end
    rect rgb(230,200,200,.3)
    Note left of Alice: Signing
    Note over Bob,Charlie: Run Signing
    activate Bob
    activate Charlie
    Bob->>Relay: Relayed Communication
    Relay->>Bob: 
    Charlie->>Relay: 
    Relay->>Charlie: 
    Note over Bob: Bob has a signature on msg
    deactivate Bob
    Note over Charlie: Charlie has a signature on msg
    deactivate Charlie
    end

Signature Verification

If necessary, the signature can be verified using extenal libraries.

ECDSA Node:

import elliptic from 'elliptic';

// An EcdsaKeygenResult was previously generated using keygen
const keygenResult = await ecdsa.keygen(...);

const pubkey = await ecdsa.derivePubkey(keygenResult, derivationPath);
// Hash the message
const messageHash = MessageHash.sha256('my message');
// 2 parties join the signing room
const signature = await ecdsa.sign(signingRoomUuid, keygenResult, messageHash, derivationPath);

var EC = elliptic.ec;
var ec = new EC('secp256k1');
const uncompressed = pubkey.serializeUncompressed();
const signDer = Buffer.from(signature.der).toString('hex');
var key = ec.keyFromPublic(uncompressed, 'hex');
const isVerified = key.verify(messageHash.bytes, signDer);

Full library documentation can be found here.

Ed25519 Node:

import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha512';
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

// An Ed25519KeygenResult was previously generated using keygen
const keygenResult = await ed25519.keygen(...);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const pubkey = await ed25519.derivePubkey(keygenResult, derivationPath);
// The message as a hex string
const message = 'cafecafe';
// 2 parties join the signing room
const signature = await ed25519.sign(signingRoomUuid, keygenResult, message, derivationPath);

// validating the signature using the '@noble/ed25519' package
const messageAsUint8Array = Uint8Array.from(Buffer.from(hexString, 'hex'));
const isValid = await ed.verify(signature, messageAsUint8Array, pubkey);


Full library documentation can be found here.

ECDSA Web:

import elliptic from 'elliptic';

// An EcdsaKeygenResult was previously generated using keygen
const keygenResult = await ecdsa.keygen(...);

const pubkey = await ecdsa.derivePubkey(keygenResult, derivationPath);
// Hash the message
const messageHash = MessageHash.sha256('my message');
// 2 parties join the signing room
const signature = await ecdsa.sign(signingRoomUuid, keygenResult, messageHash, derivationPath);

var EC = elliptic.ec;
var ec = new EC('secp256k1');
const uncompressed = pubkey.serializeUncompressed();
const signDer = Buffer.from(signature.der).toString('hex');
var key = ec.keyFromPublic(uncompressed, 'hex');
const isVerified = key.verify(messageHash.bytes, signDer);

Full library documentation can be found here.

Ed25519 Web:

import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha512';
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

// An Ed25519KeygenResult was previously generated using keygen
const keygenResult = await ed25519.keygen(...);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const pubkey = await ed25519.derivePubkey(keygenResult, derivationPath);
// The message as a hex string
const message = 'cafecafe';
// 2 parties join the signing room
const signature = await ed25519.sign(signingRoomUuid, keygenResult, message, derivationPath);

// validating the signature using the '@noble/ed25519' package
const messageAsUint8Array = Uint8Array.from(Buffer.from(hexString, 'hex'));
const isValid = await ed.verify(signature, messageAsUint8Array, pubkey);


Full library documentation can be found here.

ECDSA RN:

import elliptic from 'elliptic';

// An EcdsaKeygenResult was previously generated using keygen
const keygenResult = await ecdsa.keygen(...);

const pubkey = await ecdsa.derivePubkey(keygenResult, derivationPath);
// Hash the message
const messageHash = MessageHash.sha256('my message');
// 2 parties join the signing room
const signature = await ecdsa.sign(signingRoomUuid, keygenResult, messageHash, derivationPath);

var EC = elliptic.ec;
var ec = new EC('secp256k1');
const uncompressed = pubkey.serializeUncompressed();
const signDer = Buffer.from(signature.der).toString('hex');
var key = ec.keyFromPublic(uncompressed, 'hex');
const isVerified = key.verify(messageHash.bytes, signDer);

Full library documentation can be found here.

Ed25519 RN:

import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha512';
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

// An Ed25519KeygenResult was previously generated using keygen
const keygenResult = await ed25519.keygen(...);

// Pick the derivation path of the public key you want to sign for
const derivationPath = new Uint32Array([44,60,0,0,0]);
// Get the public key for the derivation path
const pubkey = await ed25519.derivePubkey(keygenResult, derivationPath);
// The message as a hex string
const message = 'cafecafe';
// 2 parties join the signing room
const signature = await ed25519.sign(signingRoomUuid, keygenResult, message, derivationPath);

// validating the signature using the '@noble/ed25519' package
const messageAsUint8Array = Uint8Array.from(Buffer.from(hexString, 'hex'));
const isValid = await ed.verify(signature, messageAsUint8Array, pubkey);

Full library documentation can be found [here.](https://www.npmjs.com/package/@noble/ed25519/v/1.7.1)

ECDSA Go:


import (
	"crypto/sha256"
	"github.com/kaspanet/go-secp256k1"
)

// A public key were generated using MPC
// Pick the derivation path of the public key you want to sign for
derivationPath := []uint32{44, 60, 0, 0, 0}
// Get the public key for the derivation path
derivedPubKey, err := ecdsa.DerivePubkey(secretShare, derivationPath)
if err != nil {
	panic(err)
}
// Hash the message
messageHash := sodot.MessageHashFromSha256([]byte("my message"))
// 2 parties join the signing room
signature, err := ecdsa.Sign(signingRoomUuid, secretShare, messageHash, derivationPath)
if err != nil {
	panic(err)
}

// Create a signature and public key according to kaspanet/go-secp256k's format
serializedSignature := append(signature.R(), signature.S()...)	
deserializedSignature, _ := secp256k1.DeserializeECDSASignature((*secp256k1.SerializedECDSASignature)(serializedSignature))
compressedPk = derivedPubKey.SerializeCompressed()
pubKey, _ := secp256k1.DeserializeECDSAPubKey(compressedPk[:])
var hash = secp256k1.Hash(messageHash)
// validate the siganture
isValid := pubKey.ECDSAVerify(&messageHash, deserializedSignature)

Full library documentation can be found here.

Ed25519 Go:

import (
	"crypto/ed25519"
)

// Define sodot SDK variable with sodotEd25519 so to not be cofused with crypto/ed25519 package
sodotEd25519 := sodot.NewEd25519("")

// Pick the derivation path of the public key you want to sign for
derivationPath := []uint32{44, 60, 0, 0, 0}
// Get the public key for the derivation path
derivedPubKey, err := ed25519.DerivePubkey(secretShare, derivationPath)
if err != nil {
    panic(err)
}
// The derived public key can now be used to verify signatures with the same derivation path
_ = derivedPubKey

// Define the message
message := []byte("my message")
// 2 parties join the signing room
signature, err := ed25519.Sign(signingRoomUuid, secretShare, message, derivationPath)
if err != nil {
    panic(err)
}
messageHex, err := hex.DecodeString(req.Msg)
isValid := ed25519.Verify(derivedPubKey[:], messageHex, signature[:])

Full library documentation can be found here.