Plain summary
- Issuing institutions change nothing; credentials are signed as today.
- The wallet proves a statement such as "I am over 18" without showing the date of birth or the credential.
- Two presentations by the same person cannot be linked; the limit on credential copies goes away.
- The method is Longfellow ZK, which Europe has adopted for age verification; only reviewed circuits pinned by their digest are valid.
- If a proof cannot be made (an old phone, a verifier without support), the batch-copy method continues.
Context
- Unlinkability is also achieved with batch copies: 10 copies of the identity belge (credential)Belge verenin imzaladığı ve kişinin cüzdanında duran dijital belge; kişi yalnız istenen alanları gösterir., a sticky copy per doğrulayıcı (verifier)Gösterilen belgeyi denetleyen taraf: imza, belge verenin güven listesindeki kaydı, durum ve politika. Relying party diye de anılır. (WL5) and the
age_over_18attribute (ADR-0012, ADR-0013). This is the EU's current method; copies run out and are refreshed, and the belge veren (issuer)Belgeyi imzalayıp veren kurum: üniversite, meslek kuruluşu, kamu kurumu ya da şirket. signature is visible in every copy. - On 2026-09-28 project management set the direction (backlog Z5): zk-SNARKs in the wallet, institutional credentials unchanged; BBS is not pursued (it requires institutions to change keys; it is not on the EU's approved algorithm list).
- Longfellow ZKmdoc belgeleri için açık bir sıfır bilgi ispatı sistemi; Tamga doğum tarihini göstermeden "18 yaşından büyük" gibi bilgileri kanıtlamak için kullanır.: Google's open-source (Apache-2.0) mdocISO/IEC 18013-5 mobil belge biçimi, CBOR ile kodlanır; yüz yüze gösterme ve mobil ehliyet için kullanılır./ECDSA proof system; moved to an independent foundation in 2026-09 (
github.com/longfellow-zk/longfellow-zk), passed three independent security reviews; the EU age-verification profile and TS13 are built on it. It needs no trusted setup. Circuit version 8 binds the docType into the proof; it does not bind the namespace (Stage 1 measurement). - Z5 Stage 1 — a real proof (2026-10-01,
tools/zk-circuit/): anage_over_18 = trueproof was produced and verified over the Tamga identity credential (urn:tamga:id:IdentityAttestation:1, namespacetamga.id.1,statusin the MSO, bytewise CBOR ordering); no change to the credential was needed. Desktop (CPU-specific build, median of 5 runs): proving 518 ms, verification 211 ms, proof ~343 KB, proving memory ~92 MB; the circuit (v8, 1 attribute) is deterministic, id5a893815…3c9291. In a generic x86-64 build proving takes 3.6 s / verification 1.4 s (PCLMUL/AVX2 off) — mobile builds must enable the target CPU features. Negative tests: tampered value, wrong institution key, another session/nonceBir kez kullanılan rastgele değer; doğrulayıcı gönderir, cüzdan imzalar, böylece eski bir gösterme yeniden kullanılamaz., another verifier, "true" from a "false" credential, another docType, corrupted proof → all REJECTED. The namespace is not bound to the proof (verification with another namespace ACCEPTED): at v8 runtime the namespace is used only in a pre-check.
Decision
K1 — Proof system (project management: accepted, 2026-10-01)
Longfellow ZK (longfellow-libzk-v1), circuit version ≥ 8. Reference implementation Google Rust (rust/applications/mdoc_zk); ISRG zk-cred-longfellow in cross-testing as a second implementation.
K2 — Accepted circuits
The verifier accepts only circuit digests (combined_hash, per number of attributes) published in the signed güven listesi (trust list)Bir ülkenin kök sertifikalarını, belge verenlerini ve kayıtlı relying party'lerini taşıyan imzalı liste. Bugün Tamga'da güven bu listelere dayanır; ortak defter sonra gelir. (lotl.jws). A circuit update = a list update (no ADR needed; announced in the CHANGELOG).
K3 — First predicate and scope (project management: over-18 first, 2026-10-01)
The first predicate is age_over_18 = true in the identity credential. Then: age_over_21, nationality, enrolment / graduation in education credentials (equality predicates). Ranges and "any accredited institution" (hiding the institution) are not yet in Longfellow — out of scope.
K4 — Transport
OpenID4VP (OpenID for Verifiable Presentations)Doğrulayıcının cüzdandan belge istediği ve gösterimi aldığı protokol. DCQL (Digital Credentials Query Language)Doğrulayıcının OpenID4VP'de hangi belgeleri ve hangi alanları istediğini yazdığı sorgu dili. format: "mso_mdoc_zk" (EU TS13 request) and the Digital Credentials API (EU age-verification profile). A proof is ~350 KB: it cannot be carried in a QR code; for proximity (BLE) a decision follows measurement.
K5 — Fallback
If the wallet cannot produce a proof or the verifier does not request mso_mdoc_zk, the mso_mdoc / dc+sd-jwt presentation (batch copies + WL5) continues unchanged. Batch copies are not removed.
K6 — Revocation status
The circuit does not check revocation status; opening the iptal listesi (status list)Her belgenin tek bir konumu olduğu sıkıştırılmış, imzalı liste; geçerli, askıda ya da iptal olduğunu söyler. index would bring linkability back. Interim solution: credentials presented with ZK are short-lived (silent refresh, ADR-0023); private revocation proof gets a separate ADR once the revocation scheme of EU TS13 is settled.
K7 — Device binding
The proof contains the device key's ES256 signature over the SessionTranscript and hides the device public key; the key stays in secure hardware (Secure Enclave / StrongBox) and the signing flow does not change.
K8 — The namespace is not bound: unique attribute names
A Longfellow v8 proof binds the institution key, the docType, the attribute name and the value; it does not bind the namespace. The verifier learns "in this institution's credential of this type, some namespace has age_over_18 = true". Therefore, within a credential type, an attribute name appears in only one namespace; the schema catalogue (packages/schemas) checks this at build time.
K9 — Build
Mobile and server builds use the target CPU's GF(2^128) multiplication instructions (x86-64: PCLMULQDQ; ARM: PMULL/NEON); otherwise proving is ~7× slower (measured).
Rationale / alternatives
| Option | Result | Why |
|---|---|---|
| Longfellow ZK (this ADR) | accepted | Institutional credentials unchanged; same as the EU age-verification profile and TS13; no trusted setup; independently reviewed |
| BBS / BBS# signatures | rejected | Institution keys and signature format change; not on the EU's approved mechanism list (direction of 2026-09-28) |
| Microsoft Crescent | watch | ZK over existing JWT/mdoc; not the EU profile |
| Batch copies only | fallback (K5) | Works, but copies run out and the institution's signature is the same in every presentation |
Invariants
| Code | Rule |
|---|---|
| ZK1 | The format and signature of the institutional credential are not changed for ZK presentation; ZK lives only in the wallet and the verifier. |
| ZK2 | The verifier accepts only circuit digests published in the signed trust list; an unknown circuit = REJECTED. |
| ZK3 | A ZK presentation proves only the attributes requested in DCQL; no attribute outside the proof reaches the verifier. |
| ZK4 | A ZK presentation does not reveal the status list index; credentials presented with ZK are kept short-lived (K6). |
| ZK5 | If ZK is not supported, the presentation follows the classic rules (including WL5); a proof failure is shown to the user as "cannot be shown this way right now" and leaks no data. |
| ZK6 | Within a credential type an attribute name appears in only one namespace (K8). |
Implementation plan
| Stage | Work | Prerequisite |
|---|---|---|
| 1 | ✅ Real proof + verification on the desktop, measurements, negative tests, Node bridge prototype (2026-10-01) | — |
| 2 | Wallet: Rust core → UniFFI → Expo native module; proof generation | Store build (Z1) |
| 3 | ✅ @tamga-network/verifier: mso_mdoc_zk verification (bundled WASM, /zk); circuit digests in the trust list (lotl.zk_circuits); policy format: "mso_mdoc_zk"; Tamga Verify age-over-18-zk (2026-10-01) | Stage 1 |
| 4 | Transport (DCQL + DC API), cross-testing with the EU reference verifier; updates to /docs/selective-disclosure and SPEC-WALLET-0001 | Stage 3 |
Verifier (Stage 3)
The verifier compiles only Longfellow's VERIFICATION code to WebAssembly (packages/verifier/zk, pinned upstream commit d5e6be77; the C dependency zstd is patched with a pure-Rust shim, the upstream code is untouched; the build is reproducible, npm run zk:build -- --check). The WASM is 889 KB with no external dependencies; verification takes ~3 s on the desktop (0.2 s with a native build — K9; if speed is needed, a native backend via VerifyInput.zk). Format mso_mdoc_zk (TS13 ZkDocument), step Z1 (SPEC-API-0001), circuits in lotl.zk_circuits (SPEC-TRUST-0001). Tests use a real proof fixture (scripts/zk-fixtures.ts); no Rust needed. The "prefer" mode (ZK + classic option via DCQL credential_sets) arrives with wallet support (Stage 2); until then ZK5 = the verifier asks again with the classic policy.
Native backend (K9)
A tamga-zk-verify binary built from the same Rust source with --features native (the WASM output does not change; rust-toolchain.toml pinned to 1.98.1). NativeZkBackend runs the binary as a long-lived subprocess (framed stdin/stdout; no network/file access); if the binary is missing, crashes or times out, the request falls back to WASM and an error never counts as "valid"; the circuit digest (ZK2) is checked on the Node side on both paths. Tamga Verify uses it via TAMGA_ZK_NATIVE_BIN; on the server it is built in deploy.sh step 4b (only when the source changes). Measurement (desktop, full pipeline): native median 289 ms; WASM ~4 s under the same load.
Resolved questions (project management, 2026-10-01)
- Longfellow ZK accepted as the proof system (K1).
- Setup: user-level tools (Rust + a portable compiler) were enough; no administrator installation was needed.
- First predicate: "over 18" only; enrolment in a later round.
Status
Accepted — 2026-10-01 (approved by project management: proof system Longfellow, first predicate age_over_18). Stage 1 desktop trial and Stage 3 (verifier) done; Stage 2 (phone) after the store build (Z1). Experiment: tools/zk-circuit/README.md.