StorageClient
Storage client for managing keyshare persistence and reconciliation state.
The storage client manages the lifecycle of distributed keyshares during MPC operations. It tracks two key states for each keyshare:
currentKeyshare: The fully reconciled, active keyshare ready for signing operations
stagedKeyshare: A keyshare that completed MPC computation but needs reconciliation
Keyshare Lifecycle
Generation: MPC operation creates a keyshare, stored as
stagedKeyshareReconciliation:
stagedKeysharebecomescurrentKeyshare(ready for use)Recovery: If reconciliation fails,
stagedKeysharecan be reconciled laterRefresh: New keyshare replaces old one through staged → current transition
Implementation Requirements
Your storage must be:
Secure: Encrypt sensitive data at rest
Reliable: Survive app crashes and device restarts
Atomic: Write operations should be atomic to prevent corruption
Concurrent-safe: Handle multiple simultaneous operations
Example: In-Memory Storage (Development Only)
class InMemoryStorage : StorageClient<ReconcileStoreDao> {
private val storage = mutableMapOf<String, ReconcileStoreDao>()
override suspend fun write(dao: ReconcileStoreDao) {
storage[dao.keyId] = dao
}
override suspend fun read(key: String): ReconcileStoreDao? {
return storage[key]
}
}Example: Encrypted Database Storage
class EncryptedDatabaseStorage(
private val database: MyDatabase,
private val encryptionKey: ByteArray
) : StorageClient<ReconcileStoreDao> {
override suspend fun write(dao: ReconcileStoreDao) {
val encryptedCurrent = dao.currentKeyshare?.let { encrypt(it, encryptionKey) }
val encryptedStaged = dao.stagedKeyshare?.let { encrypt(it, encryptionKey) }
database.keyshareDao().insertOrUpdate(
KeyshareEntity(
keyId = dao.keyId,
currentKeyshare = encryptedCurrent,
stagedKeyshare = encryptedStaged
)
)
}
override suspend fun read(key: String): ReconcileStoreDao? {
val entity = database.keyshareDao().getByKeyId(key) ?: return null
return ReconcileStoreDao(
currentKeyshare = entity.currentKeyshare?.let { decrypt(it, encryptionKey) },
stagedKeyshare = entity.stagedKeyshare?.let { decrypt(it, encryptionKey) },
keyId = entity.keyId
)
}
}Example: Hardware-Backed Storage
class HardwareStorage(
private val secureEnclave: SecureEnclave
) : StorageClient<ReconcileStoreDao> {
override suspend fun write(dao: ReconcileStoreDao) {
// Store metadata in regular storage
val metadata = KeyshareMetadata(
keyId = dao.keyId,
hasCurrent = dao.currentKeyshare != null,
hasStaged = dao.stagedKeyshare != null
)
saveMetadata(metadata)
// Store sensitive keyshares in hardware
dao.currentKeyshare?.let { secureEnclave.store("current_${dao.keyId}", it) }
dao.stagedKeyshare?.let { secureEnclave.store("staged_${dao.keyId}", it) }
}
override suspend fun read(key: String): ReconcileStoreDao? {
val metadata = loadMetadata(key) ?: return null
return ReconcileStoreDao(
currentKeyshare = if (metadata.hasCurrent) secureEnclave.retrieve("current_$key") else null,
stagedKeyshare = if (metadata.hasStaged) secureEnclave.retrieve("staged_$key") else null,
keyId = key
)
}
}Error Handling
Storage operations can fail due to:
Disk full: Handle gracefully, inform user
Corruption: Implement backup/recovery mechanisms
Concurrent access: Use proper locking or optimistic concurrency
Security breach: Detect tampering, wipe data if compromised
Parameters
The type of data access object to store (typically ReconcileStoreDao)
See also
for the data structure being stored
Inheritors
Functions
Reads a Dao from the storage. Returns null if the Dao is not found for the given key.
Writes a Dao to the storage. Update every field of the Dao whenever this callback is invoked.