Skip to content
ADR-0034Architecture decisionAcceptedversion 1.0.02 October 2026

Plain summary ​

  • Verifiers identify themselves not by domain name but by their certificate's fingerprint (x509_hash); the HAIP 1.0 profile the EU has adopted makes this mandatory. The wallet accepts only this form.
  • The fingerprint changes when the certificate is renewed. So each verifier record in the trust list also carries a permanent domain name (dns_name); pseudonyms, credential copies and pass cards are bound to it — users' accounts and cards keep working when the certificate is renewed.
  • The "who" field (sub) of the wallet attestation is the same on every phone (the name of the wallet solution); it carries no phone-specific value.

Context ​

In round 43 (2026-09-30) the HAIP (High Assurance Interoperability Profile)Yüksek güvenceli kullanım için OpenID4VC seçeneklerini sabitleyen profil; EUDI Wallet'ta da kullanılır. 1.0 requirements were mapped one by one; two gaps remained and, because they touched closed decisions, were left for an ADR:

  1. Client identifier prefix. HAIP 1.0 §5: "For signed requests, the Verifier MUST use, and the Wallet MUST accept the Client Identifier Prefix x509_hash". OpenID4VP (OpenID for Verifiable Presentations)Doğrulayıcının cüzdandan belge istediği ve gösterimi aldığı protokol. 1.0 §5.9.3: the value is the base64url SHA-256 digest of the DER encoding of the leaf X.509 certificate; the wallet verifies that the digest matches the leaf certificate, the signature and the certificate chain. Before this decision the Tamga 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. used x509_san_dns:<domain> (SPEC-PROTO-0002 §2; the 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. record and the ADR-0017 intermediary relationships were keyed on this string). ETSI TS 119 475 defines the erişim sertifikası (access certificate)Relying party'nin isteklerini imzaladığı X.509 sertifikası; client kimliği bu sertifikanın özetinden türetilir. as the x509_hash leaf (CIR 2026/1730).
  2. WIA (Wallet Instance Attestation)Cüzdan sağlayıcısının imzaladığı, cüzdan kurulumunun gerçek olduğunu bildiren kısa ömürlü beyan; belge veren, belgeyi vermeden önce denetler. sub. HAIP 1.0 §4.4.1: "The subject claim for the Wallet Attestation MUST be a value that is shared by all Wallet instances using the present type of wallet implementation"; moreover the client_id in the PAR (Pushed Authorization Request)Yetkilendirme isteğinin önce sunucuya gönderildiği OAuth adımı; içerik tarayıcı adresinde görünmez. is this sub value. The Tamga cüzdan sağlayıcısı (wallet provider)Cüzdanı sunan ve cüzdan ile anahtar kanıtlarını imzalayan kuruluş. Tamga Wallet ağın ilk cüzdanıdır. (apps/wallet-provider) wrote the fingerprint of a per-transaction ephemeral key as sub (ADR-0025 K2). That was fine for untraceability, but did not follow the letter of the rule.

On 2026-10-01 project management approved the EU path for both items.

Decision ​

K1 — Verifier client identifier x509_hash ​

The client_id of the signed request object is x509_hashHAIP'teki client kimliği biçimi: kimlik, relying party'nin erişim sertifikasının base64url SHA-256 özetidir.: + base64url(SHA-256(access certificate DER)). The value is never configured by hand: pemRpSigner(key, certificate) computes it from the certificate; so do the presentation requests of Tamga Verify and the institution issuers. The configuration files have no TAMGA_VERIFY_CLIENT_ID or TAMGA_ISSUER_RP_CLIENT_ID.

K2 — The wallet accepts only x509_hash ​

HAIP requires no other prefix; no backward compatibility is written in the development stage (ADR-0029). Wallet: if the prefix is not x509_hash, or the digest does not match the leaf certificate, the request is rejected. The security aspect of the x509_san_dns rule is kept: the domain of the response address (response_uri) must be one of the SAN domain names of the certificate that signed the request (except local development addresses). tamga_on_behalf_of (intermediated request, ADR-0017 K7) is also in x509_hash form.

K3 — Permanent identifier in the RP record: dns_name ​

The trust list relying_parties[] record:

  • client_id: computed by the publisher from the access certificate (x509_hash:…); not written in the registry source.
  • dns_name (mandatory): the permanent record identifier; must appear in the access certificate's SAN (the publisher checks; if not, publication stops). The registration tool (register rp, scope <dns_name>), intermediary relationships (uses_intermediaries, served_relying_parties) and kayıt sertifikası (registration certificate)Kayıt kurumunun, bir relying party'nin hangi alanları hangi amaçla isteyebileceğini bildiren imzalı belgesi (en çok 12 ay). production work with this field. TrustSource.relyingPartyByDnsName().

K4 — What must stay independent of the certificate is bound to dns_name ​

  • Copy separation (SPEC-WALLET-0001 WL5) and pseudonym derivation (ADR-0031 K2): once the record is resolved and the certificate matches, the wallet maps rpKey to the record's dns_name (stableRpKey). The verifier checks the pseudonym against the same value. For an unregistered RP (Relying Party)Relying party'nin kısaltması: belge isteyen ve doğrulayan kuruluş. client_id remains (no pseudonym is issued anyway).
  • Pass card (ADR-0012 B): the aud of the card token is the RP's dns_name (the card is valid for days, QR ≤ 400 bytes; x509_hash is both long and changes on renewal).
  • Log: the RP identifier in the presentation record is dns_name; the wallet also resolves the record by domain name.
  • Ticket (EventTicket.gate.verifier_client_id): the field name stays (no schema rename in the development stage); its value is the verifier's dns_name — tickets are long-lived.

K5 — WIA sub as a shared value ​

In the WIA the wallet provider sets sub = the wallet solution's identifier (solution_id, e.g. tamga-wallet-expo). There is no instance-specific value; what distinguishes instances is only the new cnf key and the new, unlinkable 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. entry in every transaction (ADR-0025 K2 unchanged). The client_id in the PAR and the iss of the PoP are this value. The wallet_sub the institution records points to the solution, not the person (less information, better privacy).

Invariants ​

CodeRule
CI1In a signed request the verifier uses only the x509_hash client identifier; the value is computed from the access certificate, not read from configuration (HAIP 1.0 §5).
CI2The wallet accepts only the x509_hash prefix; if the digest does not match the leaf certificate the request is rejected. The response address's domain must be in the signing certificate's SAN (except local development).
CI3In the trust list RP record, client_id is computed by the publisher from the access certificate; the permanent record identifier is dns_name, which must appear in the certificate's SAN.
CI4Copy separation, pseudonym derivation, the presentation log and intermediary relationships are bound to dns_name, not to x509_hash.
CI5The aud of a pass card token is the RP's dns_name.
CI6The WIA sub is a value shared by all instances of the same wallet solution; it carries no instance-specific identifier (HAIP 1.0 §4.4.1).

Rationale / alternatives ​

OptionResultWhy
Staying with x509_san_dnsrejectedHAIP 1.0 §5 requires the verifier to use x509_hash; EU wallets must accept only that.
Accepting both prefixesrejectedHAIP does not require the second; two paths mean two sets of security checks. Development stage (ADR-0029).
Binding pseudonyms / copies to x509_hashrejectedEvery pseudonym (account) and pass card would change when the certificate is renewed.
rp_id as the permanent identifierrejectedrp_id is also derived from the certificate (keccak256(country ‖ SHA-256(certificate))).
dns_name (the domain in the SAN)acceptedAlready in the record (the x509_san_dns string); independent of the certificate; the publisher can check consistency with the SAN.
WIA sub = ephemeral key fingerprintrejectedUntraceable, but against the letter of HAIP; an EU institution expects a shared value in the PAR.

Consequences / operational impact ​

  • Certificate renewal order: first the new access certificate is entered into the registry source and the list is republished (the new client_id in the list), then the verifier switches to the new certificate. If the certificate changes before the list is published, the wallet rejects the request as "not registered / impersonation". Pseudonyms, copies and pass cards are not affected (K4). Registration certificates (ADR-0026) are regenerated on every publication.
  • Registration tool: the application carries dns_name, not client_id; scope <dns_name>.
  • Conformance vectors: dns_name in the RP record, client_id as x509_hash.
  • ZK experiment fixture (packages/verifier/src/zk/fixtures/session.json): the client_id string in the session transcript stays in x509_san_dns form (the proof was produced with that string; the string itself is not checked).

Status ​

Accepted — 2026-10-01 (approved by project management). Implemented: core (x509HashClientId), trust (schema + relyingPartyByDnsName), trust-publisher, verifier (signer, pass card), wallet-core (request verification, stableRpKey, fetchRpRecord), wallet, Tamga Verify, wallet provider, platform issuer.