# Park Passport Mobile-first PWA for GPS-verified National Park stamps. Identity and data live on AT Protocol. This is a Rust rewrite of the original Go implementation (`~/cairns`). ## Stack | Layer | Technology | |-------------|---------------------------------| | Frontend | Vanilla JS PWA (React Native later) | | Backend | Rust (axum), hosted on Fly.io | | Identity | AT Protocol / Bluesky OAuth | | User data | User's own AT Protocol PDS | | Park data | NPS Public API + bundled GeoJSON | | Credentials | W3C Verifiable Credentials 2.0 | ## Architecture The backend issues signed W3C VCs but stores nothing. All stamp records and VC blobs live on the user's PDS. ``` Client Verification Service User PDS │ │ │ ├─ { did, parkId, coords } ────►│ │ │ ├─ check GeoJSON boundary │ │ ├─ sign VC (EdDSA) │ │ ├─ write VC blob ─────────►│ │◄─ vcRef (CID) ────────────────│ │ │ │ ├─ write app.parkpassport.stamp record ───────────────────►│ ``` ## Stamp Record (`app.parkpassport.stamp`) ``` parkId string visitedAt datetime verificationMethod "gps" vcRef blob ref (CID of VC blob in PDS blob store) ``` ## Verifiable Credential Structure ```json { "@context": ["https://www.w3.org/ns/credentials/v2"], "type": ["VerifiableCredential", "NationalParkStamp"], "issuer": "did:web:verify.parkpassport.app", "validFrom": "2026-03-22T14:30:00Z", "credentialSubject": { "id": "did:plc:abc123", "parkId": "acadia", "parkName": "Acadia National Park", "visitedAt": "2026-03-22T14:30:00Z", "verificationMethod": "gps" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-rdfc-2022", "verificationMethod": "did:web:verify.parkpassport.app#key-1", "proofValue": "..." } } ``` Stamps are independently verifiable: fetch the VC blob from the user's PDS, fetch the issuer public key from `https://verify.parkpassport.app/.well-known/did.json`, verify the EdDSA signature. ## Key Endpoints | Endpoint | Purpose | |-----------------------------|-------------------------------------------------| | `POST /api/stamp` | Issue a signed VC for a GPS-verified park visit | | `GET /api/stamps` | Fetch user's stamps from their PDS | | `GET /api/me` | Return `{ did, handle }` for auth state | | `GET /auth/login` | Start Bluesky OAuth flow | | `GET /auth/callback` | Complete OAuth, set session cookie | | `GET /.well-known/did.json` | Issuer DID document with public key | ## Key Crates | Purpose | Crate | |---------------|--------------------| | HTTP server | `axum` | | HTTP client | `reqwest` | | Async runtime | `tokio` | | DNS | `hickory-resolver` | | JSON | `serde`, `serde_json` | | Error handling | `anyhow` | | Cookies | `tower-cookies` | | Geo | `geo` | | Ed25519 | `ed25519-dalek` | ## Infrastructure - Issuer DID: `did:web:verify.parkpassport.app` - Public key: `https://verify.parkpassport.app/.well-known/did.json` - User data: user's existing Bluesky PDS (no database) - Deploy: `fly deploy`