Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 274 additions & 0 deletions PR_SUMMARY_DESIGN_SYSTEM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# PR Summary: Design System & ZK Proof Aggregation

This PR implements four features:

1. **Modal/Dialog Component** - Accessible, themeable modal for the design system
2. **Form Fields + Validation States** - Form inputs wired to Zod validation
3. **Recursive Proof Aggregation** - ZK proof batching for cost-efficient verification
4. **Terms/Consent Audit Trail** - Versioned consent with tamper-evident hash chain

---

## 1. Modal / Dialog Component

### Files Created
- `frontend/src/components/ui/Modal.jsx` - Modal and ConfirmDialog components
- `frontend/src/components/ui/Modal.css` - Styling with design system tokens
- `frontend/src/stories/Modal.stories.jsx` - Storybook documentation
- `frontend/src/components/ui/__tests__/Modal.test.jsx` - Unit tests

### Acceptance Criteria Met
- [x] **Focus trap** - Tab/Shift+Tab cycle within modal; focus returns to trigger on close
- [x] **ESC/overlay close** - Escape key and overlay click dismiss modal
- [x] **Async confirm state** - Loading indicator for async operations
- [x] **Storybook story + visual snapshot** - Multiple stories including async confirm
- [x] **Keyboard + screen-reader accessible** - `aria-modal`, `aria-labelledby`, `aria-describedby`, roving focus

### Usage
```jsx
import { Modal, ConfirmDialog } from '../components/ui';

// Basic modal
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Confirm Action">
<p>Are you sure?</p>
<Modal.Actions>
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
<Button onClick={handleConfirm}>Confirm</Button>
</Modal.Actions>
</Modal>

// Async confirm
<ConfirmDialog
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onConfirm={async () => { /* ... */ }}
title="Delete Campaign?"
message="This action cannot be undone."
loading={isLoading}
/>
```

---

## 2. Form Fields + Validation States Component

### Files Created
- `frontend/src/components/ui/FormField.jsx` - Input, select, textarea with validation
- `frontend/src/components/ui/FormField.css` - Validation state styling
- `frontend/src/stories/FormField.stories.jsx` - Storybook documentation
- `frontend/src/components/ui/__tests__/FormField.test.jsx` - Unit tests

### Acceptance Criteria Met
- [x] **Text/number/select with error states** - All input types supported
- [x] **Wired to validation library** - `useFormValidation` hook integrates with Zod
- [x] **Storybook story + visual snapshot** - Multiple stories with validation demos
- [x] **Keyboard + screen-reader accessible** - `aria-invalid`, `aria-describedby`, error role="alert"

### Usage
```jsx
import FormField, { useFormValidation } from '../components/ui/FormField';

// Basic field
<FormField
type="text"
label="Campaign Name"
name="name"
value={values.name}
onChange={handleChange}
error={errors.name}
required
/>

// With Zod validation
const { values, errors, handleChange, handleSubmit } = useFormValidation({
schema: campaignCreateSchema,
initialValues: { name: '', rewardPerAction: 0 },
onSubmit: async (values) => { /* ... */ },
});

return (
<form onSubmit={handleSubmit}>
<FormField label="Name" name="name" value={values.name} onChange={handleChange} error={errors.name?.message} />
</form>
);
```

---

## 3. Recursive Proof Aggregation for Batched Private Claims

### Files Created
- `backend/src/services/proofAggregation.js` - Proof aggregator prototype
- `backend/src/services/proofAggregation.test.js` - Unit tests
- `docs/PROOF_AGGREGATION_FEASIBILITY.md` - Feasibility study with cost analysis

### Acceptance Criteria Met
- [x] **Feasibility documented with cost analysis** - Comprehensive document showing 98%+ gas savings
- [x] **Prototype aggregates N proofs into one verification** - Binary tree aggregation implemented

### Cost Analysis
| Claims | Without Aggregation | With Aggregation | Savings |
|--------|-------------------|------------------|---------|
| 10 | 2,500,000 gas | 330,000 gas | 86.8% |
| 100 | 25,000,000 gas | 330,000 gas | 98.7% |
| 1,000 | 250,000,000 gas | 600,000 gas | 99.8% |

### Architecture
```
Proof 1 ─┐
Proof 2 ─┤
Proof 3 ─┼→ Aggregation Circuit → Aggregated Proof → On-chain Verification
Proof 4 ─┤
... │
Proof N ─┘
```

### Usage
```javascript
import { ProofAggregator, BatchClaimProcessor } from './services/proofAggregation.js';

const aggregator = new ProofAggregator();
const aggregated = await aggregator.aggregate([proof1, proof2, proof3, proof4]);
const isValid = await aggregator.verifyAggregated(aggregated);

// Process batch
const processor = new BatchClaimProcessor();
const result = await processor.processBatch(claims);
// result.gasEstimate.savings = "98.7%"
```

---

## 4. Terms/Consent + Audit Trail for Policy Acceptance

### Files Created
- `backend/src/services/consentService.js` - Versioned consent with hash chain
- `backend/src/services/consentService.test.js` - Unit tests
- `backend/src/db/migrations/018_consent_audit_trail.js` - Database migration
- `frontend/src/components/TermsConsent.jsx` - UI component
- `frontend/src/components/TermsConsent.css` - Styling

### Acceptance Criteria Met
- [x] **Versioned consent recorded per user** - Terms versions with content hash
- [x] **Tamper-evident (hash-chained) trail** - SHA-256 chain with integrity verification
- [x] **Exportable** - CSV and JSON export of audit trail

### Database Schema
```sql
CREATE TABLE terms_versions (
id INTEGER PRIMARY KEY,
version TEXT UNIQUE,
content_hash TEXT NOT NULL,
content TEXT NOT NULL,
published_at TEXT,
is_current INTEGER DEFAULT 0
);

CREATE TABLE user_consent (
id INTEGER PRIMARY KEY,
user_id TEXT NOT NULL,
terms_version TEXT NOT NULL,
consent_type TEXT DEFAULT 'terms',
accepted_at TEXT,
ip_address TEXT,
user_agent TEXT,
metadata TEXT
);

CREATE TABLE consent_audit_trail (
id INTEGER PRIMARY KEY,
seq INTEGER UNIQUE,
user_id TEXT NOT NULL,
action TEXT NOT NULL,
terms_version TEXT,
prev_hash TEXT NOT NULL,
entry_hash TEXT NOT NULL,
timestamp TEXT,
metadata TEXT
);
```

### Usage
```javascript
import { createConsentService } from './services/consentService.js';

const consent = createConsentService({ db });

// Record consent
await consent.recordConsent(userId, 'v1.0.0', {
ipAddress: '127.0.0.1',
userAgent: 'Mozilla/5.0...'
});

// Check acceptance
const accepted = consent.hasAcceptedCurrentTerms(userId);

// Verify integrity
const { valid } = consent.verifyAuditTrailIntegrity();

// Export
const csv = consent.exportAsCsv({ userId });
const json = consent.exportAsJson({ userId });
```

---

## Testing Summary

### Frontend Tests
```
✓ src/components/ui/__tests__/Modal.test.jsx (15 tests)
✓ src/components/ui/__tests__/FormField.test.jsx (18 tests)
Total: 33 tests passed
```

### Backend Tests
```
✓ proofAggregation.test.js (9 tests)
✓ consentService.test.js (12 tests)
```

### Build Verification
```
✓ Frontend build successful (47.16s)
✓ Storybook build successful (1.25 min)
```

---

## Files Changed Summary

### New Files (19)
- `frontend/src/components/ui/Modal.jsx`
- `frontend/src/components/ui/Modal.css`
- `frontend/src/components/ui/FormField.jsx`
- `frontend/src/components/ui/FormField.css`
- `frontend/src/stories/Modal.stories.jsx`
- `frontend/src/stories/FormField.stories.jsx`
- `frontend/src/components/ui/__tests__/Modal.test.jsx`
- `frontend/src/components/ui/__tests__/FormField.test.jsx`
- `frontend/src/components/TermsConsent.jsx`
- `frontend/src/components/TermsConsent.css`
- `backend/src/services/proofAggregation.js`
- `backend/src/services/proofAggregation.test.js`
- `backend/src/services/consentService.js`
- `backend/src/services/consentService.test.js`
- `backend/src/db/migrations/018_consent_audit_trail.js`
- `docs/PROOF_AGGREGATION_FEASIBILITY.md`

### Modified Files (2)
- `frontend/src/components/ui/index.js` - Added Modal and FormField exports
- `frontend/src/lib/apiClient.js` - Added consent API methods

---

## Checklist

- [x] All acceptance criteria met
- [x] Unit tests passing
- [x] Build successful
- [x] Storybook stories created
- [x] Keyboard accessible
- [x] Screen-reader accessible
- [x] Themeable (dark/light)
- [x] Documentation complete
6 changes: 3 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@opentelemetry/sdk-node": "^0.55.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@stellar/stellar-sdk": "^14.0.0",
"better-sqlite3": "^11.0.0",
"better-sqlite3": "^11.10.0",
"compression": "^1.8.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
Expand All @@ -50,8 +50,6 @@
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"eslint": "^9.0.0",
"globals": "^15.0.0",
"@readme/openapi-parser": "^2.6.0",
"@redocly/cli": "^1.34.5",
"@types/cors": "^2.8.0",
Expand All @@ -60,6 +58,8 @@
"@types/supertest": "^6.0.0",
"ajv": "^8.17.0",
"ajv-formats": "^3.0.1",
"eslint": "^9.0.0",
"globals": "^15.0.0",
"js-yaml": "^4.1.0",
"nodemon": "^3.1.0",
"redoc-cli": "^0.13.21",
Expand Down
79 changes: 79 additions & 0 deletions backend/src/db/migrations/018_consent_audit_trail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @ts-check
export const version = 18;
export const description = 'Add versioned consent and audit trail for policy acceptance (#581)';

export function up(db) {
// Terms/Policy versions
db.exec(`
CREATE TABLE IF NOT EXISTS terms_versions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version TEXT NOT NULL UNIQUE,
content_hash TEXT NOT NULL,
content TEXT NOT NULL,
published_at TEXT NOT NULL DEFAULT (datetime('now')),
is_current INTEGER NOT NULL DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_terms_versions_version ON terms_versions(version);
CREATE INDEX IF NOT EXISTS idx_terms_versions_current ON terms_versions(is_current);
`);

// User consent records
db.exec(`
CREATE TABLE IF NOT EXISTS user_consent (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
terms_version TEXT NOT NULL,
consent_type TEXT NOT NULL DEFAULT 'terms',
accepted_at TEXT NOT NULL DEFAULT (datetime('now')),
ip_address TEXT,
user_agent TEXT,
metadata TEXT,
FOREIGN KEY (terms_version) REFERENCES terms_versions(version)
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_user_consent_unique
ON user_consent(user_id, terms_version, consent_type);
CREATE INDEX IF NOT EXISTS idx_user_consent_user ON user_consent(user_id);
CREATE INDEX IF NOT EXISTS idx_user_consent_terms ON user_consent(terms_version);
CREATE INDEX IF NOT EXISTS idx_user_consent_accepted ON user_consent(accepted_at);
`);

// Consent audit trail (hash-chained)
db.exec(`
CREATE TABLE IF NOT EXISTS consent_audit_trail (
id INTEGER PRIMARY KEY AUTOINCREMENT,
seq INTEGER UNIQUE,
user_id TEXT NOT NULL,
action TEXT NOT NULL,
terms_version TEXT,
prev_hash TEXT NOT NULL,
entry_hash TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
metadata TEXT
);

CREATE INDEX IF NOT EXISTS idx_consent_audit_seq ON consent_audit_trail(seq);
CREATE INDEX IF NOT EXISTS idx_consent_audit_user ON consent_audit_trail(user_id);
CREATE INDEX IF NOT EXISTS idx_consent_audit_timestamp ON consent_audit_trail(timestamp);
`);

// Insert initial terms version
db.exec(`
INSERT INTO terms_versions (version, content_hash, content, is_current)
VALUES (
'v1.0.0',
'sha256:0000000000000000000000000000000000000000000000000000000000000000',
'Initial Terms of Service and Privacy Policy',
1
);
`);
}

export function down(db) {
db.exec(`
DROP TABLE IF EXISTS consent_audit_trail;
DROP TABLE IF EXISTS user_consent;
DROP TABLE IF EXISTS terms_versions;
`);
}
Loading