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.
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 clientsigpair-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.
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
orschnorr
).verifiable_backup
: The verifiable encrypted backup of the server's keyshare. Base64 encoded string.
{
"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.
{
"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.
{
"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="
}