Rusty cairns are better than regular cairns.
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