SolanaSolana Pre-Alpha is live! dWallets now support Solana for native cross-chain signing.
Ika LogoIka Docs

User Share Encryption Keys

The UserShareEncryptionKeys class is a core component for managing cryptographic keys in the Ika network. It handles the creation and management of encryption/decryption keys and signing keypairs needed for secure user share operations. You pass it to IkaTransaction to perform user share operations.

Overview

In the Ika network, users need to securely manage their secret shares while maintaining the ability to prove ownership and authorization. The UserShareEncryptionKeys class provides a unified interface for:

  1. Encrypting secret shares - Protecting sensitive cryptographic material
  2. Proving ownership - Creating signatures to demonstrate control over keys
  3. Authorizing operations - Signing dWallet public outputs for various operations
  4. Key management - Deriving, storing, and retrieving cryptographic keys

Security Reminder

UserShareEncryptionKeys handles extremely sensitive cryptographic material. Always follow security best practices, conduct security reviews, and consider getting security audits for production applications.

Supported Curves

UserShareEncryptionKeys supports the following elliptic curves:

  • Curve.SECP256K1 - Used by ECDSASecp256k1 and Taproot signature algorithms
  • Curve.SECP256R1 - Used by ECDSASecp256r1 signature algorithm
  • Curve.ED25519 - Used by EdDSA signature algorithm
  • Curve.RISTRETTO - Used by SchnorrkelSubstrate signature algorithm

Curve Matching Required

You must create UserShareEncryptionKeys BEFORE creating a dWallet, and the curve you choose MUST match the curve you'll use when creating the dWallet.

The workflow is:

  1. Choose a curve based on the signature algorithm you want to use
  2. Create UserShareEncryptionKeys with that curve
  3. Create your dWallet with the same curve/signature algorithm
  4. Use the UserShareEncryptionKeys for all operations with that dWallet

For example:

  • To use ECDSASecp256k1 or Taproot, create keys with Curve.SECP256K1
  • To use EdDSA, create keys with Curve.ED25519
  • To use ECDSASecp256r1, create keys with Curve.SECP256R1
  • To use SchnorrkelSubstrate, create keys with Curve.RISTRETTO

Using mismatched curves will cause all operations to fail.

Creating UserShareEncryptionKeys

There are several ways to create a UserShareEncryptionKeys instance depending on your use case.

Creating New Keys

Use fromRootSeedKeyV2 for all new encryption keys. It deterministically derives keys from a root seed using the current curve-byte hash and the new platform-independent sampler, with no legacy options:

import { Curve, UserShareEncryptionKeys } from '@ika.xyz/sdk';
 
// Generate a random 32-byte seed (in practice, derive this securely)
const rootSeedKey = new Uint8Array(32);
crypto.getRandomValues(rootSeedKey);
 
// Create UserShareEncryptionKeys from the seed
// IMPORTANT: Use the curve that matches your dWallet's curve
const userShareKeys = await UserShareEncryptionKeys.fromRootSeedKeyV2(rootSeedKey, Curve.SECP256K1);
 
console.log('Sui address:', userShareKeys.getSuiAddress());

Examples with different curves:

// For ECDSASecp256k1 or Taproot signature algorithms
const secp256k1Keys = await UserShareEncryptionKeys.fromRootSeedKeyV2(seed, Curve.SECP256K1);
 
// For EdDSA signature algorithm
const ed25519Keys = await UserShareEncryptionKeys.fromRootSeedKeyV2(seed, Curve.ED25519);
 
// For ECDSASecp256r1 signature algorithm
const secp256r1Keys = await UserShareEncryptionKeys.fromRootSeedKeyV2(seed, Curve.SECP256R1);
 
// For SchnorrkelSubstrate signature algorithm
const ristrettoKeys = await UserShareEncryptionKeys.fromRootSeedKeyV2(seed, Curve.RISTRETTO);

Recovering Existing Keys

fromRootSeedKey is deprecated and is only for recovering existing encryption keys. It always uses the deployed WASM32 sampler, in both browsers and Node.js. Use the original root seed and curve:

const existingKeys = await UserShareEncryptionKeys.fromRootSeedKey(savedRootSeed, curve);
 
// Keys created before the curve-byte hash fix also need the historical hash.
const oldHashKeys = await UserShareEncryptionKeys.fromRootSeedKey(savedRootSeed, curve, {
	legacyHash: true,
});

legacyHash defaults to false. Select it only when the original keys used the old hash, which always encoded the curve byte as 0. fromRootSeedKeyLegacyHash remains a deprecated recovery helper using the old hash and WASM32 sampling.

If you saved serialized keys, restore them with fromShareEncryptionKeysBytes instead of deriving them again. Serialization stores the actual encryption/decryption key bytes, so restoration does not depend on the sampler or the platform that created them. The BCS V1/V2 distinction records the hash version, not the sampler. For seed-only backups, record whether you used fromRootSeedKey or fromRootSeedKeyV2, the curve, and whether legacyHash was enabled.

The native Rust legacy API reproduces historical 64-bit keys; the TypeScript SDK always uses WASM and reproduces historical 32-bit keys. To move existing native keys into the SDK, restore a complete SDK-format key bundle rather than deriving them with fromRootSeedKey. This bundle must include the encryption and decryption keys, Ed25519 signing secret, curve, and hash-version envelope. Raw class-groups keypair bytes returned by the Rust helper cannot be passed directly to fromShareEncryptionKeysBytes.

Rotating Existing Encryption Keys

Existing encryption keys can be rotated by creating new keys and re-encrypting the user shares:

  1. Recover the existing keys with fromRootSeedKey and the original derivation options, or restore their serialized bytes. Keep them available while migrating the shares.
  2. Generate a fresh root seed and call fromRootSeedKeyV2 with the dWallet's curve. Register the new keys with IkaTransaction.registerEncryptionKey and wait until they are active. A fresh seed also gives the new keys a new signing address; changing only the sampler keeps the old signing address, which already has a registered encryption key.
  3. Use an IkaTransaction configured with the existing keys to call requestReEncryptUserShareFor, passing the existing encrypted share and newKeys.getSuiAddress() as destinationEncryptionKeyAddress.
  4. Wait for network verification, then accept the new encrypted share with acceptEncryptedUserShare using the new keys. Verify that you can decrypt it, and save the new keys and encrypted-share ID before switching subsequent operations to them. Repeat for every share protected by the old encryption keys.

For the re-encryption and acceptance transaction details, see Transferring a dWallet Share and Receiving a dWallet Share. Re-encryption creates a new encrypted copy of the same user share; it does not revoke access to existing copies.

Choosing the Right Curve

Choose your curve based on the signature algorithm you intend to use: - SECP256K1: Best for Ethereum, Bitcoin (ECDSA), and general ECDSA use cases - ED25519: Best for high-performance EdDSA signatures - SECP256R1: For NIST P-256 compliance requirements - RISTRETTO: For Substrate/Polkadot ecosystem compatibility

From Serialized Bytes

If you have previously serialized keys, you can restore them:

// Restore from previously serialized bytes
const serializedBytes: Uint8Array = loadKeysFromStorage(); // Your storage logic
const userShareKeys = UserShareEncryptionKeys.fromShareEncryptionKeysBytes(serializedBytes);

Serializing Keys for Storage

You can serialize keys for persistent storage:

const userShareKeys = await UserShareEncryptionKeys.fromRootSeedKeyV2(rootSeedKey, Curve.SECP256K1);
 
// Serialize keys to bytes for storage
const serializedBytes = userShareKeys.toShareEncryptionKeysBytes();
 
// Store securely (example - implement your own secure storage)
await secureStorage.store('user-share-keys', serializedBytes);

Security Warning

Always store serialized keys securely. The serialized data contains sensitive cryptographic material including private keys. Use appropriate encryption and access controls for storage.

Key Methods and Operations

Getting Key Information

Access basic information about your keys:

const userShareKeys = await UserShareEncryptionKeys.fromRootSeedKeyV2(rootSeedKey, Curve.SECP256K1);
 
// Get the Ed25519 public key
const publicKey = userShareKeys.getPublicKey();
 
// Get the Sui address derived from the signing keypair
const suiAddress = userShareKeys.getSuiAddress();
console.log('Address:', suiAddress);
 
// Get raw public key bytes for lower-level operations
const publicKeyBytes = userShareKeys.getSigningPublicKeyBytes();

Signature Operations

Verifying Signatures

Verify signatures over messages using the public key:

const message = new TextEncoder().encode('Hello, Ika!');
const signature: Uint8Array = getSignatureFromSomewhere(); // Your signature source
 
const isValid = await userShareKeys.verifySignature(message, signature);
console.log('Signature valid:', isValid);

Creating Encryption Key Signatures

Create a signature over your own encryption key to prove ownership:

// Sign your own encryption key to prove ownership
const encryptionKeySignature = await userShareKeys.getEncryptionKeySignature();
 
// This signature can be used to prove you control this encryption key

dWallet Authorization Signatures

For Newly Created dWallets

When you participate in dWallet creation, you need to sign the public output to authorize its use:

import { IkaClient } from '@ika.xyz/sdk';
 
// Assume you have a dWallet in the awaiting key holder signature state
const dWallet = await ikaClient.getdWallet(dWalletId);
const userPublicOutput: Uint8Array = getUserDKGOutput(); // From your DKG participation
 
try {
	const signature = await userShareKeys.getUserOutputSignature(dWallet, userPublicOutput);
	console.log('Authorization signature created successfully');
 
	// Use this signature in your dWallet activation transaction
} catch (error) {
	if (error.message.includes('not in awaiting key holder signature state')) {
		console.error('dWallet is not ready for signature');
	} else if (error.message.includes('User public output does not match')) {
		console.error('Public output mismatch - check your DKG participation');
	}
}

For Transferred dWallets

When receiving a transferred dWallet, you need to verify the sender and create your authorization signature:

// When receiving a transferred dWallet
const dWallet = await ikaClient.getdWallet(transferreddWalletId);
const sourceEncryptedShare = await ikaClient.getEncryptedUserSecretKeyShare(shareId);
const sourceEncryptionKey = await ikaClient.getActiveEncryptionKey(senderAddress);
 
try {
	const signature = await userShareKeys.getUserOutputSignatureForTransferredDWallet(
		dWallet,
		sourceEncryptedShare,
		sourceEncryptionKey,
	);
 
	console.log('Transfer authorization signature created');
} catch (error) {
	console.error('Failed to create transfer signature:', error.message);
}

Security Warning

When handling transferred dWallets, always verify that sourceEncryptionKey belongs to the expected sender. Don't fetch this from the network without proper verification - the sender's public key should be known to you through secure channels.

Decrypting User Shares

The most critical operation is decrypting your encrypted user secret key shares:

// Decrypt a user share for a specific dWallet
const dWallet = await ikaClient.getdWallet(dWalletId);
const encryptedUserShare = await ikaClient.getEncryptedUserSecretKeyShare(shareId);
 
// Get protocol parameters for the dWallet's encryption key
const protocolParameters = await ikaClient.getProtocolPublicParameters(dWallet);
 
try {
	// IMPORTANT: userShareKeys must have been created with the same curve as the dWallet
	// For dWallets you created: this is automatic since you created the keys first
	// For transferred dWallets: you must check the dWallet's curve and create matching keys
	const { verifiedPublicOutput, secretShare } = await userShareKeys.decryptUserShare(
		dWallet,
		encryptedUserShare,
		protocolParameters,
	);
 
	console.log('Successfully decrypted user share');
	console.log('Verified public output length:', verifiedPublicOutput.length);
	console.log('Secret share length:', secretShare.length);
 
	// Use the decrypted secret share for signing operations
	// IMPORTANT: Handle secretShare securely - it contains sensitive cryptographic material
} catch (error) {
	if (error.message.includes('dWallet is not active')) {
		console.error('Cannot decrypt share - dWallet is not in active state');
	} else if (error.message.includes('verification fails')) {
		console.error('Share verification failed - check encryption key and dWallet state');
	} else {
		console.error('Decryption failed:', error.message);
	}
}

Curve Matching Critical

The UserShareEncryptionKeys instance MUST have been created with the same curve as the dWallet.

For dWallets you created: You already created the UserShareEncryptionKeys first with a specific curve, then used it to create the dWallet - so they match by design.

For transferred/existing dWallets: You must check the dWallet's curve and create UserShareEncryptionKeys with the matching curve (see "For Existing or Transferred dWallets" section above).

If the curves don't match, all operations including decryption will fail.

Decryption Process

The decryptUserShare method performs several security checks:

  1. Verifies the dWallet state - Ensures the dWallet is active and has valid public output
  2. Validates the encrypted share - Checks the encrypted share signature against your public key
  3. Decrypts the share - Uses your decryption key to recover the secret share
  4. Verifies consistency - Ensures the decrypted share matches the dWallet's public output

This multi-layer verification ensures the integrity and authenticity of your secret shares.