Skip to Content
ArchitecturePrivacy & Security

Key Management & Privacy

Core principle: Users never touch private keys, seed phrases, or any cryptographic details. All key management relies entirely on native iOS capabilities (Keychain + iCloud Keychain). “Face ID is your identity.”


Architecture


Key Lifecycle

StageBehaviorTechnology
CreationEC P-256 key pair generated in Keychain on first launchSecKeyCreateRandomKey with .biometryCurrentSet
ProtectionAny private key access requires Face ID / Touch IDkSecAttrAccessibleWhenUnlocked (allows iCloud sync)
SynciCloud Keychain auto-syncs E2EE to all devices on the same Apple AccountNative Apple mechanism, no custom sync needed
UseEach DID operation requires Face ID unlockFace ID → Keychain access → SecKeyCreateSignature
PairwiseA separate key pair is generated in Keychain for each RPOne independent SecKey per RP
BackupApple manages E2EE backup for iCloud KeychainNo user action required
Recovery (normal)Sign in to same Apple Account → Keychain auto-syncsHandles device migration naturally
Recovery (disaster)Social Recovery → Shamir → reconstruct Master KeySee Social Recovery below

Pairwise DID (Per-RP Key Pair)

Each Verifier (RP) uses a separate key pair, making it impossible to correlate the user’s identity across RPs:

Keychain ├─ "solidarity.master" → Master Key Pair → did:key │ Used for: VC self-issuance, face-to-face exchange ├─ "solidarity.rp.bar-abc.com" → RP-A Key Pair → did:key │ Used for: presenting proof to Bar ABC ├─ "solidarity.rp.example.com" → RP-B Key Pair → did:key │ Used for: login to example.com └─ ... one independent key pair per RP

Why independent generation instead of derivation: iOS Secure Enclave does not allow exporting raw key material, so HKDF derivation is not possible. Generating an independent key pair per RP is simpler, more secure, and all key pairs are automatically synced via iCloud Keychain.


DID Format

did:key:z6Mk... (EC P-256 public key → multicodec → multibase)

The App UI never surfaces the did:key:z6Mk... string directly. It is referred to as “your digital identity.” DID details are accessible under Settings → Advanced.

Code: solidarity/Services/Identity/DIDKeyService.swift


Social Recovery (Shamir Secret Sharing)

Concept

Shamir’s Secret Sharing splits the DID Master Key recovery secret into N shares, encrypts each share for a guardian, and stores them in a Recovery Bundle. In a disaster scenario, collecting a threshold number of guardian confirmations is sufficient to reconstruct the key.

Setup Flow [SR-1]

Recovery Flow [SR-2]

Guardian Confirmation Screen [SR-3]

┌─────────────────────────────────────┐ │ 🔒 Social Recovery Request │ │ │ │ Ming is asking you to help │ │ recover his identity. │ │ │ │ You exchanged with Ming on │ │ 2025-03-15 in person. │ │ │ │ ⚠️ Only confirm if you are sure │ │ this is the real person. │ │ │ │ [Decline] [Confirm with Face ID] │ └─────────────────────────────────────┘

Recovery Scope

DataRecoverable?Mechanism
DID Master KeyShamir recovery
Per-RP Keys❌ Must regenerateUse recovered master key to re-register with each RP
Passport VC❌ Must re-scanThe passport itself is the source of truth
Contacts (verified)⚠️ PartialRecovery Bundle can optionally include encrypted contact metadata

Code: solidarity/Services/Recovery/SocialRecoveryService.swift


Security Analysis

Attack ScenarioProtection
Guardian collusionThreshold design (5 guardians, 3 required) + guardians are unaware of each other’s identity
Recovery Bundle leakEach share is encrypted with the guardian’s public key — useless without the private key
Guardian device stolenEach share requires Face ID to access
Guardian unreachableThreshold allows partial guardian unavailability; guardian list can be updated periodically

Threat Model

ThreatProtection
Server breachNo server stores user data
Network eavesdroppingMCSession TLS encryption; VP token only in URL fragment
Cross-RP trackingPairwise DID — each RP sees a different DID
Device lossKeychain encrypted, Face ID required; Social Recovery can reconstruct
Contact list inferenceExchanged hashes cannot be reverse-engineered to reveal contact list

Data Stays on Device

OperationData Flow
Passport ZKP generationEntirely on-device (mopro on-device prover) — no data leaves
Face-to-face exchangeP2P direct connection (MCSession) — no server involved
Smart QR verificationProof is in URL fragment — server never sees it
Apple Wallet signingOnly SHA256 hashes (no PII) sent to Cloudflare Worker
iCloud Keychain syncApple E2EE — Apple itself cannot read key contents
Last updated on