{"id":"recht-op-regeling-v1","built":true,"verificationKeyUrl":"https://zkp-circuit-registry.administratieomgeving.nl/circuits/recht-op-regeling-v1/verification-key.json","wasmUrl":"https://zkp-circuit-registry.administratieomgeving.nl/circuits/recht-op-regeling-v1/circuit.wasm","verificationKeySchemaProtocol":"groth16","verificationKeySchemaCurve":"bn128","readme":"# Circuit recht-op-regeling-v1\n\n**Wat dit circuit bewijst**: dat een burger aan drie voorwaarden van een regeling voldoet, zonder dat de verifier weet welke individuele voorwaarden waar zijn of op welke onderliggende data zij gebaseerd zijn. Output is één boolean: `recht_op = (voorwaarde_1 AND voorwaarde_2 AND voorwaarde_3)`.\n\n**Status**: **compleet end-to-end-werkend per 2026-05-27**. Browser-bevestiging door Gijs: scan QR op balie-pagina → wallet opent automatisch → bewijs gegenereerd → bewijs verstuurd naar balie-callback → balie verifieert via snarkjs.verify → stem anoniem geregistreerd → QR-pagina toont \"Stem geregistreerd\". Anti-double-vote-laag-1 (admin-backend uniqueness) plus laag-2 (balie cnf-DID-tracking) werken. Laag-3 (cryptografische nullifier via ZkpUniquenessSeed) blijft open als productie-upgrade-pad, niet in MVP. Onderdeel van Sub-stap 10g uit [`../../../../docs/uitbreidingen/implementatie-rule-and-principle-inferencing/plan.md`](../../../../docs/uitbreidingen/implementatie-rule-and-principle-inferencing/plan.md). Vervangt het leeftijds-21-circuit als hoofd-demo-vehikel; leeftijds-21-circuit blijft staan als hello-world-introductie.\n\n**Onderdeel van**: het generieke composiet-bewijs-pattern conform Sub-vraag 2.6 uit [`../../../../docs/uitbreidingen/research-rule-and-principle-inferencing/02-techniek-invulling.md`](../../../../docs/uitbreidingen/research-rule-and-principle-inferencing/02-techniek-invulling.md). Een circuit, meerdere regelingen herbruikbaar.\n\n## Waarom een generiek-composiet-circuit\n\nHet circuit zelf weet niets van de regeling-context. De regeling-id, regeling-naam en welke voorwaarden de drie inputs precies representeren komen in de uitgegeven SD-JWT-VC als gewone claims, niet in het circuit. Dat maakt dit ene circuit herbruikbaar voor meerdere regelingen door de tijd heen. Voor demo-iteratie 1 wordt het ingevuld als anonieme buurt-consultatie (zie hieronder); voor latere regelingen kan dezelfde circuit-binary gebruikt worden met andere claim-content.\n\n## Eerste demo-context, anonieme buurt-consultatie 2026-05-25\n\nGijs heeft op 2026-05-25 expliciet aangegeven dat ZKP-toepassingen die de \"ben je X\"-vraag-pattern volgen (leeftijd-voor-bier, energieverbruik-voor-subsidie, eligibility-voor-toeslag) altijd op het risico-pad zitten naar een surveillance-cultuur. De macht-pijl wijst in dat pattern van verifier naar burger. Voor de eerste demo van dit composiet-circuit kiezen we voor een use-case waar de macht-pijl OMGEKEERD wijst: burger initieert, burger beschermt eigen anonimiteit, verifier moet enkel kunnen vertrouwen op de echtheid van haar stem.\n\nConcreet: gemeente Diplo houdt een consultatie met vraag \"Wat is uw mening over de evt. aanleg van een extra brug bij de Grachtenlaan\", met stem-opties voor / tegen / neutraal (niet verplicht) plus een vrij-tekst-invulveld. Alleen wijk-bewoners van een stemgerechtigde leeftijd mogen meedoen, en elke stem mag maar één keer. Burger bewijst \"ik voldoe aan de deelname-eisen\" zonder dat de gemeente weet welke specifieke voorwaarden hij of zij vervult of hoe haar onderliggende data eruitziet.\n\nDrie voorwaarden in deze eerste consultatie-demo:\n\n| Voorwaarde | Wat het circuit krijgt als input | Hoe admin-backend dit afleidt |\n|---|---|---|\n| Voorwaarde 1: wijk-bewoner van de consultatie-wijk | Boolean (0 of 1) | Leest woonplaats-claim uit PID-mdoc van de burger, vergelijkt met de consultatie-context (`anonieme-consultatie-brug-grachtenlaan-diplo-2026` betreft wijk \"Diplo\") |\n| Voorwaarde 2: stemgerechtigd door leeftijd (18 jaar of ouder) | Boolean (0 of 1) | Leest birthdate-claim uit PID-mdoc, vergelijkt met huidige datum minus 18 jaar |\n| Voorwaarde 3: niet eerder gestemd voor deze consultatie | Boolean (0 of 1) | Server-state-check tegen `consultatie_stem`-tabel in admin-backend's DB met unieke index op `(userId, consultatieId)`. Voor productie-volledigheid komt een nullifier-pattern, zie sectie \"Anti-double-vote-architectuur\" hieronder. |\n\n## Inputs en outputs\n\n### Privaat (witness, alleen op de prover bekend)\n\n| Signaal | Type | Betekenis |\n|---|---|---|\n| `voorwaarde_1` | 0 of 1 | Eerste voorwaarde voldaan (boolean) |\n| `voorwaarde_2` | 0 of 1 | Tweede voorwaarde voldaan (boolean) |\n| `voorwaarde_3` | 0 of 1 | Derde voorwaarde voldaan (boolean) |\n\n### Output (publiek)\n\n| Signaal | Type | Betekenis |\n|---|---|---|\n| `recht_op` | 0 of 1 | 1 als alle drie waar, anders 0 |\n\n### Geen publieke inputs\n\nHet circuit gebruikt geen publieke inputs naast de output. Regeling-context wordt op het SD-JWT-VC-claim-niveau toegevoegd, niet in het circuit zelf. Dat houdt het circuit generiek voor toekomstige regelingen.\n\n## Hoe het circuit de logica berekent\n\nDrie stappen:\n\n1. Voor elke voorwaarde-input dwingen we af dat hij strikt 0 of 1 is via een boolean-constraint `vw_i * (vw_i - 1) === 0`. Zonder deze constraint zou een prover kunnen smokkelen met veld-elementen als 2 of 5 om de logica te omzeilen.\n2. `twee_voldoen = voorwaarde_1 * voorwaarde_2` (AND van eerste twee).\n3. `recht_op = twee_voldoen * voorwaarde_3` (AND met derde).\n\nEen AND-van-drie-booleans is het PRODUCT van de drie booleans, want 1 * 1 * 1 = 1 en elk ander geval geeft 0.\n\n## Bestanden in deze folder\n\n| Bestand | Wat het is | Wel in git |\n|---|---|---|\n| `circuit.circom` | Circom-bron van het circuit | ja |\n| `build.sh` | Compile-script plus trusted-setup-runner | ja |\n| `test-prove.mjs` | Snapshot-test met vier proeven, ES-module-extensie | ja |\n| `README.md` | Dit document | ja |\n| `.gitignore` | Sluit gegenereerde tussen-artefacten uit | ja |\n| `circuit_js/circuit.wasm` | Prover-runtime in WebAssembly | **ja, sinds eerste build** |\n| `circuit_final.zkey` | Proving-key met mock-ceremony-bijdrage | **ja, sinds eerste build** |\n| `verification_key.json` | Verification-key voor de verifier | **ja, sinds eerste build** |\n| `hashes.txt` | SHA-256 van de gegenereerde artefacten | **ja, sinds eerste build** |\n| `pot12.ptau` | Powers-of-Tau setup-file | nee, downloaden of symlink naar `../leeftijd-gte-21-v1/pot12.ptau` |\n| `circuit.r1cs` | Tussenliggend, gegenereerd door circom-compile | nee, gegenereerd |\n| `circuit.sym` | Symbool-tabel | nee, gegenereerd |\n| `circuit_0000.zkey` | Tussenliggende zkey | nee, gegenereerd |\n| `node_modules/` | Lokale snarkjs-deps | nee |\n| `package.json`, `package-lock.json` | Lokale npm-config | nee |\n\n## Lokaal compileren en testen\n\nZelfde setup-stappen als bij [`../leeftijd-gte-21-v1/README.md`](../leeftijd-gte-21-v1/README.md). Wanneer je de leeftijds-circuit-setup al hebt staan, kun je hier de pot12.ptau hergebruiken via een symlink in plaats van apart downloaden:\n\n```bash\ncd eco/voorzieningen/stelselvoorziening-bewijs-circuits-register/circuits/recht-op-regeling-v1/\nln -s ../leeftijd-gte-21-v1/pot12.ptau pot12.ptau\nbash build.sh\nnode test-prove.mjs\n```\n\nVerwachte uitvoer van `node test-prove.mjs`: vier proeven slagen achtereenvolgens, met \"Alle proeven geslaagd\" aan het einde.\n\n## Terminologie bij de nullifier-discussie\n\nIn de discussie van 2026-05-25 zijn drie verschillende identifiers door elkaar gebruikt geweest. Voor toekomstige lezers helder:\n\n| Term | Wat | Wie weet het | Wanneer komt het tot stand |\n|---|---|---|---|\n| **burger-seed** | Geheim random 256-bit getal | Alleen de burger (in wallet) plus de wallet-stichting (heeft hem uitgegeven) | Eenmalig bij wallet-onboarding via de stichting |\n| **consultatie-id** | Publieke naam van één specifieke stemming, bv. `anonieme-consultatie-brug-grachtenlaan-diplo-2026` | Iedereen | Gemeente publiceert per stemming |\n| **verifier-domain** | Publieke naam van de ontvangende organisatie, bv. `diplo.administratieomgeving.nl` | Iedereen | Bestaat al per mock-org |\n\nAlleen het **burger-seed** is geheim. De andere twee zijn gewoon publieke etiketjes die in de nullifier-hash worden meegenomen om de output context-specifiek te maken.\n\nDe nullifier-formule wordt dan ofwel:\n\n- `nullifier = HASH(burger-seed, consultatie-id)` voor de basis-variant\n- `nullifier = HASH(burger-seed, verifier-domain, consultatie-id)` voor cross-gemeente-veiligheid (productie-suggestie uit de DB-CRL-spec, Open keuze 2 in plan.md)\n\n## Twee soorten revocation bij ZKP-credentials\n\nTijdens de chat-discussie op 2026-05-25 bleek dat het woord \"revocation\" twee fundamenteel verschillende dingen kan betekenen die door elkaar geraakten:\n\n| Soort revocation | Wat wordt ingetrokken | Door wie | Wanneer relevant |\n|---|---|---|---|\n| 1, ZKP-bewijs-revocation | Het uitgegeven RechtOpRegelingBewijs zelf | De wallet-stichting (issuer van het ZKP) | Bij seed-misbruik, of als de stem-geldigheid wordt ingetrokken |\n| 2, onderliggende-VC-revocation | De PID-mdoc of een andere VC waarop het ZKP is gebaseerd | De PID-uitgever (gemeente of equivalent) | Wanneer de onderliggende claim niet meer klopt, bv. burger verhuist uit de wijk |\n\n**Beide moeten via een privacy-vriendelijke aanpak kunnen worden gecheckt door de verifier zonder dat er een correlateerbare identifier lekt**. DB-CRL biedt het pattern voor beide, want het is een revocation-mechanisme dat zonder identifier-leak werkt.\n\nVoor onze use-case is soort 2 (onderliggende VC) de meest relevante. Een burger maakt het ZKP op dag 1 met haar PID-mdoc-claim \"woonplaats Diplo\". Verhuist zij op dag 2, dan trekt de PID-uitgever haar oude PID in. Het ZKP bevat geen verwijzing meer naar de PID, dus de gemeente weet niet dat het inmiddels op een ingetrokken PID is gebaseerd. Met een DB-CRL-aanpak op de PID's revocation-id wordt dit opvangbaar: het ZKP bewijst tegelijk dat de onderliggende PID nog actief is op moment van prove, plus de verifier kan later opnieuw checken zonder identifier-leak.\n\nConcrete implementatie-implicatie: voor case 2 en case 3 uit Sub-stap 10i moeten we in de circuit-uitbreiding ook de PID's revocation-id meenemen als private input plus een domain-bound revocation-hash als publieke output. Komt aan bod bij de implementatie-keuze R3 in Open keuze 3 (plan.md).\n\n## Anti-double-vote-architectuur\n\nDrie lagen samen verminderen het double-vote-risico tot bijna nul. Twee lagen zijn (deels) gebouwd voor de demo, één is een open productie-upgrade-pad omdat hij een stabiele wallet-master-secret vereist die het MVP nog niet heeft.\n\n**Laag 1, admin-backend weigert tweede uitgifte**: TypeORM-entity `consultatie-stem.entity.ts` plus migration `1779999000000-CreateConsultatieStem.ts` in `apps/backend-administratie/src/rekenregels/`. Unieke index op `(userId, consultatieId)`. Tweede uitgifte-aanvraag voor dezelfde combo geeft 409 (`AlEerderGestemdError` in `core/recht-op-regeling-service.ts`). Beperking: een burger met meerdere wallet-accounts (verschillende `userId`-waarden) kan via verschillende accounts twee uitgiftes ontvangen.\n\n**Laag 2, mock-gemeente-diplo-balie detecteert dubbele presentatie**: balie-server houdt een lijst van eerder ontvangen cnf-binding-pseudoniem-DIDs bij per consultatie-id in de in-memory stem-store (`lib/consultatie-store.js`). Een tweede presentatie van een bewijs met dezelfde cnf-DID wordt geweigerd via `AlEerderGestemdError` (HTTP 409). **Gebouwd plus end-to-end-werkend per 2026-05-27** (commits `ef945f85b` plus opvolgende fixes). Vangt af dat hetzelfde bewijs twee keer aan dezelfde balie wordt aangeboden, maar niet twee bewijzen die via verschillende uitgifte-pads zijn ontstaan met verschillende cnf-DIDs.\n\n**Laag 3, cryptografische nullifier in het circuit (productie-upgrade-pad, alle keuzes vastgesteld 2026-05-25)**: het circuit krijgt een vierde private input voor het `ZkpUniquenessSeed`-secret plus een vijfde private input voor de `consultatie_id` plus een zesde voor het `verifier_domain`. Het circuit berekent dan een deterministische `nullifier = HASH(seed, verifier_domain, consultatie_id)` als extra publieke output (dubbel-binden voor cross-gemeente-veiligheid, vastgestelde keuze 2). De verifier (mock-gemeente-diplo) houdt een nullifier-list per consultatie bij; een tweede inkomende bewijs met een al-bekende nullifier wordt geweigerd. Hiermee is een dubbele stem cryptografisch onmogelijk ongeacht aantal accounts, aantal uitgifte-pads of aantal verifier-instances, EN de verifier leert nog steeds geen identiteit (alleen \"deze nullifier kwam al voor\"). Niet in MVP omdat het circuit-complexiteit verhoogt met een Poseidon-hash-component plus extra constraint-set, plus omdat de keuze WAAR het geheim leeft nog niet gemaakt is. Onder verschillende opties analyseerd in de sub-sectie hieronder.\n\n### Sub-keuze, waar leeft het geheim voor de nullifier\n\nIn de chat-discussie tussen Gijs plus Claude op 2026-05-25 is doorgewerkt op vier opties voor de bron van het per-burger-stabiele nullifier-geheim. Aanleiding: de waarschuwing dat **elke afleiding-uit-alleen-identiteits-data fundamenteel niet werkt** wanneer de verifier zelf toegang heeft tot die identiteits-data, zoals een Nederlandse gemeente via de Basisregistratie Personen (BRP). Een gemeente kan dan een **gerichte lookup-attack** doen: voor elke ingeschreven burger in haar wijk de hash-formule invullen plus vergelijken met de nullifier-lijst, daarmee de gestemde-burger identificeren in seconden. Dat breekt de non-traceability waarvoor het hele ZKP-pattern is opgezet.\n\nConclusie van de discussie: het geheim moet uit een bron komen die NIET door de verifier kan worden gereproduceerd. Drie soorten geheim-bronnen werken in theorie, vergelijking hieronder.\n\n| Optie | Geheim-bron | Multi-device-pijn | Trust-locatie | Schatting werk |\n|---|---|---|---|---|\n| A, niets bouwen, alleen MVP-disclaimer | n.v.t. | n.v.t. | n.v.t. | 0 |\n| B1, mock-nullifier uit PID-claims plus wallet-salt | localStorage random salt plus PID-mdoc-claims | Salt is per-wallet, niet gesynct | Geen extra trust-partij | 1 sessie |\n| C1 of C2, WebAuthn-PRF-output | Per-passkey hardware-secret | Twee passkeys (laptop plus telefoon) geven twee verschillende PRF-outputs tenzij passkey-sync werkt | Hardware (sterk) | 1 tot 2 dagen |\n| **D, stichting-uitgegeven ZkpUniquenessSeed-credential** | **Eén-time-uitgegeven 256-bit random secret in een VC** | **Nee, credential-sync via bestaande wallet-encrypted-backup-keten werkt automatisch** | **Wallet-stichting (juridisch-aanvaardbaar)** | **~1,5 dag** |\n\n**Optie B1** valt af omdat een gemeente met BRP-toegang de PID-claims kent plus alleen het wallet-salt niet, en het wallet-salt zelf is per-wallet random dus multi-device-bypass is mogelijk (twee wallets van dezelfde burger = twee salts = twee nullifiers).\n\n**Optie C1 of C2 (passkey-PRF)** is cryptografisch sterk maar lijdt aan dezelfde multi-device-issue: één persoon met passkey op laptop plus passkey op telefoon krijgt twee verschillende PRF-outputs, dus twee verschillende nullifiers. Tenzij de passkey op beide devices hetzelfde fysieke geheim bevat (iCloud Keychain, Google Password Manager cross-device-sync, of een single hybrid-transport-passkey die voor beide devices wordt gebruikt). Onbetrouwbaar als de wallet beide multi-passkey-paden moet ondersteunen.\n\n**Optie D (stichting-ZkpUniquenessSeed-credential)** lost het multi-device-issue principieel op want het seed-secret leeft als gewone Verifiable Credential in de wallet, en credentials worden al gesynchroniseerd over devices via de bestaande encrypted-backup-keten. Anti-multi-account-bescherming komt van de stichting-onboarding-flow: bij eerste uitgifte van de `ZkpUniquenessSeed`-credential wordt PID-verificatie gedaan, een tweede uitgifte-poging aan dezelfde fysieke persoon wordt geweigerd. Trust verplaatst zich naar de wallet-stichting (die toch al wallet-uitgever is); de gemeente blijft buiten de trust-keten voor het seed-secret.\n\n**Aanbeveling vastgesteld 2026-05-25 (definitief, alle drie de architectuur-keuzes vastgesteld in chat met Gijs op 2026-05-25)**: **Optie D, stichting-uitgegeven ZkpUniquenessSeed-credential** met dubbel-binden van de nullifier-formule (`nullifier = HASH(seed, verifier_domain, consultatie_id)`) plus revocation-aanpak R3 (DB-CRL-stijl) voor de onderliggende VC's. Volledige drie keuzes in plan.md sectie \"Vastgestelde architectuur-keuzes uit Sub-stap 10g plus 10i, besluiten van 2026-05-25\". Reden: lost de multi-device-pijn principieel op door hergebruik van bestaande wallet-sync-infrastructuur; verplaatst trust naar een natuurlijke rol (wallet-stichting is toch al wallet-uitgever); cryptografisch hard tegen verifier-met-BRP-toegang; werk-schatting vergelijkbaar met de PRF-route maar zonder de multi-device-engineering.\n\n#### Web-search-validatie 2026-05-25\n\nOp 2026-05-25 is een general-purpose-sub-agent ingezet om de aanbeveling te toetsen aan productie-systemen plus academisch onderzoek. Uitkomst: de aanbeveling wordt bevestigd.\n\n**Bijna identieke architectuur in EU-academisch onderzoek**: SmartphoneDemocracy (arXiv 2507.09453, gepubliceerd 2025) beschrijft exact het pattern dat wij voorstellen: een Verifier verifieert de eIDAS-PID off-chain, geeft een random `secret_id` terug als credential, de burger berekent vervolgens on-chain `nullifier = H(secret_id, election_id)`. Het enige verschil met onze D is dat zij BBS-signatures gebruiken voor multi-show-unlinkability van het secret-credential zelf, terwijl wij SD-JWT-VC gebruiken. Functioneel voor anti-double-vote is dat gelijkwaardig want het seed-credential wordt in onze setup nooit aan een derde partij gepresenteerd, alleen door de wallet lokaal gebruikt als private input voor het zk-circuit. Bron: [arXiv 2507.09453](https://arxiv.org/html/2507.09453v1), met cross-referenties naar [Making BBS eIDAS-compliant (eprint 2025/619)](https://eprint.iacr.org/2025/619.pdf) plus [SoK Anonymous Credentials EUDI (eprint 2026/330)](https://eprint.iacr.org/2026/330.pdf).\n\n**Productie-systemen die seed-with-backup-pattern al jaren draaien**: Zcash gebruikt een single binary seed via [ZIP-32 hierarchische derivatie](https://zips.z.cash/zip-0032) met client-side generatie plus seed-backup voor multi-device-recovery, geen trust-partij. Worldcoin / World ID 4.0 gebruikt een random private-key on-device bij Orb-enrollment plus ondersteunt expliciet meerdere authenticators per identity die dezelfde deterministische nullifier produceren. Tornado Cash gebruikt browser-generated random secret plus een \"secret note\"-backup met MetaMask-derived encryption voor cross-device-toegang. Alle drie tonen aan dat het pattern in productie schaalt over miljoenen gebruikers. Bronnen: [ZIP-32 Zcash](https://zips.z.cash/zip-0032), [World ID 4.0 specs](https://github.com/worldcoin/world-id-protocol/blob/main/docs/world-id-4-specs/README.md), [Tornado on-chain backups](https://tornado-cash.medium.com/tornado-cash-adds-on-chain-deposit-backups-dbef9ac9e61d).\n\n**Semaphore V4 (PSE, Ethereum Foundation)** gebruikt geen issuer voor het geheim maar een client-side EdDSA-keypair plus een Merkle-tree-group, en de [documentatie waarschuwt expliciet dat backup-en-recovery niet-triviaal is](https://docs.semaphore.pse.dev/guides/identities). Vocdoni (anonymous voting framework gebruikt door [Aragon DAO-stack](https://blog.vocdoni.io/anonymous-voting-zksnarks)) gebruikt een census-merkle-tree-aanpak met `nullifier = hash(signature + password + electionID)`. Beide werken op blockchain-context; voor onze NL-gemeente-context past de stichting-route uit D beter omdat zij de PID-verificatie aansluit op een bestaande wallet-onboarding-flow.\n\n**Twee productie-trade-offs die de agent noemt, voor latere productie-iteratie**:\n\n| Trade-off | Risico | Mitigatie (latere productie-iteratie) |\n|---|---|---|\n| Single point of compromise bij de stichting | Als de stichting de seed-uitgifte-DB lekt en de gemeente komt eraan, kan zij alsnog elke nullifier reproduceren via gerichte lookup | Stichting hasht het uitgegeven seed direct na uitgifte uit haar eigen state, of stapt over op blind-issuance via BBS zodat zij het seed nooit in plaintext kent (eprint 2025/619-pattern) |\n| Multi-wallet-provider-bypass | Als een burger via twee wallet-providers twee PID-tonen-acties doet, krijgt zij twee verschillende seeds plus twee verschillende nullifiers per consultatie | Vereist nationale PID-uniciteits-garantie via BSN; in NL praktisch hard, in cross-border-EUDI-context potentieel zwakker |\n\nBeide trade-offs zijn voor onze demo niet kritiek. Vermelden wel in de modal-tekst plus in de demo-narratief.\n\n**Twee paden die de agent vond maar voor ons niet relevant zijn**:\n\n- **MACI's key-change-mechanisme** ([maci.pse.dev](https://maci.pse.dev/docs/core-concepts/key-change)) biedt anti-bribery door een burger zijn keypair op elk moment cryptografisch te laten vervangen, zodat een briber nooit zeker weet of een gekochte stem nog telt. Overkill voor een gemeente-consultatie zonder hoog bribery-risico.\n- **Issuer-hiding BBS** ([eprint 2026/369](https://eprint.iacr.org/2026/369.pdf)) verbergt zelfs welke wallet-stichting het seed-credential heeft uitgegeven. In onze NL-context met één stichting niet nodig.\n\n**Conclusie web-search**: aanbeveling D is solide. Status verhoogd van \"concept\" naar \"gevalideerd, pending implementatie-akkoord\". Wachten op Gijs's groen-licht voor een aparte implementatie-sessie.\n\n#### Twee primaire bronnen toegevoegd 2026-05-25\n\nNa de web-search heeft Gijs twee specifieke bronnen aangedragen die direct relevant zijn voor de productie-uitwerking van aanbeveling D.\n\n**Bron 1, Horvat et al. (OID 2026 paper, doi 10.18420/OID2026_09)**: peer-reviewed paper \"Tensions Between Data Minimisation and Legal Proof Obligations: Zero-Knowledge Proofs in the EUDI Wallet\" door Alen Horvat (Netis, ook auteur van de DB-CRL-spec hieronder), Steffen Schwalm (msg group), Johannes Sedlmeir (University of Münster), Hakan Yildiz (interID UG). Gepubliceerd in Lecture Notes in Informatics, Gesellschaft für Informatik, Bonn 2026.\n\nInhoud relevant voor ons:\n\n- **Twee verificatie-modi onderscheiden**: point-in-time-verification (zoals onze buurt-consultatie, gemeente checkt alleen tijdens stemperiode) versus post-presentation-verification (zoals KYC, employment-status, identity-credentials, waar de verifier jaren later opnieuw moet kunnen checken).\n- Conclusie: standaard-ZKPs werken alleen voor point-in-time; voor post-presentation is een aanvullend mechanisme nodig.\n- **Drie oplossingsrichtingen** in sectie 5: (5.1) Domain-Bound Revocation Lists, (5.2) Adapting Preservation Standards (ETSI TS 119 511, BSI TR-03125 moeten ZKPs formeel erkennen), (5.3) Regulatory Alignment (procedurele rules moeten ZKPs als evidentiary erkennen).\n- Verwijst naar ETSI TR 119 476 als de officiële ETSI-feasibility-study voor selective-disclosure-plus-ZKP. Voor onze productie-aansluiting verplichte literatuur.\n\nRelevantie voor onze use-case: bevestigt dat onze buurt-consultatie in de **point-in-time-categorie** valt (waar standaard-ZKPs voldoende zijn) en dat we geen post-presentation-mechanisme nodig hebben voor de demo. De stem heeft alleen geldigheid tijdens de consultatie-periode.\n\n**Bron 2, DB-CRL spec (MyNextID/eudi-zk, versie 0.2)**: implementatie-spec voor Domain-Bound Credential Revocation List. URL: [github.com/MyNextID/eudi-zk/blob/main/specs/crl-domain-bound.md](https://github.com/MyNextID/eudi-zk/blob/main/specs/crl-domain-bound.md). Auteur Alen Horvat (zelfde als bron 1).\n\nInhoud relevant voor ons:\n\n- **Het wiskundige pattern** dat wij voor onze nullifier hergebruiken: `domain_rev_id = SHA256(revocation_id || domain)` met `revocation_id` als 32-byte secret en `domain` als context-string. Voor ons: `nullifier = SHA256(seed || consultatie_id)`. Zelfde wiskundige bouwsteen, andere semantische toepassing (revocation vs anti-double-vote).\n- **Exacte crypto-keuze** direct overneembaar: SHA-256 als hash, 32-byte random secret, UTF-8-encoding voor string-inputs, cryptografisch-secure random generator voor secret-generatie.\n- **Domain canonicalization** (sectie 6): vereist dat verschillende representaties van dezelfde context-string naar één canonical string worden gebracht voor de hash. Voor ons: we moeten een canonicalization-conventie kiezen voor `consultatie_id`.\n- **Twee bekende collusion-risico's** expliciet benoemd: timing-analyse bij newly-revoked-credential (mitigatie: batch of pad), plus issuer-verifier-collusion (mitigatie: verifier deelt alle observed `domain_rev_id`-waarden niet met issuer, of issuer kent het secret niet via blind-issuance).\n- **Returning-user-detectie** als feature/limitatie: zelfde (secret, context) geeft zelfde output. Voor DB-CRL-use-case (revocation) een limitatie. Voor onze use-case (anti-double-vote) precies het gewenste gedrag.\n- **Tijd-limiterende extensie** (sectie 8.1): `time_bound_rev_id = HASH(domain_rev_id || time_index)` voor verifier-toegang binnen een tijdsvenster. Niet nodig voor MVP, wel productie-uitbreidingsoptie.\n- **Zuster-specs** in dezelfde repo: [Mini CRL](https://github.com/MyNextID/eudi-zk/blob/main/specs/crl-range-proof.md) voor presentation-time-only verification (simpeler ZKP-pattern), plus [Temporal Validation within ZKPs](https://github.com/MyNextID/eudi-zk/blob/main/specs/temporal-validation-zkp.md) voor temporal-validity-proofs.\n\nRelevantie voor onze use-case: levert een **production-ready cryptografische specificatie** die we direct kunnen volgen voor de circuit-uitbreiding. Spaart ons design-werk plus voorkomt dat we eigen wielen uitvinden.\n\n**Productie-suggestie uit de bronnen-vergelijking**: dubbel-binden van de nullifier aan zowel verifier-domain als consultatie-id, dus `nullifier = SHA256(seed || verifier_domain || consultatie_id)`. Voor cross-gemeente-veiligheid (als twee gemeenten toevallig dezelfde consultatie-id zouden kiezen). In onze huidige id-conventie (`...-diplo-2026` bevat al de gemeente-naam) niet kritiek, maar netter als expliciete crypto-keuze.\n\n**Naamgeving-keuze vastgesteld 2026-05-25**: `ZkpUniquenessSeed` als code-naam (camelCase conform `Leeftijdsbewijs`, `RechtOpRegelingBewijs`), mens-leesbaar UI-label `ZKP Uniqueness Seed` met spaties, URN `urn:nl.administratieomgeving:zkp-uniqueness-seed:1`. Afgewezen alternatieven: `NullifierSeed` (te jargon-bevangen), `DeelnameSeed` (te functioneel-eng), `VotingSeed` (zelfde bezwaar).\n\n#### Revocation-keuze voor RechtOpRegelingBewijs zelf, separaat van de nullifier\n\nTijdens dezelfde chat op 2026-05-25 vroeg Gijs hoe wij momenteel revocation hebben geregeld voor de RechtOpRegelingBewijs-credential zelf (dus: kan de gemeente checken of het bewijs is ingetrokken sinds uitgifte). Eerlijk antwoord: in MVP geen expliciete keuze gemaakt. Standaard SD-JWT-VC ondersteunt een `status_list`-pointer (OAuth Status Lists) waar elke credential een persistente index krijgt in een publieke lijst. Twee verifiers die dezelfde credential ontvangen zien dezelfde index, dat is een correlatie-leak die de DB-CRL-spec hierboven juist probeert op te lossen.\n\nVoor onze buurt-consultatie-use-case heeft de bron 1 (Horvat et al.) een belangrijke implicatie: een consultatie valt in de point-in-time-categorie. De gemeente hoeft niet jaren later het bewijs opnieuw te kunnen verifiëren, alleen tijdens de stemperiode. Daarmee zijn er drie opties voor de revocation-aanpak:\n\n| Optie | Wat | Trade-off |\n|---|---|---|\n| R1, geen status_list voor RechtOpRegelingBewijs, alleen `exp`-claim op einde-consultatie-datum | Simpel; geen revocation-correlatie-leak | Bewijs kan niet ingetrokken worden tijdens consultatie, bv. als seed-misbruik wordt ontdekt |\n| R2, standaard status_list met per-bewijs verse index | Revocation mogelijk; index is correlateerbaar tussen verifiers, maar in onze setup is er alleen één verifier per consultatie | Lekt index naar gemeente, die toch al verifier is voor de stem |\n| R3, DB-CRL-stijl domain-bound revocation | Productie-grade, geen correlatie-leak; conform Horvat et al. 2026 voor post-presentation-cases | Extra circuit-complexiteit, niet nodig voor onze korte-duur-use-case |\n\n**Concept-aanbeveling**: **R1 voor MVP** want buurt-consultatie heeft een natuurlijke einddatum plus geen jaren-bewaartermijn. R3 alleen overwegen wanneer we naar productie gaan voor use-cases die wel post-presentation-jaren-verificatie vereisen (KYC, employment). Open keuze, wacht op Gijs.\n\n**Implementatie-pad Optie D, alle architectuur-keuzes vastgesteld 2026-05-25 plus klaar voor implementatie-sessie**:\n\n1. Nieuwe credential-type `ZkpUniquenessSeed` in vct-registry-voorziening met URN `urn:nl.administratieomgeving:zkp-uniqueness-seed:1`, met één claim `seed` (256-bit random).\n2. Stichting-onboarding-flow uitbreiden met seed-generatie plus eenmalige uitgifte na PID-verificatie.\n3. Wallet leest seed bij prove plus geeft door aan admin-backend in de uitgifte-call (of bij client-side prove rechtstreeks aan circuit).\n4. Circuit-uitbreiding met Poseidon-hash plus extra inputs en `nullifier`-output.\n5. Mock-gemeente-diplo-balie nullifier-list per consultatie.\n\nOpen vraag: of de seed in admin-backend kortstondig zichtbaar is (admin-backend-prove-pad) of alleen in browser (client-side-prove-pad). Sluit aan op de scope-discussie C1 versus C2 voor de PRF-variant.\n\n## Volgende stappen in Sub-stap 10g\n\n**Gereed per 2026-05-25** (vier commits):\n\n- Vct-registry-entry voor het credential-type `RechtOpRegelingBewijs` met claims `recht_op`, `proof`, `circuit_id`, `circuit_version`, `regeling_id`, `regeling_naam_kort`. Commit `c770c4153`.\n- Begrippenkader-entry voor `recht-op-regeling` als generiek-begrip. Commit `c770c4153`.\n- Admin-backend service-laag plus HTTP-endpoint voor de uitgifte: leest PID-mdoc claims, doet de drie voorwaarden-check, roept zkp-prover aan met de drie booleans als witness, geeft RechtOpRegelingBewijs uit via wallet-EUDIPLO. Plus `consultatie_stem`-tabel plus anti-double-vote-laag-1. Commit `0bc2692b5`.\n- Wallet-frontend \"Recht op deelname (anonieme consultatie)\"-kaartje in `CircuitBewijzenSection.tsx` plus `RechtOpRegelingModal.tsx` met meta-fetch plus uitgifte-flow plus per-voorwaarde-resultaat-tickjes plus kopieer-baar SD-JWT-VC. Commit `51caddbba`.\n\n**Nog open** (aparte sessie):\n\n- Mock-gemeente-diplo consultatie-balie met snarkjs.verify plus anonieme stem-registratie (voor/tegen/neutraal plus vrije tekst) plus stand-van-stemming-overzicht plus laag-2 cnf-DID-tracking.\n\nVolledige plan-doc in [`../../../../docs/uitbreidingen/implementatie-rule-and-principle-inferencing/plan.md`](../../../../docs/uitbreidingen/implementatie-rule-and-principle-inferencing/plan.md) Sub-stap 10g.\n","hashes":"  r1cs:             6e307a75d16192b1c49cb6aedd29d92d82fd714a24f568b905b4ae1ef76e2b53\n  wasm:             02b4f8c60b803aed5b9c3c601240e5992e915aac799add4aff305787cda50f40\n  verification_key: aee055d9a215259077cdad450ee8735598b54157a8b3f9053346f018c250d9ef\n  zkey:             b065b3acb471c3263097404ac044c3e0dad01d33dc8530d4f279c55d702c3fdb\n"}