Skip to content

Commit fd12a5a

Browse files
authored
feat(zupass): complete GPC proof verification system (#416)
Implements complete Zupass event ticket verification using GPC (General Purpose Circuits) proofs with nullifier-based identity tracking. This enables privacy-preserving event ticket gating for conversations while supporting guest accounts, soft login, and account merging. Backend (API): - Add GPC proof verification with GPCBoundConfig and GPCRevealedClaims - Implement watermark-based proof validation using event-specific secrets - Create zupass_nullifier table with composite unique constraint on (nullifier_hash, event_slug) to track event-specific identities - Add merge service for account consolidation during ticket verification - Implement guest user recovery via nullifier matching - Add auto-guest-creation during ticket verification for unknown users - Refactor auth service: split monolithic service into core types and state helpers modules for maintainability - Update Rarimo integration to use consistent watermark pattern - Add nullifier-based authentication type detection helpers - Implement dynamic import workaround for @pcd/gpc broken ESM exports - Add shared Zupass event configuration (eventId, signerPublicKey, productIds) - Update event_ticket schema: replace ticketId with nullifier_hash column Frontend (Agora): - Add EventTicketRequirementBanner component with inline verification flow - Implement auto-trigger Zupass verification on gated interactions (voting, commenting) with automatic retry after successful verification - Update authentication store with ticket verification state management - Add poll voting and comment submission ticket gating with optimistic UI updates and proper error rollback - Add settings UI for viewing verified tickets and managing authentication methods - Implement guest/soft-login upgrade flow with account merge confirmation - Add Node.js crypto/fs/path stubs for @pcd/gpc packages in Quasar webpack build (required for browser compatibility) - Use Parcnet API with ticketProofRequest() helper for proof generation - Add event-specific external nullifiers: agora-{eventSlug}-v1 Database: - New table: zupass_nullifier (nullifier_hash, user_id, event_slug, created_at, updated_at, deleted_at) - Composite unique constraint on (nullifier_hash, event_slug) - Index on user_id for efficient reverse lookups - Migration V0028: add zupass_nullifier table Legal & Documentation: - Update privacy policy (last updated 2025/11/11): - Add guest account explanation (DID-based device identifier) - Document soft login vs hard login distinction - Explain Zupass event ticket verification using GPC proofs - Add account upgrade/merge policy (guest→soft→hard progression) - Clarify data retention: only cryptographic proofs stored, never raw identity documents or ticket details - Update terms of service: - Add Zupass alongside Rarimo as ZKP verification method - Update account deletion flow for Rarimo users (nullifier-only proof) - Fix typo: "RariMe" → "Rarimo" throughout - Update AUTHORS: fix "RariMe SDK" → "Rarimo SDK" Infrastructure: - Add @pcd/gpc, @pcd/pod, @pcd/pod-pcd packages for proof verification - Add @parcnet-js/client-rpc for Parcnet API integration - Add ZUPASS_SERVER_URL and ZUPASS_EVENT_* config variables to env.example - Update .gitignore to exclude CLAUDE_*.md analysis files - Add Quasar webpack configuration for Node.js polyfill stubs Architecture: - Update all architecture diagrams (drawio files) to reflect new auth flows - Add CLAUDE_AUTHENTICATION_SECURITY_ANALYSIS.md documenting security model - Add CLAUDE_PLAN_MERGE_AND_RECOVERY.md documenting account merge logic - Add CLAUDE_REFACTORING_SUMMARY.md documenting auth service refactoring Security model: - GPC proofs use 2-tuple classification (signerPublicKey + eventId only) - Nullifiers are event-specific via externalNullifier = agora-{eventSlug}-v1 - Backend validates ALL tuples in membershipLists match expected values - Watermark prevents replay attacks across different event contexts - Account merging only allowed: guest→verified or soft→hard (never verified→verified) - Nullifier tracking prevents double-verification with soft-deleted users This implementation enables seamless privacy-preserving event ticket requirements with automatic verification flows and guest account management. Users can browse as guests, verify for specific events (soft login), or create permanent verified accounts (hard login) with all content preserved during upgrades.
1 parent 035b269 commit fd12a5a

119 files changed

Lines changed: 18400 additions & 2683 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,10 @@ lerna-debug.log*
7171
*.vsix
7272

7373
TODO*
74+
75+
#external repo
76+
zupass
77+
parcnet-client
78+
efdevcon-monorepo
79+
80+
CLAUDE_*.md

AUTHORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Agora is written by:
1010
- Takeru Fukushima <takeru.f.2004@gmail.com> (proofread Japanese translations)
1111

1212
With help from many libraries and frameworks including:
13-
- RariMe SDK
13+
- Rarimo SDK
1414
- UCAN
1515
- WebCrypto API
1616
- Typescript

doc/direct-anonymous-posting.drawio

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<root>
55
<mxCell id="0" />
66
<mxCell id="1" parent="0" />
7-
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-1" value="User&#39;s wallet&lt;div&gt;(RariMe or SSI Wallet)&lt;/div&gt;" style="swimlane;whiteSpace=wrap;html=1;startSize=30;" vertex="1" parent="1">
7+
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-1" value="User&#39;s wallet&lt;div&gt;(Rarimo or SSI Wallet)&lt;/div&gt;" style="swimlane;whiteSpace=wrap;html=1;startSize=30;" vertex="1" parent="1">
88
<mxGeometry x="93.75" y="320" width="142.5" height="150" as="geometry" />
99
</mxCell>
1010
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;startArrow=classic;startFill=1;endArrow=none;endFill=0;" edge="1" parent="1" source="lZM2KmHgM9Xgh8onh7Ww-1" target="lZM2KmHgM9Xgh8onh7Ww-5">
@@ -19,7 +19,7 @@
1919
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-5" value="Agora" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;" vertex="1" parent="1">
2020
<mxGeometry x="495" y="335" width="120" height="80" as="geometry" />
2121
</mxCell>
22-
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-6" value="User&#39;s wallet&lt;div&gt;(RariMe or SSI Wallet)&lt;/div&gt;" style="swimlane;whiteSpace=wrap;html=1;startSize=30;" vertex="1" parent="1">
22+
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-6" value="User&#39;s wallet&lt;div&gt;(Rarimo or SSI Wallet)&lt;/div&gt;" style="swimlane;whiteSpace=wrap;html=1;startSize=30;" vertex="1" parent="1">
2323
<mxGeometry x="93.75" y="530" width="142.5" height="150" as="geometry" />
2424
</mxCell>
2525
<mxCell id="lZM2KmHgM9Xgh8onh7Ww-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=classic;endFill=1;" edge="1" parent="1" source="lZM2KmHgM9Xgh8onh7Ww-6" target="lZM2KmHgM9Xgh8onh7Ww-10">

doc/mix-account-and-direct-anon-posting.drawio

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<mxCell id="2zX7negMlNs0xSwpkuFU-5" value="1) Login with email address&amp;nbsp;&lt;span style=&quot;background-color: initial;&quot;&gt;through&amp;nbsp;&lt;/span&gt;&lt;div&gt;&lt;span style=&quot;background-color: initial;&quot;&gt;OAuth2 or local-first auth&lt;/span&gt;&lt;/div&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
2020
<mxGeometry x="185" y="150" width="220" height="40" as="geometry" />
2121
</mxCell>
22-
<mxCell id="2zX7negMlNs0xSwpkuFU-6" value="User&#39;s wallet&lt;div&gt;(RariMe or SSI Wallet)&lt;/div&gt;" style="swimlane;whiteSpace=wrap;html=1;startSize=30;" vertex="1" parent="1">
22+
<mxCell id="2zX7negMlNs0xSwpkuFU-6" value="User&#39;s wallet&lt;div&gt;(Rarimo or SSI Wallet)&lt;/div&gt;" style="swimlane;whiteSpace=wrap;html=1;startSize=30;" vertex="1" parent="1">
2323
<mxGeometry x="93.75" y="320" width="142.5" height="150" as="geometry" />
2424
</mxCell>
2525
<mxCell id="2zX7negMlNs0xSwpkuFU-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="2zX7negMlNs0xSwpkuFU-6" target="2zX7negMlNs0xSwpkuFU-10">

doc/modules.drawio

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<mxCell id="GkvW0CMtngjdruLvTlMq-3" value="Module 3: shared module" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
2323
<mxGeometry x="655" y="670" width="120" height="60" as="geometry" />
2424
</mxCell>
25-
<mxCell id="GkvW0CMtngjdruLvTlMq-6" value="Module 6: Integration with RariMe" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
25+
<mxCell id="GkvW0CMtngjdruLvTlMq-6" value="Module 6: Integration with Rarimo" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
2626
<mxGeometry x="655" y="400" width="120" height="60" as="geometry" />
2727
</mxCell>
2828
<mxCell id="GkvW0CMtngjdruLvTlMq-7" value="Module 7: Integration with Polis" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
@@ -37,13 +37,13 @@
3737
<mxCell id="GkvW0CMtngjdruLvTlMq-35" value="Module 3: shared module" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
3838
<mxGeometry x="150" y="520" width="120" height="60" as="geometry" />
3939
</mxCell>
40-
<mxCell id="GkvW0CMtngjdruLvTlMq-36" value="Module 6: Integration with RariMe" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
40+
<mxCell id="GkvW0CMtngjdruLvTlMq-36" value="Module 6: Integration with Rarimo" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
4141
<mxGeometry x="150" y="280" width="120" height="60" as="geometry" />
4242
</mxCell>
4343
<mxCell id="GkvW0CMtngjdruLvTlMq-37" value="Module 3: shared module" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
4444
<mxGeometry x="95.61999999999999" y="810" width="228.75" height="310" as="geometry" />
4545
</mxCell>
46-
<mxCell id="GkvW0CMtngjdruLvTlMq-38" value="Module 6: Integration with RariMe" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
46+
<mxCell id="GkvW0CMtngjdruLvTlMq-38" value="Module 6: Integration with Rarimo" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
4747
<mxGeometry x="194.37" y="820" width="120" height="60" as="geometry" />
4848
</mxCell>
4949
<mxCell id="GkvW0CMtngjdruLvTlMq-39" value="Module 1: local-first authentication" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
@@ -64,7 +64,7 @@
6464
<mxCell id="GkvW0CMtngjdruLvTlMq-44" value="Module 7: Integration with Polis" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
6565
<mxGeometry x="835" y="665" width="120" height="60" as="geometry" />
6666
</mxCell>
67-
<mxCell id="GkvW0CMtngjdruLvTlMq-45" value="Module 6: Integration with RariMe" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
67+
<mxCell id="GkvW0CMtngjdruLvTlMq-45" value="Module 6: Integration with Rarimo" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
6868
<mxGeometry x="835" y="600" width="120" height="60" as="geometry" />
6969
</mxCell>
7070
<mxCell id="GkvW0CMtngjdruLvTlMq-48" value="Module 7: Integration with Polis" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">

0 commit comments

Comments
 (0)