Skip to main content

Ports

The host implements these and injects them via WalletPorts. You must supply at least SecureArea, StorageDriver, and HttpTransport; WalletClock, Rng, WalletLogger, TransactionLogStore, and WalletAttestationProvider all have defaults (a system clock, the platform secure RNG, no logging, an in-memory log store, and no attestation, respectively). ProximityTransport is not part of WalletPorts — you pass it per call to wallet.proximity.present / wallet.reader.read.

SecureArea​

The private-key custody boundary: creates and uses key material that never leaves the secure boundary. Back it with Android Keystore or iOS Secure Enclave; a software implementation exists for tests. capabilities (SecureAreaCapabilities) declares supported algorithms and whether the area is hardware-backed and can do user-auth, key attestation, and key agreement — the SDK reads it before, e.g., selecting mdoc Mac device auth. Signing over the COSE/JOSE layers is bridged by SecureAreaCoseSigner / SecureAreaJwsSigner, which call sign for you.

interface SecureArea {
val id: SecureAreaId
val capabilities: SecureAreaCapabilities
suspend fun createKey(spec: KeySpec): KeyInfo
suspend fun publicKey(key: KeyHandle): EcPublicKey
suspend fun sign(key: KeyHandle, algorithm: SigningAlgorithm, data: ByteArray, hint: AuthorizationHint? = null): ByteArray
suspend fun keyAgreement(key: KeyHandle, peerPublicKey: EcPublicKey, hint: AuthorizationHint? = null): ByteArray
/** Null when the area cannot attest (e.g. software area, or a key created without a challenge). */
suspend fun attestation(key: KeyHandle, challenge: ByteArray): KeyAttestation?
suspend fun deleteKey(key: KeyHandle)
}

An adapter qualifies by passing SecureAreaContract.verify(area) from the test kit.

StorageDriver​

Byte persistence keyed by collection + key, plus a transaction scope. Wrap it with encryption at rest for production.

interface StorageDriver {
suspend fun put(collection: String, key: String, value: ByteArray)
suspend fun get(collection: String, key: String): ByteArray?
suspend fun delete(collection: String, key: String)
suspend fun keys(collection: String): List<String>
suspend fun transaction(block: suspend StorageTx.() -> Unit)
}

HttpTransport​

Must honour request.followRedirects — the OpenID flows depend on intercepting redirects.

interface HttpTransport {
suspend fun execute(request: HttpRequest): HttpResponse
}

ProximityTransport​

A duplex framed-message channel for ISO 18013-5 (see Proximity). The SDK drives the message exchange; you supply the BLE/NFC radio. retrievalMethods() / nfcCarrier() let the transport tell the SDK its BLE carrier (UUID + mode) for the QR / NFC engagement — both default to none, so a minimal transport implements only the three duplex methods.

interface ProximityTransport {
suspend fun send(message: ByteArray)
suspend fun receive(): ByteArray
suspend fun close()
fun retrievalMethods(): List<ByteArray> = emptyList() // BLE DeviceRetrievalMethod(s) for QR engagement
fun nfcCarrier(): NfcCarrier? = null // BLE carrier for NFC static handover
}

TransactionLogStore​

Append-only audit persistence — the facade wraps it as wallet.transactions.

interface TransactionLogStore {
suspend fun append(entry: TransactionLogEntry)
suspend fun all(): List<TransactionLogEntry>
}

WalletAttestationProvider​

Optional — the link to your Wallet Provider backend. walletAttestation returns a Wallet Unit Attestation (WUA) used for attestation-based client authentication during issuance; keyAttestation returns a per-issuance key attestation over the holder keys the issuer will bind credentials to.

interface WalletAttestationProvider {
suspend fun walletAttestation(keyInfo: KeyInfo): String
suspend fun keyAttestation(keys: List<KeyInfo>, nonce: String?): String
}

Runtime ports​

WalletClock, Rng, and WalletLogger are small runtime hooks. The clock and RNG are injectable so tests can pin them for deterministic validity checks and transcripts; both default to the platform implementation. WalletLogger is a structured sink for the SDK's and adapters' logs — default none.

fun interface WalletClock { fun now(): Instant } // default: WalletClock.System
fun interface Rng { fun nextBytes(size: Int): ByteArray } // default: Rng.Default

interface WalletLogger {
enum class Level { Debug, Info, Warn, Error }
fun log(level: Level, message: String, throwable: Throwable? = null)
}