Sign
Produce a signature over a message hash. The user's device and the first cloud node run a distributed signing protocol, each contributing a partial computation from its own share alone, to jointly produce 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
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';
// The full message to sign. The SDK digests it with the given hashAlgo before signing.
// For an EIP-1559 transaction this would be the RLP-encoded transaction bytes (hex).
const message =
'02ef0182012c843b9aca00850165a0bc008252089470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0';
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);
};
// Sign with hash: pass the full message and the hash algorithm; the SDK digests it before signing.
export const signWithHash = async (session: EcdsaSession) => {
const keyshare = await session.keygen();
const signConfig = {
keyshare,
message,
hashAlgo: 'KECCAK256',
} as const;
const signature = await session.signWithHash(signConfig);
console.log('Signature (with hash):', signature);
};
// Sign with hash & custom tags: attach a TLV stream read by the policy engine.
// Tag slot 0 carries the transaction type.
export const signWithHashAndTag = async (session: EcdsaSession) => {
const keyshare = await session.keygen();
const signConfig = {
keyshare,
message,
hashAlgo: 'KECCAK256',
customTags: {
0: 'eip1559',
},
} as const;
const signature = await session.signWithHash(signConfig);
console.log('Signature (with hash & custom tag):', 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);
};
// Sign with custom tags: attach a TLV stream read by the policy engine.
// Tag slot 0 carries the transaction type.
export const signWithCustomTags = 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 customTags = {
0: 'solanaTransaction',
};
const signature = await session.sign({
keyshare,
messageHash,
customTags,
});
console.log('Signature with custom tags:', 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.
Signing a full message
sign() sends a 32-byte hash, so the server only ever sees that hash — it cannot tell what the message actually says.
signWithHash — EcdsaSignWithHashConfig
Available on EcdsaSession only. Instead of messageHash, pass:
message— the full message to sign, as a hex string. For an EIP-1559 transaction, this is the RLP-encoded unsigned transaction.hashAlgo— one ofKECCAK256,SHA256,SHA256DorHASH32. The SDK hashesmessagewith it before signing, so the signature matches whatsign()would have produced over that hash.
Because the server receives the whole message, it can apply transaction policies to it. EdDSA sessions do not have signWithHash.
customTags — SignCustomTags
Optional on every algorithm, with either sign() or signWithHash().
- Keys are tag slots — integers from
0to63. - Each value is a non-empty UTF-8 string (up to 65536 bytes), or an array of strings to put several values in one slot.
- Tags are encoded as a TLV stream into the signing setup message and delivered to your backend, which can act on them in its DSG hook.
What each slot means is defined by your backend, not by the SDK.
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 TrioSession if you haven't already.
Step 2 : Perform Sign
- Call trioSession.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, trioSession: TrioSession): ByteArray {
return withContext(Dispatchers.IO) {
trioSession.sign(
keyId = keyId,
message = messageHash,
derivationPath = "m" // This is the default; use your desired path, e.g. "m/1/2"
).getOrThrow()
}
}
keyIdis the identifier returned by keygen that addresses the client's keyshare in your storage.messageHashis the hash of the message to be signed asByteArray.- trioSession.sign() performs message exchange between mobile and server to generate a ECDSA/EdDSA signature.
- Result of trioSession.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 TrioSession if you haven't already.
Step 2 : Perform Sign
- Call trioSession.sign() which returns
ResultofSuccesswithSignaturebytes asDataor Failure witherror.
Example
let MESSAGE_HASH = "53c48e76b32d4fb862249a81f0fc95da2d3b16bf53771cc03fd512ef5d4e6ed9"
// Sign the message hash with the key addressed by keyId (from keygen/import).
func performSignature(keyId: String, trioSession: TrioSession) async -> Data? {
let result = await trioSession.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
}
}
keyIdidentifies the client's key (fromkeygen/import); the SDK loads the keyshare from your StorageClient using it.messageHashis the hash of the message to be signed as a hexString.- trioSession.sign() performs message exchange between mobile and server to generate a ECDSA/EdDSA signature.
- Result of trioSession.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 Trio nodes 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 | A server ended the session unexpectedly | Show the error and offer a retry |
| Connection / transport error | A 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.