Mobile Nodes (iOS and Android)

We provide libraries for embedding an MPC node into a Mobile app.


The most common use case is a setup where the first MPC node in the TSM is embedded in the customer's mobile device, and where the remaining MPC node(s) are running on servers controlled by an organization, e.g. a bank.

In this scenario the first MPC node is in some sense multitentant, since it is embedded in many different mobile devices, e.g. the mobile devices of the bank's customers.

To create a setup where the first MPC node is multitenant, the other MPC nodes must be setup as described here but the MPC nodes will not contain the first MPC node's (MPC node 0's) public key in their configuration files. Instead, the static MPC nodes will "approve" a specific customer, by providing the public key used for authenticating the customer MPC node (MPC Node 0). This is done by invoking a special method, RegisterTenantPublicKey for each separate MPC operation that is to be done (see example below).

Mobile Library

The app running on the mobile device will embed an MPC node by using a library provided by Blockdaemon. For Android you will need the library:

and for iOS:

Replace X.Y.Z with the version you want. Usually there is no reason to go with anything but the latest available. The interface to interact with the node is the same as the TSM SDK described elsewhere.


Below we illustrate in code how to use the Sepior library from both an iOS and an Android app. The example assumes that a customer of a bank wants to create an ECDSA signature (e.g. on a blockchain transaction). The bank controls two MPC nodes (Node 1 and Node 2) and each customer controls one (Node 0).


To create an instance of a TSM client for controlling the node it needs to be configured. In this case, there are two static MPC nodes ("banknode1" and "banknode2"):

let embeddedNodeConf: String = """
    Embedded = true

    Threshold = 1

    Index = 0

    Address = "ws://"
    PublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEr5wpemuhP8VPak5CAVg+PyKCuutiJ0biEAMeblTG8bB28fqDa8Z0dNoKwNe5FNHIhVZBwdSCXKS0wgidG9XMfw=="

    Address = "ws://"
    PublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu4mngTS2pmRzdHZzd+cWmma8g2floy9vDbrH1h+prrqokyfKBmXN9vS7VIRrmAlOfiNuVMU+MUUxzF/6WXeGXw=="

    DriverName = "sqlite3"
    DataSourceName = "%@"
    EncryptorMasterPassword = "aVjMYyiLjj+TBPkm2xSPVKB7dRnSiDLNZHomvO0JDLShk9JsF0nO1eRvig4fk2X5CEGz62vsCXiPb4UAXx2t7rsTOFwN25AE"

    LagrangeCacheSize = 128
    Bip32CacheSize = 1024
    EnableInteractiveSign = true
    EnablePartialSign = true

    <string name="database_name">tsmdb</string>
    <string name="embedded_node_conf">"
    Embedded = true

    Threshold = 1

    Index = 0

    Address = \"ws://\"
    PublicKey = \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEr5wpemuhP8VPak5CAVg+PyKCuutiJ0biEAMeblTG8bB28fqDa8Z0dNoKwNe5FNHIhVZBwdSCXKS0wgidG9XMfw==\"

    Address = \"ws://\"
    PublicKey = \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu4mngTS2pmRzdHZzd+cWmma8g2floy9vDbrH1h+prrqokyfKBmXN9vS7VIRrmAlOfiNuVMU+MUUxzF/6WXeGXw==\"
    DriverName = \"sqlite3\"
    DataSourceName = \"%1$s\"
    EncryptorMasterPassword = \"aVjMYyiLjj+TBPkm2xSPVKB7dRnSiDLNZHomvO0JDLShk9JsF0nO1eRvig4fk2X5CEGz62vsCXiPb4UAXx2t7rsTOFwN25AE\"

    LagrangeCacheSize = 128
    Bip32CacheSize = 1024"</string>

Using this configuration, we can instantiate the SDK in the mobile app:

let databasePath = try! FileManager.default
            .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    let conf = String(format: embeddedNodeConf, databasePath.absoluteString)
    var err: NSError?
    let _client = TsmNewEmbeddedTenantClient(conf, &err)
    client = TsmECDSAClient(_client)
String dbPath = getDatabasePath(getString(R.string.database_name)).getAbsolutePath();
TenantClient tClient = Tsm.newEmbeddedTenantClient(getString(R.string.embedded_node_conf, dbPath));
ECDSAClient client = new ECDSAClient(client);

In order for MPC nodes to collaborate in an MPC operation, they need to agree on a unique session ID. Each MPC node has a key pair used for authenticating towards the other nodes. Since we have multiple customers in this example, the bank needs to register the public key with each server/bank node linking it to the session ID. The Go tab below shows what to do on the bank side.

let derEncodedPublicKey = client?.getTenantPublicKey()
byte[] derEncodedPublicKey = client.getTenantPublicKey()
client.RegisterTenantPublicKey(sessionID, derEncodedPublicKey)

Once the two bank nodes have registered the customer's tenant public key, we can generate a key:

let curveName = "secp256k1"
        var err: NSError?
        let keyID = client?.keygen(withSessionID: sessionID, curveName: curveName, error: &err)
String keyID = client.keygenWithSessionID(sessionId, "secp256k1");

This causes the three MPC nodes to engage in an interactive MPC protocol. When done, each node holds its own key share and the key itself has never existed in a combined form.

Before we can generate signatures using the key, we need to create pre-signatures. Again, the MPC nodes must agree on a session ID and the bank node must register the public key of the customer node with this session ID before the operation can run. Once registered, pre-signatures are created as follows:

let presigCount = 10
try client?.presigGen(withSessionID: sessionID, keyID: keyID, count: presigCount)
int presigCount = 10;
client.presigGenWithSessionID(sessionID, keyID, presigCount);

Now each node can create a partial signature, and all partial signatures can be combined into the actual signature. Below, we illustrate how a mobile app can create a partial signature and combine it with the partial signature generated by the bank node(s) in order to obtain an actual signature. For some use cases it would make more sense to have the bank combine the partial signatures.

let data = "message to be signed".data(using: .utf8)
let hash = SHA256.hash(data: data!).data
let chainPath: TsmChainPath = TsmNewChainPath(4)!
try chainPath.set(0, value: 1)
try chainPath.set(1, value: 2)
try chainPath.set(2, value: 3)
try chainPath.set(3, value: 4)
let localPartialSignature = try client?.partialSign(withPresig: keyID, presigID: "", chainPath: chainPath, messageHash: hash)
let remotePartialSignature = try backend.partialSignWithPresig(keyID: keyID, presigID: localPartialSignature!.presigID, chainPath: chainPath, message: hash)
let combinedPartialSignature = try client?.combine(localPartialSignature?.partialSignature, partialSignatureB: remotePartialSignature.partialSignature)
let signature = try client?.finalize(combinedPartialSignature)
let publicKey = try client?.publicKey(keyID, chainPath: chainPath)
try client?.verify(publicKey, messageHash: hash, derSignature: signature?.signature)
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest("message to be signed".getBytes(StandardCharsets.UTF_8));

ChainPath chainPath = new ChainPath(4)
chainPath.set(0, 6);
chainPath.set(1, 2);
chainPath.set(2, 2);
chainPath.set(3, 0);

PartialSignatureWithPublicKeyWithPresigID psWPub = client.partialSignWithPresig(keyID, "", chainPath, hash);

byte[] combined = client.combine(psWPub.getPartialSignature(), partialSignatureFromBank);
byte[] signature = client.finalize(combined).getSignature();
client.verify(mPartialSignature.getPublicKey(), hash, signature);

A Complete Example

On request we can give you access to complete demo source code examples showing how to build mobile applications for Android and iOS using our TSM SDK.

What’s Next