Skip to content

Commit 2973f3b

Browse files
yumemayuMayumi Haraclaude
authored
feat(attest-zk-groth16): contains_no_pii Groth16 attestation e2e (VOKF-7 / M1) (#5)
Wires a real Groth16 proof into the §10 attestation path, exercising the verified and attestation_failed branches (not just unsupported_scheme). - circuits/contains_no_pii/: a minimal Circom 2 demo circuit proving a private field is 0 (PII absent), plus gen.sh and the committed verification key + a sample proof (heavy ptau/zkey/wasm gitignored). - @verifiable-okf/attest-zk-groth16: an AttestationVerifier for the zk-groth16 scheme — decodes the base64 proof and runs snarkjs.groth16.verify offline (verification key only; no proving key, no circom, no network). Bundles the contains_no_pii vkey. - fixtures/signed/with-zk-attestation.md (valid proof → verified) and with-bad-zk-attestation.md (validly signed, wrong public signal → attestation_failed). Tests (5): real proof → verified via the verifier policy; validly-signed but failing proof → attestation_failed; carrier-only (not required) → verified; verifier fn valid→true / unknown-circuit→false / wrong-signal →false. Clean build orders all 5 packages; dist bundles the vkey. Full suite green (canon 28 + signer 7 + verifier 22 + visualizer 5 + attest 5 = 67). Co-authored-by: Mayumi Hara <mayumi@Mayumis-iMac.local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ddb1035 commit 2973f3b

16 files changed

Lines changed: 750 additions & 1 deletion

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ consumers must preserve unknown fields.
1616
| `@verifiable-okf/canon` | ✅ M0 | Canonicalization (§5) + JCS (§6) + signing-payload builder. The shared, version-pinned core. |
1717
| `@verifiable-okf/signer` | ✅ M0 | `vokf-sign` — attaches `provenance` without mutating any other byte. |
1818
| `@verifiable-okf/verifier` | ✅ M0 | `vokf-verify` — the §10 verification algorithm (status + reason codes), schema validation, did:web/did:key. |
19-
| `@verifiable-okf/visualizer` | ⏳ M1 | Bundle → single offline HTML with per-concept badges. |
19+
| `@verifiable-okf/visualizer` | ✅ M1 | Bundle → single offline HTML with per-concept badges. |
20+
| `@verifiable-okf/attest-zk-groth16` | ✅ M1 | `zk-groth16` attestation verifier (Groth16/BN254 via snarkjs, offline); bundles the `contains_no_pii` demo circuit. |
2021

2122
## Quickstart (< 5 min)
2223

@@ -47,4 +48,25 @@ pnpm build
4748
pnpm test
4849
```
4950

51+
## Attestations (zk-groth16)
52+
53+
Attestations are pluggable, machine-checkable claims carried in `provenance.attestations`.
54+
`@verifiable-okf/attest-zk-groth16` provides an offline Groth16 proof verifier; pass it to the
55+
verifier's policy to turn the §10 attestation path into real ZK checks:
56+
57+
```ts
58+
import { verifyConcept } from "@verifiable-okf/verifier";
59+
import { groth16AttestationVerifier } from "@verifiable-okf/attest-zk-groth16";
60+
61+
await verifyConcept(concept, {
62+
policy: { requiredClaims: ["contains_no_pii"] },
63+
attestationVerifiers: { "zk-groth16": groth16AttestationVerifier },
64+
});
65+
```
66+
67+
The `contains_no_pii` demo circuit (`circuits/contains_no_pii/`, regenerate with `gen.sh`) proves a
68+
private field is absent; verification needs only the bundled verification key — no proving key, no
69+
circom, no network. Proof *issuance* (and metering) is a separate, commercial concern, not part of
70+
this OSS engine.
71+
5072
See [`CANONICALIZATION.md`](./CANONICALIZATION.md) and [`SECURITY.md`](./SECURITY.md).
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build/
2+
*.ptau
3+
*.zkey
4+
*.wtns
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
pragma circom 2.1.9;
2+
3+
// Minimal demo circuit for the `contains_no_pii` claim.
4+
// Proves (in zero knowledge) that a private field equals 0 — i.e. a PII
5+
// counter / flag is absent. A valid proof therefore attests "no PII",
6+
// without revealing anything further. Public output `ok` is always 1.
7+
template ContainsNoPii() {
8+
signal input pii_field; // private witness (e.g. count of PII matches)
9+
signal output ok; // public
10+
pii_field === 0; // constraint: only satisfiable when pii_field == 0
11+
ok <== 1;
12+
}
13+
14+
component main = ContainsNoPii();

circuits/contains_no_pii/gen.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
# Regenerate the contains_no_pii Groth16 artifacts (circom 2 + snarkjs).
3+
# Outputs build/, then copies vkey.json + a sample proof to the circuit root.
4+
set -e
5+
cd "$(dirname "$0")"
6+
mkdir -p build && cd build
7+
circom ../contains_no_pii.circom --r1cs --wasm -o .
8+
npx snarkjs powersoftau new bn128 8 pot_0.ptau
9+
npx snarkjs powersoftau contribute pot_0.ptau pot_1.ptau --name=vokf -e="$(head -c 32 /dev/urandom | xxd -p)"
10+
npx snarkjs powersoftau prepare phase2 pot_1.ptau pot_final.ptau
11+
npx snarkjs groth16 setup contains_no_pii.r1cs pot_final.ptau cnp_0.zkey
12+
npx snarkjs zkey contribute cnp_0.zkey cnp_final.zkey --name=vokf -e="$(head -c 32 /dev/urandom | xxd -p)"
13+
npx snarkjs zkey export verificationkey cnp_final.zkey vkey.json
14+
echo '{"pii_field":"0"}' > input.json
15+
node contains_no_pii_js/generate_witness.js contains_no_pii_js/contains_no_pii.wasm input.json witness.wtns
16+
npx snarkjs groth16 prove cnp_final.zkey witness.wtns proof.json public.json
17+
npx snarkjs groth16 verify vkey.json public.json proof.json
18+
cp vkey.json ../vkey.json && cp proof.json ../proof.sample.json && cp public.json ../public.sample.json
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"pi_a": [
3+
"17461939730782852438770980938862447298870257132741896786032076201177378191598",
4+
"8607488854563628719732053192769587193635892693849864793195992234487286965736",
5+
"1"
6+
],
7+
"pi_b": [
8+
[
9+
"11728139912199972330023136803603667424732587153851331680813612153155232420464",
10+
"11200038715154571765669600573424442998636501858871986095753385916766423332238"
11+
],
12+
[
13+
"15898827097719954490264646923107552463817122452175480198633607290137491470234",
14+
"39309480319576073215644807530307792566864076610431022681114688907195330005"
15+
],
16+
[
17+
"1",
18+
"0"
19+
]
20+
],
21+
"pi_c": [
22+
"20569737558178479334100010919083231235115538011684311997543995295726755938354",
23+
"7407113397076647680008114124813864763833032734057969247775727988171334507260",
24+
"1"
25+
],
26+
"protocol": "groth16",
27+
"curve": "bn128"
28+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"1"
3+
]

circuits/contains_no_pii/vkey.json

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"protocol": "groth16",
3+
"curve": "bn128",
4+
"nPublic": 1,
5+
"vk_alpha_1": [
6+
"1232894381328749349801999194020364750860714090412859425702737227783362329035",
7+
"4436118752635877068544674281701685683067136631392057119874998162712106835950",
8+
"1"
9+
],
10+
"vk_beta_2": [
11+
[
12+
"14716936584645639048355760343770265219429753952539561837148583891266482232582",
13+
"16016704211999055611191571745890472454130292775011844893044274766649904226588"
14+
],
15+
[
16+
"9057080589359257783096967548757429701196839693892521700242482106975202632599",
17+
"20533264667873917127608317601690877061735828347178346689977693657285325072488"
18+
],
19+
[
20+
"1",
21+
"0"
22+
]
23+
],
24+
"vk_gamma_2": [
25+
[
26+
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
27+
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
28+
],
29+
[
30+
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
31+
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
32+
],
33+
[
34+
"1",
35+
"0"
36+
]
37+
],
38+
"vk_delta_2": [
39+
[
40+
"3474799006316917099767645587129093771951975749530518410992048528464400789842",
41+
"14906175476459237866989995382063729370101320006270423677795159512995448203917"
42+
],
43+
[
44+
"5379252983319465967809417952863222386238200287388003645473005624099013594517",
45+
"18986786293210347468478260290195243983141901438725909310713432120103963521504"
46+
],
47+
[
48+
"1",
49+
"0"
50+
]
51+
],
52+
"vk_alphabeta_12": [
53+
[
54+
[
55+
"403749106030442990619315241768518264979636829364042921852032020812672065137",
56+
"7621931038878796889452159494924950157179135094573670140480507261568496685977"
57+
],
58+
[
59+
"16865090028061755323054514475260851841876300676446183862568668824480223320175",
60+
"18327880048007991543735184721865349268992505013333563818619430008549808266562"
61+
],
62+
[
63+
"8787872532129923515649140272636460985886459682924130307132452782207431646038",
64+
"13260321749514392872666733411862938534642858847009090275335091916896884689184"
65+
]
66+
],
67+
[
68+
[
69+
"6387628840775407748790935730268315938700872123575627156397259578938427030883",
70+
"16771398627571756483969946929083216360331205551563536906712676092481376278695"
71+
],
72+
[
73+
"15792279552950039777871914493874300116271355423474748217406866705529595597418",
74+
"16476731688131302025407637631205413052485691063167902404388883062051157327695"
75+
],
76+
[
77+
"14261736778804873940795974174260233059211405548185751146935318338563960445767",
78+
"6447326858417875907252629836795830459066202724682340898082801712486689440581"
79+
]
80+
]
81+
],
82+
"IC": [
83+
[
84+
"18641926387932197056747795654697750247402624656485328984590349834099713095425",
85+
"5797156867949200385824451310324450740426988220911984895467204416449729528236",
86+
"1"
87+
],
88+
[
89+
"566468554377789334102443174096780922474059205361865467335928056340967116619",
90+
"19928558876800948858731875360898910993093008468686622797144379353445393047770",
91+
"1"
92+
]
93+
]
94+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
title: GA4 Purchase Event
3+
resource: https://example.com/okf/ga4/purchase
4+
description: Server-side GA4 purchase event mapping for the storefront.
5+
tags:
6+
- analytics
7+
- ga4
8+
provenance:
9+
spec_version: verifiable-okf/0.3
10+
issuer: did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7
11+
content_hash: sha256:94ea40b78f963160edd233a2ad2aa10c5da2d945eb40e4059dd80399d4ccbd00
12+
valid_from: 2026-01-01T00:00:00Z
13+
valid_until: 2027-01-01T00:00:00Z
14+
attestations:
15+
- claim: contains_no_pii
16+
scheme: zk-groth16
17+
circuit: contains_no_pii
18+
proof: ewogInBpX2EiOiBbCiAgIjE3NDYxOTM5NzMwNzgyODUyNDM4NzcwOTgwOTM4ODYyNDQ3Mjk4ODcwMjU3MTMyNzQxODk2Nzg2MDMyMDc2MjAxMTc3Mzc4MTkxNTk4IiwKICAiODYwNzQ4ODg1NDU2MzYyODcxOTczMjA1MzE5Mjc2OTU4NzE5MzYzNTg5MjY5Mzg0OTg2NDc5MzE5NTk5MjIzNDQ4NzI4Njk2NTczNiIsCiAgIjEiCiBdLAogInBpX2IiOiBbCiAgWwogICAiMTE3MjgxMzk5MTIxOTk5NzIzMzAwMjMxMzY4MDM2MDM2Njc0MjQ3MzI1ODcxNTM4NTEzMzE2ODA4MTM2MTIxNTMxNTUyMzI0MjA0NjQiLAogICAiMTEyMDAwMzg3MTUxNTQ1NzE3NjU2Njk2MDA1NzM0MjQ0NDI5OTg2MzY1MDE4NTg4NzE5ODYwOTU3NTMzODU5MTY3NjY0MjMzMzIyMzgiCiAgXSwKICBbCiAgICIxNTg5ODgyNzA5NzcxOTk1NDQ5MDI2NDY0NjkyMzEwNzU1MjQ2MzgxNzEyMjQ1MjE3NTQ4MDE5ODYzMzYwNzI5MDEzNzQ5MTQ3MDIzNCIsCiAgICIzOTMwOTQ4MDMxOTU3NjA3MzIxNTY0NDgwNzUzMDMwNzc5MjU2Njg2NDA3NjYxMDQzMTAyMjY4MTExNDY4ODkwNzE5NTMzMDAwNSIKICBdLAogIFsKICAgIjEiLAogICAiMCIKICBdCiBdLAogInBpX2MiOiBbCiAgIjIwNTY5NzM3NTU4MTc4NDc5MzM0MTAwMDEwOTE5MDgzMjMxMjM1MTE1NTM4MDExNjg0MzExOTk3NTQzOTk1Mjk1NzI2NzU1OTM4MzU0IiwKICAiNzQwNzExMzM5NzA3NjY0NzY4MDAwODExNDEyNDgxMzg2NDc2MzgzMzAzMjczNDA1Nzk2OTI0Nzc3NTcyNzk4ODE3MTMzNDUwNzI2MCIsCiAgIjEiCiBdLAogInByb3RvY29sIjogImdyb3RoMTYiLAogImN1cnZlIjogImJuMTI4Igp9
19+
public_inputs:
20+
- "2"
21+
signature:
22+
scheme: ed25519
23+
value: Fo0PsKt0yw5QZ4UW7eErcJIgz0FAgOiTdp8+dErnml189GSmyida8gq9DijaIzQnfRNLa/Y6LojEPKcIGx/WAw==
24+
---
25+
# GA4 Purchase Event
26+
27+
Send a `purchase` event when an order is confirmed.
28+
29+
| param | source |
30+
|---|---|
31+
| value | order.total |
32+
| currency | order.currency |
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
title: GA4 Purchase Event
3+
resource: https://example.com/okf/ga4/purchase
4+
description: Server-side GA4 purchase event mapping for the storefront.
5+
tags:
6+
- analytics
7+
- ga4
8+
provenance:
9+
spec_version: verifiable-okf/0.3
10+
issuer: did:key:z6MkneMkZqwqRiU5mJzSG3kDwzt9P8C59N4NGTfBLfSGE7c7
11+
content_hash: sha256:94ea40b78f963160edd233a2ad2aa10c5da2d945eb40e4059dd80399d4ccbd00
12+
valid_from: 2026-01-01T00:00:00Z
13+
valid_until: 2027-01-01T00:00:00Z
14+
attestations:
15+
- claim: contains_no_pii
16+
scheme: zk-groth16
17+
circuit: contains_no_pii
18+
proof: ewogInBpX2EiOiBbCiAgIjE3NDYxOTM5NzMwNzgyODUyNDM4NzcwOTgwOTM4ODYyNDQ3Mjk4ODcwMjU3MTMyNzQxODk2Nzg2MDMyMDc2MjAxMTc3Mzc4MTkxNTk4IiwKICAiODYwNzQ4ODg1NDU2MzYyODcxOTczMjA1MzE5Mjc2OTU4NzE5MzYzNTg5MjY5Mzg0OTg2NDc5MzE5NTk5MjIzNDQ4NzI4Njk2NTczNiIsCiAgIjEiCiBdLAogInBpX2IiOiBbCiAgWwogICAiMTE3MjgxMzk5MTIxOTk5NzIzMzAwMjMxMzY4MDM2MDM2Njc0MjQ3MzI1ODcxNTM4NTEzMzE2ODA4MTM2MTIxNTMxNTUyMzI0MjA0NjQiLAogICAiMTEyMDAwMzg3MTUxNTQ1NzE3NjU2Njk2MDA1NzM0MjQ0NDI5OTg2MzY1MDE4NTg4NzE5ODYwOTU3NTMzODU5MTY3NjY0MjMzMzIyMzgiCiAgXSwKICBbCiAgICIxNTg5ODgyNzA5NzcxOTk1NDQ5MDI2NDY0NjkyMzEwNzU1MjQ2MzgxNzEyMjQ1MjE3NTQ4MDE5ODYzMzYwNzI5MDEzNzQ5MTQ3MDIzNCIsCiAgICIzOTMwOTQ4MDMxOTU3NjA3MzIxNTY0NDgwNzUzMDMwNzc5MjU2Njg2NDA3NjYxMDQzMTAyMjY4MTExNDY4ODkwNzE5NTMzMDAwNSIKICBdLAogIFsKICAgIjEiLAogICAiMCIKICBdCiBdLAogInBpX2MiOiBbCiAgIjIwNTY5NzM3NTU4MTc4NDc5MzM0MTAwMDEwOTE5MDgzMjMxMjM1MTE1NTM4MDExNjg0MzExOTk3NTQzOTk1Mjk1NzI2NzU1OTM4MzU0IiwKICAiNzQwNzExMzM5NzA3NjY0NzY4MDAwODExNDEyNDgxMzg2NDc2MzgzMzAzMjczNDA1Nzk2OTI0Nzc3NTcyNzk4ODE3MTMzNDUwNzI2MCIsCiAgIjEiCiBdLAogInByb3RvY29sIjogImdyb3RoMTYiLAogImN1cnZlIjogImJuMTI4Igp9
19+
public_inputs:
20+
- "1"
21+
signature:
22+
scheme: ed25519
23+
value: dt9g6B7vZx5hM6j3BZnBJDu5F2d1OjHkoZ7ZkQ7i78Fx1CjzlezpZ5A4YM8ipISCHEF+m0PcM1jo8wPaN+sXAA==
24+
---
25+
# GA4 Purchase Event
26+
27+
Send a `purchase` event when an order is confirmed.
28+
29+
| param | source |
30+
|---|---|
31+
| value | order.total |
32+
| currency | order.currency |
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@verifiable-okf/attest-zk-groth16",
3+
"version": "0.1.0",
4+
"description": "Verifiable OKF attestation verifier for the zk-groth16 scheme (Groth16/BN254 via snarkjs). Offline proof verification; bundles the contains_no_pii demo verification key.",
5+
"license": "Apache-2.0",
6+
"type": "module",
7+
"main": "dist/index.js",
8+
"types": "dist/index.d.ts",
9+
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
10+
"files": ["dist"],
11+
"scripts": {
12+
"build": "tsc -p tsconfig.json && cp -r src/vkeys dist/vkeys",
13+
"test": "vitest run",
14+
"lint": "tsc -p tsconfig.json --noEmit"
15+
},
16+
"dependencies": {
17+
"snarkjs": "^0.7.6"
18+
},
19+
"devDependencies": {
20+
"@verifiable-okf/verifier": "workspace:*",
21+
"@verifiable-okf/signer": "workspace:*",
22+
"@types/node": "^20.14.0",
23+
"typescript": "^5.6.0",
24+
"vitest": "^2.1.0"
25+
}
26+
}

0 commit comments

Comments
 (0)