Sign
Produce a signature over a message hash. The device and the Duo Server each sign with their own share, and the results combine into a single, standard signature. The full private key is never reassembled.
Please refer to the Session creation section to learn how to create a new session.
Full example
- ECDSA
- EdDSA
- MLDSA
import { type EcdsaSession } from '@silencelaboratories/silent-shard-sdk/ecdsa';
// This could be a hash digest of any message you want to sign.
// For example, for the Ethereum transaction signing, you would use the keccak256 hash of the transaction data.
const messageHash = 'e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03';
export const signGen = async (session: EcdsaSession) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
const signConfig = {
keyshare,
messageHash,
};
const signature = await session.sign(signConfig);
console.log('Signature:', signature);
};
import { type EddsaSession } from '@silencelaboratories/silent-shard-sdk/eddsa';
const messageHash = 'e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03';
export const signGen = async (session: EddsaSession) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
const signConfig = {
keyshare,
messageHash,
};
const signature = await session.sign(signConfig);
console.log('Signature:', signature);
};
import { MldsaLevels } from '@silencelaboratories/silent-shard-sdk';
import { type MldsaSession } from '@silencelaboratories/silent-shard-sdk/mldsa';
const messageHash = 'e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03';
export const signGen = async (session: MldsaSession) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
const signConfig = {
keyshare,
messageHash,
// ML-DSA security level (MlDsa44, MlDsa65, or MlDsa87)
level: MldsaLevels.MlDsa44,
};
const signature = await session.sign(signConfig);
console.log('Signature:', signature);
};
- The
signmethod takes a EcdsaSignConfig object as an argument. keyshare(Keyshare) is the client's "share" of the MPC wallet.messageHashis the hash of the message to be signed as a hex string.- When
session.sign()is called, the app and the server exchange messages to generate an ECDSA signature. signatureis the ECDSA signature (hex string) ofmessageHash, corresponding to the public key (or address) of the wallet.
Please refer to the Session creation section to learn how to create a new session.
Full example
- ECDSA
- EdDSA
- Taproot
import 'dart:typed_data';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
// This could be a hash digest of any message you want to sign.
// For example, for the Ethereum transaction signing, you would use the keccak256 hash of the transaction data.
const messageHash =
'e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03';
Future<Uint8List> signGen(sdk.EcdsaSession session) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.DklsKeyshare keyshare = await session.keygen();
print('Keyshare created, public key: ${keyshare.publicKeyHex}');
final signature = await session.sign(
keyId: keyshare.keyId,
messageHash: messageHash,
);
print('Signature: $signature');
return signature;
}
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
// This could be a hash digest of any message you want to sign.
// For example, for the Ethereum transaction signing, you would use the keccak256 hash of the transaction data.
const messageHash =
'e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03';
Future<void> signGen(sdk.EddsaSession session) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.SchnorrKeyshare keyshare = await session.keygen();
print('Keyshare created, public key: ${keyshare.publicKeyHex}');
final signature = await session.sign(
keyId: keyshare.keyId,
messageHash: messageHash,
);
print('Signature: $signature');
}
import 'dart:typed_data';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
// This could be a hash digest of any message you want to sign.
// For Bitcoin Taproot transactions, this is the sighash as defined in BIP 341.
const messageHash =
'5ae9337fe6b54559bf2c16aea4472f8f8cfab3cfa6844547801fb8c752151552';
Future<Uint8List> signGen(sdk.TaprootSession session) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.TaprootKeyshare keyshare = await session.keygen();
print('Keyshare created, public key: ${keyshare.publicKeyHex}');
final signature = await session.sign(
keyId: keyshare.keyId,
messageHash: messageHash,
);
print('Signature: $signature');
return signature;
}
- DklsKeyshare is the client's "share" of the MPC wallet.
messageHashis the hash of the message to be signed as a hex string.- When
session.sign()is called, the app and the server exchange messages to generate an ECDSA signature. signatureis the signature (hex string) ofmessageHash, corresponding to the public key (or address) of the wallet.
This distributed signing process allows for secure transaction authorization while preserving the key's distributed nature, exemplifying the MPC wallet's enhanced security model.
Step 1 : Create Session
- Create DuoSession if you haven't already.
Step 2 : Perform Sign
- Call duoSession.sign() which returns Result of Success with Signature ByteArray or Failure with exception.
Example
val messageHash = "e2a159d17b7bb714aed7675d7c7d394dec8d2e4337842848104694bf89c71c03"
// Sign the message hash with the key addressed by keyId (returned from keygen/import).
suspend fun performSignature(keyId: String, duoSession: DuoSession): ByteArray {
return withContext(Dispatchers.IO) {
duoSession.sign(
keyId = keyId,
message = messageHash,
derivationPath = "m" // This is the default; use your desired path, e.g. "m/1/2"
).getOrThrow()
}
}
keyIdaddresses the keyshare to sign with (returned by keygen/import); the SDK reads the share from your StorageClient.messageHashis the hash of the message to be signed as a hexString.- duoSession.sign() performs message exchange between mobile and server to generate a ECDSA/EdDSA signature.
- Result of duoSession.sign() could be a
SuccesswithByteArray(ECDSA/EdDSA signature) ofmessageHash, corresponding to the public key (or address) of the wallet orFailurewithException.
This distributed signing process allows for secure transaction authorization while preserving the key's distributed nature, exemplifying the MPC wallet's enhanced security model.
Step 1 : Create Session
- Create DuoSession if you haven't already.
Step 2 : Perform Sign
- Call duoSession.sign() with the
keyIdof the keyshare to sign with. It returns aResultofSuccesswith theSignaturebytes asDataorFailurewitherror.
Example
let MESSAGE_HASH = "53c48e76b32d4fb862249a81f0fc95da2d3b16bf53771cc03fd512ef5d4e6ed9"
// Sign the message hash with the key addressed by keyId (from keygen/import).
func performSignature(keyId: String, duoSession: DuoSession) async -> Data? {
let result = await duoSession.sign(
keyId: keyId, message: MESSAGE_HASH,
derivationPath: "m" // This is the default; use your desired path, e.g. "m/1/2"
)
// returns nil if the operation fails, or handle it however your flow needs
switch result {
case .success(let signatureBytes):
// do something with the signature bytes
Swift.print(signatureBytes)
return signatureBytes
case .failure(let error):
// show the error to the user or abort the process
Swift.print(error)
return nil
}
}
keyIdaddresses the client's keyshare in your storage client (returned bykeygen/import).messageHashis the hash of the message to be signed, passed as a hexString.- duoSession.sign() performs message exchange between mobile and server to generate a ECDSA/EdDSA signature.
- Result of duoSession.sign() could be a
SuccesswithData(ECDSA/EdDSA signature) ofmessageHash, corresponding to the public key (or address) of the wallet orFailurewitherror.
Handling the operation
This is an MPC operation, so it takes a few seconds (the app and the Duo Server exchange several messages). Show a non-blocking loading state while it runs, confirm on success, and offer a retry on failure. When something goes wrong, the SDK surfaces the error for you to handle:
| Error | What it means | How to handle |
|---|---|---|
| Keyshares not in sync, run reconcile | A previous operation didn't finish cleanly (for example the app was force-closed mid-operation) | Run reconcile, then retry |
| Server error | The server ended the session unexpectedly | Show the error and offer a retry |
| Connection / transport error | The server was unreachable or the connection dropped | Show the error and offer a retry |
All of these operations are safe to retry from the start.