Skip to content

euwallet · sans-IO Rust core · Lean 4 · Tamarin

EU Wallet infrastructure, engineered to be proven.

An interoperable digital-identity architecture for credential issuance, privacy-preserving presentation, signature authorization, and payment SCA — built around explicit trust boundaries, machine-checked invariants, symbolic protocol analysis, and a reproducible, versioned evidence pipeline.

Every claim on this page names its scope, tool, version, date, and artifact. Evidence refreshed 2026-07-19 · commit f5b2ef0.

Last evidence refresh: 2026-07-19tools/evidence/generate.sh — reproducible from a clean checkout; regenerates Tier 0–3 results into the verification report.

Architecture

Separation of concerns is a security control.

The wallet is a sans-IO Rust behaviour core driven by thin native shells. Responsibility boundaries — not frameworks — are the design: protocol logic cannot touch the network, the shell cannot make trust decisions, and keys cannot leave secure hardware. Assessed against the pinned EUDI specification register: ARF v2.9.0 · PID Rulebook v1.6 · FCAF v0.0.7 (snapshot 2026-07-17) — never an unversioned "latest ARF".

Sans-IO protocol core

The core is a pure state machine: events in, effects out. No network, clock, radio, or disk access exists inside protocol logic — the shell executes effects and feeds results back.

Why it matters Protocol decisions are deterministic and testable without mocking the world; the same core binary logic runs under tests, fuzzing, oracle replay, and on device.

crates/wallet-core/src/lib.rs (Event/Effect contract)

Deterministic, exhaustively-matched state machines

Each protocol (presentation, issuance, proximity, payment, QES, transfer) is an explicit state machine with exhaustive matches; every guard maps 1:1 to a named abort reason.

Why it matters Same state + same event ⇒ same effects. This is what makes the Lean models faithful and the oracle-replay conformance suites meaningful.

crates/oid4vp/src/lib.rs · formal/lean/*.lean · crates/*/tests/conformance.rs

Explicit cryptographic boundary

All cryptography goes through the crypto-traits interface; the production backend is aws-lc-rs. Protocol crates cannot reach a crypto library directly.

Why it matters Cryptographic agility: the backend can be swapped or FIPS-pinned without touching protocol logic; the allow-list of algorithms is auditable in one place.

crates/crypto-traits/ · crates/crypto-backend/

Device keys never cross the FFI

Signing is an effect carrying bytes-to-sign and a key reference. The Secure Enclave (non-exportable, biometric-gated) fulfils it on device.

Why it matters A compromised UI process cannot exfiltrate key material it never holds.

ios/Sources/WalletShell/SecureEnclaveSigner.swift · Effect::Sign contract

Consent computed in-core, hash-committed

The core renders fully-resolved screens (exact claims, amount, payee, document) and records a committing consent hash in the tamper-evident audit log.

Why it matters What the user approved is cryptographically bound to what is signed — the basis of the dynamic-linking and WYSIWYS proofs.

crates/presenter/ · crates/txnlog/

Trust decisions are core decisions

RP registration and issuer trust are decided in-core by X.509 path validation against operator-signed trusted lists; status lists gate presentation fail-closed. The shell only fetches bytes.

Why it matters A buggy or malicious shell cannot inject 'trusted: true'.

crates/trust/ · crates/x509/ · crates/status/

Memory-safe by construction

All 21 workspace crates are #![forbid(unsafe_code)]; the pinned toolchain is Rust 1.97.1.

Why it matters Entire vulnerability classes (spatial/temporal memory errors) are excluded at compile time in wallet code.

crates/*/src/lib.rs · rust-toolchain.toml

Platform-independent domain logic

One Rust core serves iOS today (UniFFI bindings + Swift shell) and is the same artifact an Android shell would embed. The reference shell-io crate drives the identical core over real sockets.

Why it matters Behaviour reviewed and verified once, shipped everywhere; shells stay thin and auditable.

crates/wallet-core/ · ios/ · crates/shell-io/

Architecture Evidence Explorer

DEVICE BOUNDARYSECURE CRYPTO BOUNDARYREGULATORY TRUST INFRASTRUCTUREEXTERNAL TRUST SERVICESSERVICE PROVIDERSQUALIFIED TRUST SERVICES — PLANNEDconsentevents ⇄ effects (UniFFI)Sign(key ref) — via shellsigned trusted listHTTPS: token · credentialHTTPS: direct_post vp_tokenpublishesSCA code — plannedauthorized signing — plannedpublic network · TLSHolderconsent · biometricsNative shellSwiftUI · effect executorRust coresans-IO state machinesSecure Enclavenon-exportable P-256Trust-list operatorsigned trusted listsWUA serviceunit attestationCredential issuerOpenID4VCIStatus servicetoken status listsRelying partyOpenID4VP verifierPayment servicePSD2 SCAQTSP / QSCDCSC API — planned

Select any component (click or keyboard) to open its evidence panel — responsibilities, trust assumptions, threats, proofs, tests, and source path. Static SVG artifacts: ecosystem-context.svg · wallet-components.svg

Protocol journeys

Exact roles, exact profiles — never “OpenID compliant”.

The implementation role is the wallet. OpenID4VCI 1.0 is the OAuth-protected issuance API; OpenID4VP 1.0 is the presentation mechanism — each journey below names its standards, the security decision at every step, the negative cases that are tested, and where the evidence lives.

Wallet role in OpenID4VCI 1.0 (pre-authorized + authorization-code with PAR/PKCE)

An issuer offer starts a fresh issuance session. The core decides issuer trust against the operator-signed trusted list, requires a sender-bound token and a fresh c_nonce, gates on the Wallet Unit Attestation binding the device key, has the Secure Enclave sign the proof-of-possession, validates the returned credential's structure, and stores it as a holding — recorded in the audit log.

Tested (executable suite)
Credential issuance — OpenID4VCI 1.0, wallet roleAn issuer's credential offer reaches the shell (QR or deep link) and is fed to the core with the issuer's certificate chain. The core decides issuer trust by X.509 path validation to a PID/attestation anchor on the operator-signed trusted list; an untrusted issuer aborts. The shell exchanges the token (sender-bound, fresh c_nonce); the core refuses to sign the proof-of-possession unless the Wallet Unit Attestation binds the device key; the Secure Enclave signs; the credential response is validated for format and structure, stored as a holding, and the issuance is recorded in the audit log.HolderNative shellRust coreSecure EnclaveIssuer1. credential offer (QR / deep link)2. CredentialOfferReceived + issuer cert chainTrust decision in-core: X.509 path → trusted-list anchorabort: IssuerNotTrusted3. Effect: RequestToken4. POST /token5. sender-bound token + c_nonce6. TokenReceivedGates: c_nonce fresh (replay set) · WUA attests device keyabort: proof key not attested7. Effect: Sign(proof-of-possession)8. sign (biometric gate)9. ES256 signature10. DeviceSignatureProduced11. Effect: RequestCredential(proof JWT)12. POST /credential13. SD-JWT VC (compact)14. CredentialReceivedValidate format + structure → store holding → audit logabort: UnsupportedFormat / CredentialInvalid
Credential issuance — sequence with security decisions.SVG artifact
Wallet core (this project)External partySecure hardwarePlanned / not yet integrated
Text description of this diagram

An issuer's credential offer reaches the shell (QR or deep link) and is fed to the core with the issuer's certificate chain. The core decides issuer trust by X.509 path validation to a PID/attestation anchor on the operator-signed trusted list; an untrusted issuer aborts. The shell exchanges the token (sender-bound, fresh c_nonce); the core refuses to sign the proof-of-possession unless the Wallet Unit Attestation binds the device key; the Secure Enclave signs; the credential response is validated for format and structure, stored as a holding, and the issuance is recorded in the audit log.

Security decisions

  • Offer received. Issuer trust = X.509 path validation to PID/attestation anchors, in-core.
  • Grant selection. Only HAIP-allowed grants; auth-code path requires PAR + PKCE S256.
  • Token. Must be sender-bound; c_nonce must be fresh (replay set).
  • Proof of possession. Refused unless the WUA attests the device key at the required level; key never leaves the enclave.
  • Credential response. Format must match the request; structure must parse; only then stored.

Negative cases tested

  • abort_untrusted_issuercrates/oid4vci
  • abort_c_nonce_replayedcrates/oid4vci
  • abort_format_mismatch_on_responsecrates/oid4vci
  • unattested_proof_key_is_rejected_in_corecrates/wallet-core/tests/e2e_issuance.rs

Standards & evidence

  • OpenID4VCI 1.0
  • HAIP 1.0 (guard subset)
  • SD-JWT VC
  • WUA (EUDI TS)
  • Live-TCP lifecycle E2E (issue → hold → present)crates/shell-io/tests/e2e_live_lifecycle.rs
  • Lean IssuanceModel (4 safety properties)formal/lean/IssuanceModel.lean
  • Tamarin oid4vci_issuance (4 lemmas)formal/tamarin/oid4vci_issuance.spthy

Honest scope Machine + wire flow tested in-repo including over live TCP. On-device orchestration against a real external issuer (browser/eID step, metadata-driven endpoints, mso_mdoc formats) is in progress.

Formal verification

Claims, assumptions, proofs.

Two complementary tracks. Lean 4 is an interactive theorem prover whose kernel checks proof terms — used here for machine-checked state-machine invariants and safety properties of the wallet's protocol machines. Tamarin analyses the protocols in a symbolic (Dolev–Yao) adversary model and supports both attack discovery and unbounded verification — and it did find a real attack here before it could ship.

Lean 4 — machine-checked invariants

Verified

37 theorems across 6 models, 0 sorry — toolchain Lean 4 v4.32.0 (formal/lean/lean-toolchain). lake build PASS for all six models — verification report, 2026-07-19. Zero `sorry`; no additional axioms declared in the models.

Model-to-code binding Each Lean model doubles as an executable oracle: `lake exe` emits transition traces that crates/{oid4vp,payment,iso18013-5,oid4vci,qes,w2w}/tests/conformance.rs replay against the production Rust machines. CI regenerates the traces and fails if the checked-in traces are stale (job tier2-lean-oracle), so the model and the shipped code cannot silently diverge.

Tamarin — symbolic protocol analysis

Verified

23 lemmas proven across 6 models, 0 falsified — all automatic (no interactive steps). Verification report, 2026-07-19. Tool: tamarin-prover 1.12.0. Adversary: Dolev–Yao: the attacker fully controls the network (read, intercept, inject, replay); cryptography is ideal (symbolic); key compromise is modelled with explicit Reveal rules and each lemma states its exclusions.

Result wording For each lemma: no violating trace was found and the lemma was proven in the specified symbolic model. This is a statement about the modelled protocol under symbolic assumptions — not a claim that the implementation or its cryptographic primitives are unbreakable.

What the proofs cover — and what they do not

Covered

  • – The six protocol state machines: their invariants, safety properties, and abort behaviour (Lean, kernel-checked).
  • – The protocol designs under a Dolev–Yao network adversary with stated key-compromise exclusions (Tamarin, 23 lemmas).
  • – The correspondence between each Lean model and its production Rust machine, via oracle-trace replay gated in CI.
  • – Codec robustness on hostile bytes: canonical-CBOR rejection tests, 4 fuzz targets, Kani bounded proofs on the mdoc crate.

Trusted computing base & gaps

  • – The Lean kernel and Tamarin's semantics/equational theories are trusted, not verified here.
  • – Cryptographic primitives (aws-lc-rs), the OS, the Secure Enclave, and randomness sources are assumed correct; symbolic models treat cryptography as ideal.
  • – The model-to-code mapping covers the state machines — not the shell UI, FFI serialization gadgets, or network transport (TLS is the platform's).
  • – Side channels, unlinkability/observational equivalence, and multi-session interleavings beyond the models are not analysed yet.
  • – No independent assessment (audit, pentest, certification) has been performed to date.

Verification coverage map

Which assurance instrument covers which component. Symbols carry the meaning (not colour): covered · partial · planned · assumed (TCB) · none.

Verification coverage per component: Lean proofs, Tamarin lemmas, unit tests, integration tests, fuzzing, external conformance, independent assessment.
ComponentLean proofsTamarin lemmasUnit / state testsIntegration / E2EFuzz / KaniExternal conformanceIndependent assessment
oid4vp (presentation machine)coveredcoveredcoveredcoverednoneplannednone
oid4vci (issuance machine)coveredcoveredcoveredcoverednoneplannednone
iso18013-5 (proximity machine)BLE/NFC transports not on device yetcoveredcoveredcoveredpartialnoneplannednone
payment (SCA authorization)PSD2 RTS suite; TS12 envelope pendingcoveredcoveredcoveredcoverednoneplannednone
qes (signing authorization)QTSP/CSC integration plannedcoveredcoveredcoveredcoverednoneplannednone
w2w (credential transfer)coveredcoveredcoveredcoverednonenonenone
Format codecs (sdjwt · mdoc · cose · x509)Kani bounded proofs on mdoc in CInonenonecoveredcoveredcoveredplannednone
Trust & status (trust · status · wua)nonenonecoveredcoverednoneplannednone
Consent & audit (presenter · txnlog)consent hash appears in payment/QES modelspartialpartialcoveredcoverednonenonenone
wallet-core facade + UniFFI contractnonenonecoveredcoverednoneplannednone
iOS shell (SwiftUI · executor · adapters)22 Swift tests; UI itself untestednonenonecoveredpartialnonenonenone
Crypto backend (aws-lc-rs)TCB: primitives assumed correct; KATs pendingassumed (TCB)assumed (TCB)partialcoverednonenonenone
Secure Enclave / OS / platformTrusted computing baseassumed (TCB)assumed (TCB)nonenonenonenonenone

Security & privacy

Designed against the attacker, not just the happy path.

The threat model comes first; every control links to a proof, a test, or an architecture element that exists in the repository.

Threat model: adversary, scenario, controls, and the evidence backing each control.
AdversaryControlsEvidenceStatus
Malicious / compromised verifierOver-asks for claims, replays old requests, or spoofs another RP's identity.
  • Signed request objects verified against RP keys resolved from the operator-signed trusted list (in-core decision, never a shell boolean)
  • Data minimisation: only requested-AND-held claims are disclosable; consent screen shows the exact set
  • Nonce replay set persisted; audience must match the wallet
  • Lean: disclose_requires_consent_and_validationformal/lean/WalletModel.lean
  • Tamarin: injective_agreement, nonce_authenticformal/tamarin/oid4vp_haip.spthy
  • Tests: abort_audience_mismatch, abort_nonce_replayed, minimum_claim_setcrates/oid4vp/
Verified
Network attacker (read / inject / replay / relay)Controls the channel between wallet, issuer, verifier, or a proximity reader.
  • All protocol decisions assume an untrusted channel (Dolev–Yao analysis)
  • Proximity: SessionTranscript binding + session encryption + reader authentication (anti-relay)
  • Remote: holder binding via KB-JWT over nonce + audience + presentation hash
  • Tamarin: session_binding, claim_secrecy (proximity)formal/tamarin/iso18013_5_proximity.spthy
  • Tamarin: claim_secrecy (remote)formal/tamarin/oid4vp_haip.spthy
  • Lean: response_requires_bound_sessionformal/lean/ProximityModel.lean
Verified
Malicious or untrusted issuerTries to plant credentials from outside the trust framework.
  • Issuer trust decided in-core by X.509 path validation to PID/attestation anchors on the operator-signed trusted list
  • Untrusted issuer aborts the machine before any token request
  • Lean: issued_requires_issuer_trust, untrusted_issuer_is_rejectedformal/lean/IssuanceModel.lean
  • Tamarin: issuer_authenticationformal/tamarin/oid4vci_issuance.spthy
  • Tests: abort_untrusted_issuer, untrusted_issuer_is_rejected_in_corecrates/oid4vci/ · crates/wallet-core/tests/e2e_issuance.rs
Verified
Transaction-data tamperer (payments / signing)Changes amount, payee, or the document between user confirmation and signing.
  • Dynamic linking: the authentication code IS a device signature over (payee, IBAN, amount, currency, transaction id, nonce)
  • WYSIWYS: the QES authorization binds the confirmed document digest + consent hash
  • Tamarin: dynamic_linking, no_tamperingformal/tamarin/payment_sca.spthy
  • Tamarin: what_you_see_is_what_you_sign, no_document_substitutionformal/tamarin/qes.spthy
  • Tests: PSD2 RTS Art. 4–5 suite (10 named tests)crates/crypto-backend/tests/regulatory_sca.rs
Verified
Replay attackerRe-submits a previous presentation, payment, signing, or issuance proof.
  • Per-flow nonce/c_nonce replay sets, persisted via shell effects
  • Replay rejection proven at model level and tested at implementation level per flow
  • Lean: replay_is_rejected (wallet, payment, QES)formal/lean/
  • Tamarin: nonce_authentic, cnonce_authenticformal/tamarin/
  • Tests: abort_nonce_replayed, abort_c_nonce_replayed, rts_transaction_uniqueness_replay_rejectedcrates/
Verified
Lost / stolen deviceAttacker with physical possession attempts to present or authorize.
  • Device key in the Secure Enclave, non-exportable, biometric/passcode access control (.biometryCurrentSet)
  • Private key never crosses the FFI — the core only ever requests a signature by reference
  • SecureEnclaveSigner (device); keychain fallback only on Simulatorios/Sources/WalletShell/SecureEnclaveSigner.swift
  • Signature-conversion unit tests (DER→JOSE)ios/Tests/WalletShellTests/SecureEnclaveSignerTests.swift
Partial
Stale trust: expired, revoked, or mis-chained certificates & credentialsPresents after revocation; issuer/RP certs expired or chained to the wrong anchor.
  • Token Status List checked before presenting; fail-closed when status is unresolvable online
  • X.509 path validation with validity windows against the clock the shell supplies
  • Trusted-list signature + validity verified before any anchor is used
  • Tests: rejects_expired, rejects_expired_list, rejects_wrong_operator_signature, revoked-blocking e2ecrates/status/ · crates/trust/ · crates/x509/
Tested
Malformed / hostile input (CBOR, COSE, JOSE, JSON, QR)Parser exploitation via crafted credentials, requests, or QR payloads.
  • Canonical-CBOR enforcement (rejects non-canonical, duplicate keys, indefinite lengths, trailing bytes)
  • Coverage-guided fuzz targets for the four codec surfaces; Kani bounded proofs on the mdoc codec in CI
  • memory-safe Rust: all 21 crates are #![forbid(unsafe_code)]
  • Fuzz: cose_cbor, mdoc_cbor, sdjwt_parse, x509_parsefuzz/fuzz_targets/
  • Tests: rejects_non_canonical, rejects_duplicate_map_keys, rejects_trailing_bytes …crates/cose/ · crates/mdoc/
  • CI: tier1-fuzz-kani.github/workflows/ci.yml
Tested
Dependency / build-pipeline compromiseA malicious or vulnerable dependency enters the build.
  • Pinned Rust toolchain (1.97.1) and locked dependency graph
  • cargo-deny (licenses, bans, sources, advisories) + cargo-audit in CI; documented dependency budget
  • CycloneDX SBOM published in-repo (21 crates; regenerable from a clean checkout)
  • deny.toml (2 documented build-time-only advisory ignores)deny.toml
  • CI: supply-chain job.github/workflows/ci.yml
  • Dependency budgetdocs/dependency-budget.md
Tested
Overlay / UI-redressing, phishing verifierTricks the user into consenting to something other than what is displayed.
  • The core renders fully-resolved consent screens (exact claims, payee, amount, document) — the shell cannot display an unbound approximation
  • A committing consent hash is recorded in the audit log for every approval
  • presenter::consent_hash + ScreenDescription contractcrates/presenter/
  • Test: consent_hash_is_stable_and_tamper_evidentcrates/presenter/
Partial

The written threat-model document (TOE boundary, full asset/attacker matrices) is a stub pending publication — docs/certification-evidence/threat-model.md. The rows above are limited to adversaries the code and formal models demonstrably address today.

Privacy as architecture

What leaves the wallet

Only the requested-and-held disclosure subset, inside a holder-bound presentation. The minimum-claim-set computation is core logic with unit tests; family_name staying private while age_over_18 is disclosed is asserted end-to-end over a live socket.

crates/presenter/ · crates/shell-io/tests/e2e_live_presentation.rs

Issuer non-involvement at presentation

Presentation is wallet↔verifier only; the issuer is not contacted. Revocation is checked against issuer-published status lists the wallet fetches independently (fail-closed when unresolvable online).

crates/status/

Audit log without values

The transaction log records claim PATHS and a committing consent hash — never claim values. Entries can be redacted to chain-preserving tombstones; the whole log can be wiped; the activity report contains counts only.

crates/txnlog/ · crates/wallet-core/tests/txn_log.rs

Portable, integrity-protected export

Holder data export carries an integrity hash; the core's own verifier detects tampered bundles (asserted in tests).

crates/wallet-core/src/export.rs · crates/wallet-core/tests/export.rs

Telemetry

None. The core is sans-IO; no analytics or logging endpoints exist in this codebase.

crates/ (no telemetry dependency in the dependency budget)

Unlinkability limits — stated honestly

Unlinkability / observational equivalence is NOT yet modelled in Tamarin, and salted SD-JWT disclosure hashes are the only correlation reduction currently implemented. An experimental Ristretto Pedersen/Schnorr ZK predicate proof exists behind a non-default feature flag (6 tests) and is explicitly not production. DPIA pending publication.

crates/zkp/src/experimental.rs · docs/certification-evidence/dpia.md (stub)

Conformance & standards

Standards are executable contracts.

One row per standard or profile, with its exact version, the wallet's implementation role, the suite that exercised it, the result, the date, and the artifact. External certification has not been performed for any row — planned rows claim nothing.

Filter by status:
13/13 rows
OpenID for Verifiable Presentations (OpenID4VP)1.0 · role: WalletTested

Signed request objects, DCQL query parsing, type-aware credential selection (meta.vct_values / meta.doctype_value), data-minimised consent, KB-JWT holder binding for SD-JWT VC, and an ISO 18013-5 mdoc DeviceResponse vp_token whose device authentication is signed over the OpenID4VP SessionTranscript — direct_post form response with DCQL-keyed vp_token + echoed state, all exercised, including a full presentation over a live TCP relying-party endpoint and an mdoc presentation verified by an independent party from wire data.

Format:
SD-JWT VC (dc+sd-jwt)
Suite:
In-repo: state-machine, wire-format and live-TCP E2E suites (cargo test)
Executed:
2026-07-19
Commit:
4570ea6

Evidence: crates/oid4vp/ · crates/wallet-core/tests/e2e_mdoc_flow.rs · crates/shell-io/tests/e2e_live_presentation.rs

Notes & limitations mdoc-over-OpenID4VP (DeviceResponse as vp_token) is now implemented and tested end-to-end through wallet-core; direct_post.jwt (JWE) response encryption is not implemented yet. OIDF conformance suite not yet run (see row below); a reachability/wire-shape interop probe against the reference environment does pass.

OpenID for Verifiable Credential Issuance (OpenID4VCI)1.0 · role: WalletTested

Pre-authorized and authorization-code (PAR + PKCE S256) machines; in-core issuer-trust decision; Wallet Unit Attestation key gate; device-signed proof-of-possession; c_nonce replay rejection; credential stored exactly as received over a live socket.

Format:
SD-JWT VC (dc+sd-jwt)
Suite:
In-repo: issuance state machine + live-TCP lifecycle E2E (cargo test)
Executed:
2026-07-19
Commit:
cb688a8 (+ working tree)

Evidence: crates/oid4vci/ · crates/shell-io/tests/e2e_live_lifecycle.rs

Notes & limitations Browser/eID authorization UX and issuer-metadata-driven endpoint discovery on device are in progress. mso_mdoc issuance is wired in-core (a received mso_mdoc credential is parsed into an mdoc holding on receipt); a live external mso_mdoc issuer round-trip is pending.

High Assurance Interoperability Profile (HAIP)1.0 · role: WalletPartial

HAIP-aligned guards are enforced in-core (signed request objects, grant-type allow-list, PKCE S256, sender-bound tokens, attested proof keys).

Commit:
cb688a8

Evidence: crates/oid4vci/src/lib.rs · crates/oid4vp/src/lib.rs

Notes & limitations Not the full profile: direct_post.jwt response encryption remains (the mdoc-over-OpenID4VP presentation variant is now implemented and tested). No profile-level test run has been executed.

SD-JWT-based Verifiable Credentials (SD-JWT VC)draft (dc+sd-jwt) · role: Wallet (holder) + verifier-side checks in testsTested

Parse/verify, selective disclosure with digest checking, KB-JWT issuance & verification, alg:none and malformed-input rejection; fuzz target sdjwt_parse.

Format:
dc+sd-jwt
Suite:
In-repo: crates/sdjwt tests + E2E presentation verification (cargo test)
Executed:
2026-07-19
Commit:
cb688a8

Evidence: crates/sdjwt/

Notes & limitations Test vectors are repo-generated; alignment against published external vectors is planned.

ISO/IEC 18013-5 (mdoc / mDL proximity)2021 · role: mdoc holder (Wallet)Tested

IssuerSigned/MSO build+verify, DeviceResponse assembly, SessionTranscript binding, reader authentication, session-encryption state machine; canonical-CBOR negative tests; fuzz targets mdoc_cbor and cose_cbor; Kani bounded proofs on the mdoc crate in CI.

Format:
mso_mdoc (CBOR/COSE)
Suite:
In-repo: crates/mdoc + crates/iso18013-5 suites (cargo test)
Executed:
2026-07-19
Commit:
cb688a8

Evidence: crates/mdoc/ · crates/iso18013-5/

Notes & limitations BLE/NFC transports are shell adapters not yet implemented on device. The mdoc-over-OpenID4VP handover (the OID4VPHandover SessionTranscript that device authentication signs over) is now wired and tested end-to-end — see crates/oid4vp/tests/mdoc_presentation.rs and crates/wallet-core/tests/e2e_mdoc_flow.rs.

OpenID Foundation Conformance Suite (OpenID4VP / OpenID4VCI / HAIP)self-certification program opened 2026-02-26 · role: WalletPlanned

Notes & limitations Not yet executed. The suite is the intended external interop bar once the remaining wire-fidelity item (direct_post.jwt response encryption) lands. No result is claimed.

EUDI ARF requirement traceability (harmonized register)ARF v2.9.0 · PID Rulebook v1.6 (register snapshot 2026-07-17) · role: Wallet UnitPartial

180 of 684 harmonized requirements mapped to implementation symbols AND named tests; the remaining 504 are explicitly recorded as unassigned (provider-side backends, governance, and P2 credential types not built).

Suite:
tools/evidence/generate.sh (Tier 0)
Applicable / mapped:
180/684
Executed:
2026-07-19
Commit:
cb688a8

Evidence: traceability/requirements.csv · docs/certification-evidence/verification-report.md

Notes & limitations Mapping coverage, not conformance testing. Kept deliberately high-precision (no keyword inflation).

EUDI Functional Conformance Assessment Framework (FCAF)v0.0.7 (pinned) · role: Wallet UnitPlanned

Notes & limitations FCAF defines harmonized functional test cases; it is not itself an automated executable suite. A pinned FCAF run + report directory exists in the evidence set (docs/certification-evidence/fcaf-reports/, currently empty). No result is claimed.

PSD2 SCA & dynamic linking (Commission Delegated Regulation (EU) 2018/389)RTS Articles 4–5 · role: Wallet as SCA/authorization component (never payment execution)Tested

Article-by-article traceability: authentication-code generation & verification (Art. 4), forgery resistance, payer awareness of amount+payee (Art. 5(1)), dynamic linking to amount, payee name, payee IBAN and currency (Art. 5(1)(c)/5(3)), code integrity (Art. 5(2)), replay rejection, possession-factor requirement.

Suite:
crates/crypto-backend/tests/regulatory_sca.rs (cargo test, real aws-lc-rs crypto)
Executed:
2026-07-19
Commit:
cb688a8

Evidence: docs/certification-evidence/payment-sca.md

Notes & limitations TS12 wire envelope is approximated pending the published schema; transport confidentiality is the shell's TLS; SCA exemptions (Arts. 10–18) out of wallet scope.

eIDAS qualified electronic signatures (QES orchestration)Regulation (EU) 910/2014 as amended by (EU) 2024/1183 · role: Wallet as authorization/orchestration component (not a QSCD)Partial

The WYSIWYS authorization core is implemented and formally analysed (Lean QesModel; Tamarin qes.spthy: what_you_see_is_what_you_sign, no_document_substitution).

Commit:
cb688a8

Evidence: crates/qes/ · formal/tamarin/qes.spthy

Notes & limitations No QTSP integration yet: CSC API v2.0 alignment and a remote-QSCD flow against a qualified trust service are planned. No signature produced today is a qualified signature.

Token Status List (credential revocation/suspension)IETF draft · role: Wallet (relying on issuer-published lists)Tested

Signed status-list verification, bit-level status lookup, fail-closed policy for remote presentation (unresolvable status blocks presentation).

Suite:
crates/status tests + wallet-core e2e_status (cargo test)
Executed:
2026-07-19
Commit:
cb688a8

Evidence: crates/status/

Notes & limitations Fetch-and-refresh scheduling is a shell concern; core decides on verified lists only.

Wallet Unit Attestation (key attestation gate)EUDI TS (register snapshot 2026-07-17) · role: WalletTested

WUA JWT verification binding the device public key at the stated assurance level; issuance proof-of-possession is refused in-core when the key is not attested.

Suite:
crates/wua tests + issuance gate tests (cargo test)
Executed:
2026-07-19
Commit:
cb688a8

Evidence: crates/wua/

Notes & limitations Wallet-provider WUA issuance service is out of scope of this repository.

Wallet certification (CIR (EU) 2024/2981 + EUCC)2024 · role: Wallet solutionPlanned

Notes & limitations Certification is performed by an accredited CAB under the national scheme; this project maintains the evidence set (docs/certification-evidence/) but has not entered certification. No certification is claimed.

Testing & software quality

Every important failure mode becomes a test.

205 Rust workspace tests and 22 Swift shell tests pass against real aws-lc-rs cryptography (no mocks), run 2026-07-19. Organised below by assurance purpose, not framework.

Unit & state-machine tests

Tested

Domain rules, guard functions, transitions, encoding/decoding, boundary conditions — every protocol guard maps 1:1 to a named abort reason and has a test.

  • transitions.rs (per-machine transition tables)crates/oid4vp · oid4vci · payment · qes · w2w · iso18013-5
  • rejects_non_canonical / rejects_duplicate_map_keys / rejects_trailing_bytescrates/cose
  • binding_is_specific_to_receiver_and_credentialcrates/w2w

Model-conformance (oracle replay)

Verified

The Lean models emit transition traces; six Rust suites replay them against the production machines, byte-for-byte. CI regenerates traces and fails on staleness — the formal model and shipped code cannot silently diverge.

  • conformance.rs × 6crates/{oid4vp,payment,iso18013-5,oid4vci,qes,w2w}/tests

Integration & end-to-end

Tested

Full flows through the sans-IO core with real aws-lc-rs cryptography: issue → hold → present over live TCP sockets; payment SCA; QES; wallet-to-wallet; status blocking; export integrity; FFI JSON contract.

  • e2e_live_presentation.rs / e2e_live_lifecycle.rs (real sockets, RP verifies)crates/shell-io/tests
  • e2e_flow / e2e_issuance / e2e_payment / e2e_status / qes_flow / w2w_flow / add_credentialcrates/wallet-core/tests
  • 22 Swift tests: effect executor, QR classifier, DER→JOSEios/Tests/WalletShellTests

Adversarial & negative tests

Tested

51named cases

Every important failure mode becomes a test: replay, wrong nonce/audience, untrusted issuer, revoked/expired credentials, tampered transactions, forged codes, malformed input, misdirected transfers, alg:none.

Distinct test functions whose names encode a rejection/attack case (rejects_*, *replay*, *tamper*, *forged*, *mismatch*, *untrusted*, *revoked*, *invalid*, *malformed*), counted by grep on 2026-07-19 — a subset of the 205 workspace tests.

  • rts_art4_code_cannot_be_forgedcrates/crypto-backend/tests/regulatory_sca.rs
  • rejects_a_misdirected_transfer_in_corecrates/wallet-core/tests/w2w_flow.rs
  • export_round_trips_and_detects_tamperingcrates/wallet-core/tests/export.rs
  • rejects_alg_nonecrates/sdjwt

Fuzzing & bounded proofs

Tested

4fuzz targets

Coverage-guided fuzzing of the four codec attack surfaces; Kani bounded model checking on the mdoc codec. Both run in CI (bounded time per run).

cargo-fuzz targets in fuzz/fuzz_targets/.

  • cose_cbor · mdoc_cbor · sdjwt_parse · x509_parsefuzz/fuzz_targets/
  • Kani bounded proofs (cargo kani -p mdoc).github/workflows/ci.yml (tier1-fuzz-kani)

Static analysis & supply chain

Tested

clippy -D warnings across the workspace (PASS, 2026-07-19); rustfmt gate; cargo-deny (licenses/bans/sources/advisories) with a written dependency budget; cargo-audit; and a PUBLISHED CycloneDX SBOM (21 crates; wallet-core = 111 runtime components).

  • clippy PASS — verification reportdocs/certification-evidence/verification-report.md
  • deny.toml (2 documented build-time-only ignores)deny.toml
  • Published SBOM (CycloneDX 1.3)docs/certification-evidence/sbom/

Mutation testing

Tested

73viable mutants caught

cargo-mutants mutates the security-critical OpenID4VP presentation crate and checks the tests catch each change — measuring whether tests constrain behaviour, not just execute it. The initial run caught 52/73 viable mutants (71%); the 21 misses were real test gaps, closed by adding crate-local tests. Re-run: 73/73 viable caught, 0 missed.

Viable mutants caught / total viable, cargo-mutants v27.1.0 on crates/oid4vp with the crate-local test suite (2026-07-19). 8 unviable (non-compiling) mutants excluded per standard practice. Scope: oid4vp only — a test-adequacy measure, not a correctness proof.

  • cargo mutants -p oid4vpdocs/certification-evidence/mutation-testing.md

Performance benchmarks

Tested

Dependency-free micro-benchmarks of the hot paths against the real aws-lc-rs backend (ES256 sign ~28.7µs / verify ~83.6µs, SHA-256 ~58ns, SD-JWT parse ~430ns; release, Apple Silicon, 2026-07-19). A flow is dominated by one or two ES256 ops plus one TLS round trip.

  • cargo run -p benches --releasedocs/certification-evidence/perf-benchmarks.md

Interoperability probe

Partial

A reproducible probe of the EUDI reference environment over platform TLS: the reference issuer's live OpenID4VCI metadata (HTTP 200, 27 configurations — 11 in the SD-JWT VC format this wallet implements) and reference-verifier reachability. Reachability + wire-shape, NOT a conformance pass.

  • tools/interop/probe.shdocs/certification-evidence/interop.md

Coverage, honestly Code coverage (line/branch/function) has not been measured yet — the number is deliberately absent rather than implied. Coverage also would not prove correctness: the complementary assurance here is machine-checked invariants (Lean), symbolic protocol analysis (Tamarin), oracle-replay conformance binding model to code, adversarial tests, and fuzzing.

Requires an external party (not closeable by our code)

  • Independent penetration test (accredited security assessor)
  • Accessibility audit of the mobile app (the landing page itself targets WCAG 2.2 AA; a formal audit of the iOS app is external)
  • OpenID Foundation conformance certification (their harness; self-certification program opened 2026-02-26)
  • CAB certification under CIR (EU) 2024/2981 + EUCC, and wallet-provider / Member-State registration

Planned (codeable) — not yet done, no result implied

  • Completed live issuance / presentation round trip against issuer.eudiw.dev / verifier.eudiw.dev — automatable as an XCUITest driving the app (pre-authorized-code issuance + direct_post presentation) with the reference trust anchors pinned as fixtures; the interactive browser-eID grant is the one step that stays manual or mocked, so it runs as a nightly integration job, not a gating unit test
  • direct_post.jwt (JWE) response encryption, and live mso_mdoc issuance against an external issuer
  • Endurance / soak (hours-long) load tests and device-hardware latency numbers
  • Line/branch coverage report

Operational assurance

Measured process, not adjectives.

Reproducible evidence pipeline

Tested

tools/evidence/generate.sh regenerates every tier (traceability, tests, Lean, Tamarin, lint) from a clean checkout into the verification report — the page you are reading quotes that artifact.

tools/evidence/generate.sh

CI gates mirror the architecture

Tested

Jobs: rust-core (fmt, clippy -D warnings, tests) · supply-chain (cargo-deny, cargo-audit, CycloneDX SBOM) · tier1 (bounded fuzz + Kani) · tier2 (Lean build + oracle-trace staleness gate + replay) · tier3 (Tamarin, advisory) · ios-shell (swift build + test) · traceability import.

.github/workflows/ci.yml

Pinned toolchains & dependency budget

Tested

Rust 1.97.1, Lean v4.32.0 and the dependency graph are pinned; a written dependency budget bounds what may enter the build, enforced by cargo-deny (two documented build-time-only advisory ignores).

docs/dependency-budget.md · deny.toml

Interop probe (reference environment)

Tested

A reproducible harness fetches the reference issuer's live OpenID4VCI metadata over platform TLS (HTTP 200, 27 configurations — 11 in the SD-JWT VC format this wallet implements) and confirms the reference verifier is reachable. The iOS app runs the same fetch on its real URLSession stack. Reachability + wire-shape only — not a conformance pass.

tools/interop/probe.sh · docs/certification-evidence/interop.md

Published SBOM

Tested

CycloneDX 1.3 SBOMs are published in-repo (21 crates; wallet-core = 111 runtime components), regenerable from a clean checkout — not just generated transiently in CI.

docs/certification-evidence/sbom/ · tools/evidence/sbom.sh

Performance benchmarks

Tested

Published hot-path numbers against the real backend (ES256 sign ~28.7µs / verify ~83.6µs, SHA-256 ~58ns, SD-JWT parse ~430ns; release, 2026-07-19), reproducible with one command.

docs/certification-evidence/perf-benchmarks.md · crates/benches/

Signed releases, reproducible builds, disclosure policy

Planned

Release signing, build provenance/reproducibility, and a published vulnerability-disclosure policy are planned. Threat model, DPIA, key-lifecycle and KAT documents remain stubs pending publication in the certification-evidence set.

docs/certification-evidence/

Vulnerability disclosure & incident response

Planned

No published disclosure policy yet — planned alongside the certification evidence set.

docs/certification-evidence/ (pending)

Evidence

Trust should be inspectable.

Review the architecture decisions, formal models, proof status, conformance mappings, and test artifacts behind the implementation — all in one repository, regenerated by one command.