Sign
Signing produces a threshold signature over a message hash using the device's keyshare and the server's keyshare. The full private key is never reconstructed — each party contributes its share to the MPC protocol and the resulting signature is valid on-chain.
For the SDK contract see Sign (Kotlin) and Sign (Swift).
How the example does it
- Android
- iOS / macOS
suspend fun sign(keyId: String, message: ByteArray, derivationPath: String): ByteArray {
val dao = readDao(keyId)
val keyshare = dao.currentKeyshare
?: throw Exception("No active keyshare found for keyId: $keyId")
val signature = sessionFor(dao.keyType)
.signature(keyshare = keyshare, message = message.toHex(), derivationPath = derivationPath)
.getOrThrow()
return signature
}
func sign(keyId: String, message: Data, derivationPath: String) async throws -> Data {
let record = try loadRecord(keyId: keyId)
let keyshare = try requireActiveKeyshare(record)
let messageHex = message.map { String(format: "%02x", $0) }.joined()
let sig = try await sessionForKeyType(record.keyType)
.signature(keyshare: keyshare, message: messageHex, chainPath: derivationPath).get()
return sig
}
The example loads the keyshare from encrypted storage via readDao(keyId) / loadRecord(keyId:), which returns the storage client's VaultReconcileStoreDao / VaultReconcileRecord wrapper. The keyType on that wrapper is how the example knows which session (ECDSA or EdDSA) to dispatch to.
The message parameter is the transaction hash (hex-encoded). The derivationPath is "m" in this example (the root BIP-32 path). For child key derivation see BIP-32.