Thoryn

Wallets, programmable

How it works

KMP split, platform adapters, the holder-key flow, and distribution via SPM + Maven Central.

Module layout (Kotlin Multiplatform)

The SDK splits into wallet-core/commonMain (protocol logic written once) with iosMain and androidMain for platform adapters. Platform apps consume the shared core via Swift Package Manager (iOS) or Maven Central (Android).

KMP module layout — wallet-core/commonMain with iosMain and androidMain platform adapters
Protocol logic in commonMain; Secure Enclave / Keystore bindings per platform.

Platform adapters

Platform-specific code handles what can't be shared: key material, biometric prompts, deep links. iOS uses Swift wrappers over Secure Enclave + LAPolicyDeviceOwnerAuthenticationWithBiometrics. Android uses Kotlin wrappers over Android Keystore + BiometricPrompt (BIOMETRIC_STRONG).

Holder-key flow

When the user presents a credential, the SDK triggers a fresh biometric prompt. On success, the private key inside Secure Enclave / StrongBox signs the VP token. The key never leaves hardware; even a compromised app process can't extract it.

Biometric gate triggers key use inside Secure Enclave; signed VP token returned
Biometric required on every presentation; private key stays in secure hardware.

Receive flow (OID4VCI)

The app invokes sdk.receive(offerUrl). SDK parses the offer, exchanges the pre-auth code at the issuer's /token, generates a holder-key proof (signed with the device key), requests the credential at /credential, and stores the SD-JWT VC in encrypted local storage.

Present flow (OpenID4VP)

The app invokes sdk.present(requestUri). SDK fetches the signed authorization request, matches available credentials against the presentation definition, surfaces a consent UI to the user, prompts for biometrics, signs the VP token, and posts it to the verifier's response endpoint.

Encrypted local storage

Credentials are stored encrypted in platform-appropriate secure storage (iOS Keychain, Android EncryptedSharedPreferences / Keystore-backed). No cloud backup by design — credentials are device-bound. Losing the device means re-issuing credentials into a new device; that's the security guarantee, not a limitation.

Distribution

iOS: Swift Package Manager. Android: Maven Central. Semver releases; changelog published per release. No CocoaPods, no JitPack — the standard platform channels only.

Ready to embed a wallet in your app?

See the ADR, the scaffold, and the roadmap — book a 30-minute integration call.