Skip to main content

Duo Server

Silent Shard Duo Server is the backend service that handles the cryptographic operations and provides necessary APIs. It acts as the second party in the MPC protocol along with the mobile SDK.

The company deploying the SDK should host the server on their own infrastructure. We provide a Docker image for the server that can be deployed on any cloud provider.

Deploying the server

Let's take the example docker-compose.yml used in MPC in minutes

Our server image is up on dockerhub: ghcr.io/silence-laboratories/duo-server:v2-latest. It requires a Postgres Database for storing the keyshares.

docker-compose.yml
version: '3.1'

services:

db:
image: postgres:14
restart: always
environment:
POSTGRES_PASSWORD: sigpair
POSTGRES_USER: sigpair
POSTGRES_DB: sigpair
ports:
- 5432:5432

sigpair:
image: ghcr.io/silence-laboratories/duo-server:v2-latest
restart: always
environment:
PGHOST: db
PGUSER: sigpair
PGDATABASE: sigpair
PGPASSWORD: sigpair
ports:
- 8080:8080
depends_on:
- db

Instant testing from a cli client

Run optionally the following script after having your server up and running to test all the mpc exposed API from a local cli acting as a client to the duo server.

  • sigpair-embedded is the cli client
  • sigpair-admin is an internal cli server for the non sdk exposed functionalities:
    • backup
    • export
Save it in run-test.sh and shell run-test.sh to run it
#!/bin/sh

set -e

embedded() {
cargo run -q --bin sigpair-embedded -- "$@"
}

admin() {
cargo run -q --bin sigpair-admin -- "$@"
}

SERVER="http://localhost:8080"
mkdir -p output

echo "
██████╗ ██╗ ██╗ ██████╗
██╔══██╗██║ ██║██╔═══██╗
██║ ██║██║ ██║██║ ██║
██║ ██║██║ ██║██║ ██║
██████╔╝╚██████╔╝╚██████╔╝
╚═════╝ ╚═════╝ ╚═════╝
"

response=$(curl -s ${SERVER}/v3/verifying-key)
server_vk=$(echo "${response}" | jq -r .verifying_key)
server_vk="${server_vk:2}"
echo "server verifying key: ${server_vk}"

# # generate signing key for a mobile party
keys=$(embedded gen-sign-keys)

# Split at : and get sk and pk
user_sk=${keys%%:*}
# user_vk=${keys#*:}

echo ---------------------------------------KEYGEN--------------------------------------------

# and finally, generate a key, save keyshare to file `keys-1.share`
key_id=$(embedded key-gen \
--user-sk "${user_sk}" \
--share output/keys-1.share \
--sign-algo "ecdsa" \
--server-vk "${server_vk}" \
--server "${SERVER}")

#
echo "key-id: ${key_id}"

echo ---------------------------------------SIGN--------------------------------------------

sign=$(embedded sign-gen \
--user-sk "${user_sk}" \
--share output/keys-1.share \
--message "e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03" \
--chain-path "m" \
--sign-algo "ecdsa" \
--server-vk "${server_vk}" \
--server ${SERVER})

echo "signature: ${sign}"

echo ---------------------------------------BIP32 SIGN--------------------------------------------

sign=$(embedded sign-gen \
--user-sk "${user_sk}" \
--share output/keys-1.share \
--message "e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03" \
--chain-path "m/1/2" \
--sign-algo "ecdsa" \
--server-vk "${server_vk}" \
--server ${SERVER})

echo "signature: ${sign}"

echo ---------------------------------------KEY REFRESH--------------------------------------------

new_key_id=$(embedded key-refresh \
--user-sk "${user_sk}" \
--share output/keys-1-refreshed.share \
--old-share output/keys-1.share \
--sign-algo "ecdsa" \
--server-vk "${server_vk}" \
--server "${SERVER}")

echo "keyshare refreshed, new key-id: ${new_key_id} must be same as ${key_id}"

echo ---------------------------------------SIGN AFTER REFRESH--------------------------------------------
sign=$(embedded sign-gen \
--user-sk "${user_sk}" \
--share output/keys-1-refreshed.share \
--message "e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03" \
--chain-path "m" \
--sign-algo "ecdsa" \
--server-vk "${server_vk}" \
--server ${SERVER})

echo "signature: ${sign}"

# generate encryption keys for a mobile party
enc_keys=$(embedded gen-enc-keys)

# Split at : and get sk and pk
user_enc_sk=${enc_keys%%:*}
user_enc_pk=${enc_keys#*:}

echo ---------------------------------------KEY EXPORT--------------------------------------------
export=$(admin export-key \
--key-id "${key_id}" \
--user-enc-pk "${user_enc_pk}" \
--server ${SERVER})

# Split at : and get sk and pk
ciphertext=${export%%:*}
server_enc_pk=${export#*:}

echo "ciphertext: ${ciphertext}"
echo "server_env_pk: ${server_enc_pk}"

echo ---------------------------------------COMBINE SHARES--------------------------------------------

private_key=$(embedded export-key \
--user-enc-sk "${user_enc_sk}" \
--sign-algo "ecdsa" \
--ciphertext "${ciphertext}" \
--server-enc-pk "${server_enc_pk}" \
--share output/keys-1-refreshed.share)

echo "private-key: ${private_key}"

echo ---------------------------------------KEY IMPORT--------------------------------------------

# Generate a new key-id, URL-safe base64 encoded
new_key_id=$(openssl rand -base64 32 | tr '/+' '_-' | tr -d '=')

embedded import-key \
--user-sk "${user_sk}" \
--private-key "${private_key}" \
--key-id "${new_key_id}" \
--out-share output/import-keys-1.share \
--server-vk "${server_vk}" \
--user-enc-sk "${user_enc_sk}" \
--sign-algo "ecdsa"
echo KEY_IMPORTED

echo BACKUP

## Create a new RSA Key pair using openssl
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 2>/dev/null
recv_enc_pubkey=$(openssl rsa -in private_key.pem -RSAPublicKey_out 2>/dev/null)

backup=$(admin backup-key \
--key-id "${key_id}" \
--receiver-enc-pk "${recv_enc_pubkey}" \
--label "backup" \
--server ${SERVER})



echo "------------------------------------EdDSA KEYGEN--------------------------------------------"
key_id=$(embedded key-gen \
--user-sk "${user_sk}" \
--share output/ed-keys-1.share \
--sign-algo "eddsa" \
--server-vk "${server_vk}" \
--server "${SERVER}")

echo "EdDSA key-id: ${key_id}"

echo "------------------------------------EdDSA SIGN--------------------------------------------"
sign=$(embedded sign-gen \
--user-sk "${user_sk}" \
--share output/ed-keys-1.share \
--message "e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03" \
--chain-path "m" \
--sign-algo "eddsa" \
--server-vk "${server_vk}" \
--server ${SERVER})

echo "eddsa signature: ${sign}"


echo ---------------------------------------EdDSA KEY REFRESH--------------------------------------------

new_key_id=$(embedded key-refresh \
--user-sk "${user_sk}" \
--share output/ed-keys-1-refreshed.share \
--old-share output/ed-keys-1.share \
--sign-algo "eddsa" \
--server-vk "${server_vk}" \
--server "${SERVER}")

echo "EdDSA keyshare refreshed, new key-id: ${new_key_id} must be same as ${key_id}"

echo ---------------------------------------EdDSA KEY EXPORT--------------------------------------------
export=$(admin export-key \
--key-id "${new_key_id}" \
--user-enc-pk "${user_enc_pk}" \
--server ${SERVER})

# Split at : and get sk and pk
ciphertext=${export%%:*}
server_enc_pk=${export#*:}

echo "ciphertext: ${ciphertext}"
echo "server_env_pk: ${server_enc_pk}"

echo ---------------------------------------EdDSA COMBINE SHARES--------------------------------------------

private_key=$(embedded export-key \
--user-enc-sk "${user_enc_sk}" \
--sign-algo "eddsa" \
--ciphertext "${ciphertext}" \
--server-enc-pk "${server_enc_pk}" \
--share output/ed-keys-1-refreshed.share)

echo "EdDSA private-key: ${private_key}"

echo ---------------------------------------EdDSA KEY IMPORT--------------------------------------------
new_key_id=$(openssl rand -base64 32 | tr '/+' '_-')
echo "importing with key-id: ${new_key_id}"
embedded import-key \
--user-sk "${user_sk}" \
--private-key "${private_key}" \
--key-id "${new_key_id}" \
--out-share output/ed-import-keys-1.share \
--server-vk "${server_vk}" \
--user-enc-sk "${user_enc_sk}" \
--sign-algo "eddsa"
echo KEY_IMPORTED

echo ---------------------------------------EdDSA BACKUP--------------------------------------------

backup=$(admin backup-key \
--key-id "${new_key_id}" \
--receiver-enc-pk "${recv_enc_pubkey}" \
--label "backup" \
--server ${SERVER})

echo BACKUP_GENERATED

embedded verify-backup \
--backup "${backup}" \
--label "backup" \
--rsa-pubkey "${recv_enc_pubkey}" \
--share output/ed-import-keys-1.share \

echo BACKUP_VERIFIED

Environment variables

Database Config

  • PGHOST
  • PGUSER
  • PGPASSWORD
  • PGDATABASE

These are standard Postgres vars to connect to a database. For additional configuration you can pass in extra variables as well. Refer to the PostgreSQL documentation for a full list.


Private Endpoints

Sensitive operations for backup and export are not publicly available to the SDK. These endpoint are meant to be used by the company deploying the SDK to expose these endpoints to the user in a secure way with extra authentication logic.

tip

Example case:

  • When the user requests a backup/export, the backend can request additional authentication from the user (e.g., 2FA) before responding.
  • Once the user is authenticated, the backend can get the verifiable backup/export from the cloud node and pass it to the user.
  • /v3/backup-key: Generate a verifiable backup of the server's keyshare.
  • /v3/export-key: Export the full private key of the MPC wallet.

Verifiable Backup

  • Description: Generate a verifiable backup of the server's keyshare.
  • HTTP Method: POST
  • URL: http://{SERVER_URL}/v3/backup-key
  • Request Body:
    • key_id: The key ID of the keyshare.
    • rsa_pubkey_pem: The RSA public key in PEM format.
    • label: The label used as associated data while performing RSA encryption of the server's keyshare. The label is required while decrypting/verifying the backup.
{
"key_id": "KEY_ID",
"rsa_pubkey_pem": "RSA_PUBLIC_KEY_PEM",
"label": "BACKUP_LABEL"
}
  • Response Body:
    • key_id: The key ID of the keyshare.
    • algo: The signing algorithm of the Keyshare (ecdsa or schnorr).
    • verifiable_backup: The verifiable encrypted backup of the server's keyshare. Base64 encoded string.
Example Response Body
{
"key_id": "D4K6eBI9SIbem4mxRONtBuox+5Y0JuhyosGcbjcAi5E=",
"algo": "ecdsa",
"verifiable_backup": "HJ4SSOxGJ/TZwLyKKWAS+OO1yTF14M8JWFiw9/ncS/7qt+oLn5xR+IFBu2Qka9FyP1K5PIoUl72UWOZbJ4m/BIL4fjs04Ru+UeqQkICYl2B6qf5YySFSHtZbLAjR3pk81q+eD/PH5eVA5jwf0ntNrxg9Fy3zbaUpv7MitAaTzEQ="
}

Export Key

  • Description: Export the full private key of the MPC wallet.
  • HTTP Method: POST
  • URL: http://{SERVER_URL}/v3/export-key
  • Request Body:
    • key_id: The key ID of the keyshare.
    • client_enc_key: The encryption public key of the client. 32 byte Base64 encoded string.
Request Body
{
"key_id": "KEY_ID",
"client_enc_key": "CLIENT_ENC_KEY"
}
  • Response Body:
    • key_id: The key ID of the keyshare.
    • server_public_key: The server's encryption public key in bytes. Required for decrypting the backup.
    • enc_server_share: The encrypted server share as a Base64 encoded string.
Example Response Body
{
"key_id": "D4K6eBI9SIbem4mxRONtBuox+5Y0JuhyosGcbjcAi5E=",
"server_public_key": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
"enc_server_share": "HJ4SSOxGJ/TZwLyKKWAS+OO1yTF14M8JWFiw9/ncS/7qt+oLn5xR+IFBu2Qka9FyP1K5PIoUl72UWOZbJ4m/BIL4fjs04Ru+UeqQkICYl2B6qf5YySFSHtZbLAjR3pk81q+eD/PH5eVA5jwf0ntNrxg9Fy3zbaUpv7MitAaTzEQ="
}