Something went wrong. Try again.
Rusty cairns are better than regular cairns.
Something went wrong. Try again.
Rust 84%
HTML 11%
Dockerfile 2%
JavaScript 2%
Makefile <1%
README.md
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 #
{
"@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