Skip to main content

Quick Start

info

If you don't have have access to the docker image or don't have access to the android sdk repository, please contact us at info@silencelaboratories.com.

Here is how you can setup the Mobile SDK and perform MPC operations in less than a minute.

MPC Communication between Duo Server and Mobile App

You will create a working MPC two-party setup, where the first party, an Android application interacts with Duo Server as a second party. We are providing a demo server endpoint for your convenience.

Prerequisites

info

For quick testing, the demo server is already deployed at demo-server.silencelaboratories.com. We are using this server for the quickstart guide.

The Cloud Verifying Key for the demo server is 01829099948d222f9731323900bc42bd3cc6f4e692f920705a9959b3e52378f188.

The Cloud Node Endpoint for the demo server is demo-server.silencelaboratories.com.

Setup the Mobile SDK (Android)

Create a new Android Studio project by following the official guide or use existing one.

Dependency Installation

Step 1: Create access token for the repository
  • Get access to the repository https://github.com/silence-laboratories/silentshard-artifacts from the Silence Laboratories team.
  • Create a personal access token at https://github.com/settings/tokens with the following scopes checked[✓] to access the repository, and it's associated GithubPackages through gradle.
    • Under repo -> [✓] public_repo
    • Under write:packages -> [✓] read:packages
      We can leave write:packages untouched/unchecked as we only need read access
    • It will end up looking like this : Two items checked everything else unchecked. See below. github_pat_checked_scopes
Step 2: Configure settings.gradle.kts
  • Add the silentshard maven repo with the access credentials under the dependencyResolutionManagement -> repositories.
  dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url =
uri("https://maven.pkg.github.com/silence-laboratories/silentshard-artifacts")
credentials {
// In production app we store private credentials outside of the files added to git.
username = "github_username"
password = "token_we_just_created"
}
}
//Other private repos or other resolution configuration. Or maybe another maven repo.
}
}
Step 3: Configure app level build.gradle.kts
  • Add the silentshard dependency in your dependencies block
  dependencies {
implementation("com.silencelaboratories.silentshard:duo-android:1.0.0")
//Your Other dependencies
//..
//..
}
Step 4: Gradle-Sync
  • Sync gradle with project files.

Session Creation

Instantiate DuoSession object, which could be used to perform all of the supported MPC operations such as keygen, sign etc.

MainActivity.kt
import com.silencelaboratories.silentshard.duo.SilentShard
import com.silencelaboratories.silentshard.duo.DuoSession
import com.silencelaboratories.network.websocket.WebsocketConfig

object Constants {
//Replace with your own
const val CLOUD_NODE_URI = "demo-server.silencelaboratories.com"
//Replace with your own
//const val PORT = "8080"
}

//Other party verifying-key/public-key. Replace with your own Verifying Key.
val cloudPublicKey = "01829099948d222f9731323900bc42bd3cc6f4e692f920705a9959b3e52378f188"

//Create websocketConfig to let SilentShard use default WebsocketClient.
val websocketConfig = SilentShard.buildWebsocketConfig {
withBaseUrl(CLOUD_NODE_URI)
//usingPort(PORT)
isSecureProtocol(false)
}

//Create storageClient instance to manage keyshare states
val storageClient = object : StorageClient<ReconcileStoreDao> {
/**
* Representing in-memory database. In real world it should be some SQL based DB or
* secure storage or custom hardware. It's up to the implementation app's use-case.
* */
private val keyshareDaoSet = mutableSetOf<ReconcileStoreDao>()

override suspend fun write(
dao: ReconcileStoreDao,
) {
if (!keyshareDaoSet.add(dao)) {
keyshareDaoSet.remove(dao)
keyshareDaoSet.add(dao)
}
}

override suspend fun read(
key: String,
): ReconcileStoreDao? {
return keyshareDaoSet.find { it.keyId == key }
}
}

//Create duoSession for EDSA algorithm
val duoSession: DuoSession = SilentShard.ECDSA.createDuoSession(
cloudPublicKey, websocketConfig, storageClient
)
//or for EdDSA algorithm
//val duoSession: DuoSession = SilentShard.EdDSA.createDuoSession(
// cloudPublicKey, websocketConfig, storageClient
//)

Run the MPC operations

After creating the session, you can perform MPC operations.

Key Generation

Generate MPC keyshares and return the client keyshare with keygen method.

// call this from a Dispatcher.IO coroutine
val result = duoSession.keygen()
result.onSuccess { keyshare ->
// Use the generated keyshare : ByteArray
Log.d("MPC", "Keyshare size: ${keyshare.size}")
}.onFailure { error ->
// Handle the error
Log.e("MPC", "Key generation failed: ${error.message}")
}

Signature Generation

Sign a message using the signature method.

val messageHash = "53c48e76b32d4fb862249a81f0fc95da2d3b16bf53771cc03fd512ef5d4e6ed9"
// call this from a Dispatcher.IO coroutine
val result = duoSession.signature(
keyshare = keyshare,
message = messageHash,
derivationPath = "m" // This is the default, use your desired path here. For e.g 'm/1/2'
)
result.onSuccess { signature ->
// Use the generated signature
Log.d("MPC", "Signature: ${signature.toHexString()}")
}.onFailure { error ->
// Handle the error
Log.e("MPC", "Signing failed: ${error.message}")
}

Complete Code Example

MainActivity.kt
package com.silencelaboratories.silentshardduoquickstart

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import com.silencelaboratories.silentshard.duo.SilentShard
import com.silencelaboratories.silentshard.duo.DuoSession
import com.silencelaboratories.storage.StorageClient
import com.silencelaboratories.network.websocket.WebsocketConfig
import com.silencelaboratories.storage.silentshard.ReconcileStoreDao

class MainActivity : ComponentActivity() {

object Constants {
//Replace with your own
const val CLOUD_NODE_URI = "demo-server.silencelaboratories.com"
//Replace with your own
//const val PORT = "8080"
}

//Other party verifying-key/public-key. Replace with your own Verifying Key.
val cloudPublicKey = "01829099948d222f9731323900bc42bd3cc6f4e692f920705a9959b3e52378f188"

//Create websocketConfig to let SilentShard use default WebsocketClient.
val websocketConfig = SilentShard.buildWebsocketConfig {
withBaseUrl(CLOUD_NODE_URI)
//usingPort(PORT)
isSecureProtocol(false)
}

//Create storageClient instance to manage keyshare states
val storageClient = object : StorageClient<ReconcileStoreDao> {
/**
* Representing in-memory database. In real world it should be some SQL based DB or
* secure storage or custom hardware. It's up to the implementation app's use-case.
* */
private val keyshareDaoSet = mutableSetOf<ReconcileStoreDao>()

override suspend fun write(
dao: ReconcileStoreDao,
) {
if (!keyshareDaoSet.add(dao)) {
keyshareDaoSet.remove(dao)
keyshareDaoSet.add(dao)
}
}

override suspend fun read(
key: String,
): ReconcileStoreDao? {
return keyshareDaoSet.find { it.keyId == key }
}
}

//Create duoSession for EDSA algorithm
val duoSession: DuoSession = SilentShard.ECDSA.createDuoSession(
cloudPublicKey, websocketConfig, storageClient
)
//or for EdDSA algorithm
//val duoSession: DuoSession = SilentShard.EdDSA.createDuoSession(
// cloudPublicKey, websocketConfig, storageClient
//)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {

val messageHash = "53c48e76b32d4fb862249a81f0fc95da2d3b16bf53771cc03fd512ef5d4e6ed9"
// call this from a Dispatcher.IO coroutine

LaunchedEffect(Unit) {
lifecycleScope.launch {
val result = duoSession.keygen()
result.onSuccess { keyshare ->
// Use the generated keyshare; Here using for signing
Log.d("MPC", "Keyshare size: ${keyshare.size}")

// call this from a Dispatcher.IO coroutine
val result = duoSession.signature(
keyshare = keyshare,
message = messageHash,
derivationPath = "m" // This is the default, use your desired path here. For e.g 'm/1/2'
)
result.onSuccess { signature ->
// Use the generated signature
Log.d("MPC", "Signature: ${signature.toHexString()}")
}.onFailure { error ->
// Handle the error
Log.e("MPC", "Signing failed: ${error.message}")
}

}.onFailure { error ->
// Handle the error˛
Log.e("MPC", "Key generation failed: ${error.message}")
}
}
}
}
}
}

Once the app launches, check your logcat logs to see the key generation and signing process updates in real-time.

Optional: Run your own Duo Server

Install Docker

curl -fsSL https://get.docker.com | sh

Install OpenSSL

# Ubuntu/Debian
sudo apt-get update && sudo apt-get install openssl

Login to Docker

  • Login to docker using Personal Access Token(PAT) and your username.
Guide to generate GitHub Personal Access Token
  1. Go to GitHub Personal Access Tokens
  2. Click on "Generate new token", we are using the classic token for this guide.
  3. Enter a name for the token
  4. Select the "read::packages Download packages from GitHub Package Registry" scope.
  5. Generate the token.
info

Learn more about GitHub Personal Access Tokens here.

echo <PAT> | docker login ghcr.io -u <username> --password-stdin

Start the Server

For local setup, let's use this example docker-compose.yml

docker-compose.yml
services:
duo-server:
image: ghcr.io/silence-laboratories/dkls23-rs/duo-server:v4
command: /usr/local/bin/sigpair-node
environment:
RUST_LOG: info
LISTEN: 0.0.0.0:8080
DEV_MASTER_SIGN_KEY: /run/secrets/duo-signing-master-key
FILE_STORAGE_URL: file:///data/
ports:
- 8080:8080
volumes:
- duo-server-data:/data
secrets:
- duo-signing-master-key

volumes:
duo-server-data:

secrets:
duo-signing-master-key:
file: ${MESSAGE_SIGNING_KEY:-./party_0_sk}

The docker compose file is configured to use the party_0_sk file in the root of the project. party_0_sk is the random bytes used as seed to create the server private key.

Create the party_0_sk file with random bytes in the root of the project.

openssl rand 32 > party_0_sk

Now let's start the server by running

shell
docker compose up

If everything was setup correctly, the server should be running with these logs

Attaching to duo-server-1
duo-server-1 | WARN: The dotenv file is not set
duo-server-1 | 2025-09-16T09:59:09.200234Z INFO sigpair_node: Creating SimpleStorage
duo-server-1 | 2025-09-16T09:59:09.201975Z INFO sigpair_node: Party VK <YOUR_CLOUD_VERIFYING_KEY_HEX_STRING>
duo-server-1 | 2025-09-16T09:59:09.202413Z INFO sigpair_node: listening on 0.0.0.0:8080

Great! The server is now setup to perform MPC actions with our mobile.

In logs you will notice a hex string of length 66 (33 bytes). This is the YOUR_CLOUD_VERIFYING_KEY_HEX_STRING which will be used by server to verify the requests from other party (In this case, our mobile app).

The YOUR_CLOUD_NODE_ENDPOINT is the endpoint of the server. In this case, it is 0.0.0.0:8080 for ios and 10.0.2.2:8080 for android. Also you can use the IP address of your machine if you are running the server on your machine.

Second argument of the CloudWebSocketClient constructor is a boolean indicates if the connection is WSS or WS. Use true for secure connection, otherwise use false for local server.