StorageClient

interface 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

  1. Generation: MPC operation creates a keyshare, stored as stagedKeyshare

  2. Reconciliation: stagedKeyshare becomes currentKeyshare (ready for use)

  3. Recovery: If reconciliation fails, stagedKeyshare can be reconciled later

  4. Refresh: 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

Dao

The type of data access object to store (typically ReconcileStoreDao)

See also

ReconcileStoreDao

for the data structure being stored

Inheritors

Functions

Link copied to clipboard
abstract suspend fun read(key: String): StorageDao?

Reads a Dao from the storage. Returns null if the Dao is not found for the given key.

Link copied to clipboard
abstract suspend fun write(dao: StorageDao)

Writes a Dao to the storage. Update every field of the Dao whenever this callback is invoked.