# Frozen interfaces These contracts are shared by every crate. The function signatures in `crates/*/src` are authoritative. Change a signature only together with every caller, and record the change here. ## Hosts in the devnet | Host | Serves | DID | |---|---|---| | `zkwid.test` | N (`zkwid-n`) | `did:web:zkwid.test`, service `#zkwid` of type `ZkwidService` | | `h.zkwid.test` | H stub (`zkwid-h`) | `did:web:h.zkwid.test`, `#atproto` K256 key | | `pds.zkwid.test`, `*.pds.zkwid.test` | `atproto-pds` binary | `did:web:pds.zkwid.test` | | `plc.zkwid.test` | `atproto_testkit` fake PLC | none | Every https request to these hosts goes through the devnet CONNECT proxy. Process env `HTTPS_PROXY` points at it, and `SSL_CERT_FILE` points at the devnet CA. Any `reqwest::Client` built after `Devnet::start` picks both up. ## Records All in U's repo. - `at.zkwid.workload/` is `zkwid::lexicon::Workload`. `measurements[i].wasm` is a blob in U's PDS. `measurements[0]` is the shim, `[1..]` are stage2..N. - `at.zkwid.key/` is `zkwid::lexicon::Key`. It is written only by N through N's own OAuth session (client `store::N_CLIENT`). - `at.zkwid.demo.log/` is `zkwid::lexicon::DemoLog`. The demo workload writes it through N's proxy. - Reserved, defined in `zkwid::nsid::RESERVED`: `at.zkwid.key` and `at.zkwid.workload`. - A workload scope naming a reserved collection makes `client_scope` fail, so its metadata returns 404. - The proxy always refuses writes to reserved collections, and refuses every `at.zkwid.*` lxm. Write records with a `$type` field, using `zkwid::lexicon::record(nsid, &value)`. ## N routes (`zkwid-n`) | Route | Module | |---|---| | `GET /.well-known/did.json` | `did` | | `GET /oauth-client-metadata.json`, `GET /login?handle=`, `GET /callback` | `oauth` (N's own client, scope `atproto repo:at.zkwid.key`) | | `GET /{handle}/{rkey}/oauth-client-metadata.json`, `GET /{handle}/{rkey}/login`, `GET /{handle}/{rkey}/callback` | `oauth` (per-workload client, scope `zkwid::scope::client_scope(workload.scope)`) | | `POST /xrpc/at.zkwid.createChallenge`, `POST /xrpc/at.zkwid.createKey`, `POST /xrpc/at.zkwid.deleteKey` | `keys` | | `ANY /xrpc/{nsid}` (fallback, not `at.zkwid.*`) | `proxy` | Login routes answer `302` to the PDS authorize URL. The callback answers `200` with JSON `{ "did": ..., "client": ... }`. XRPC errors use `{ "error": Name, "message": text }` with status: - `400` for `InvalidRequest`, `MeasurementMismatch`, `InvalidReceipt`, `ChallengeInvalid`, `ChallengeUsed` - `401` for `AuthRequired` / `InvalidToken` - `403` for `HostNotAllowed`, `NotAuthorized`, `ScopeMissing` - `404` for `WorkloadNotFound`, `KeyNotFound` ## Store keys - `Store::client_key(client_id_url)` gets or creates a P-256 client assertion key per OAuth client_id. - `Session.client` is the workload rkey, or `store::N_CLIENT` (`""`) for N's own client. - Sessions are keyed by (did, client). - `Store::put_request(state, json)` stores a serialized `atproto_oauth::workflow::OAuthRequest`, together with the client string and the client_id. ## H helpers - `zkwid_h::stage_bytes(name)` runs `cargo build --release --target wasm32-unknown-unknown` for `stages/` and returns the wasm bytes. - `zkwid_h::run_stage(wasm, input)` runs a stage natively in wasmi 0.31, providing the same `env.keygen` import the guest does. ## Service auth - **H calls N:** `atproto_xrpcs::mint_service_auth(h_key, "did:web:h.zkwid.test", "did:web:zkwid.test#zkwid", lxm, 60 s)`. - **Instance calls the proxy:** the same call with `iss = `, signed by the instance K256 private key (`KeyData::new(K256Private, seed)`). - `lxm` is the proxied NSID. - An `atproto-proxy` header is optional. - **N verifies:** `atproto_xrpcs::verify_service_auth` with `zkwid::resolver::DidKeyResolver { inner: DidResolver }`. - The policy is `ServiceAuthPolicy::new(lxm, "did:web:zkwid.test#zkwid")`. - The proxy also rejects any jti already in `Store::seen_jti`. ## Challenge - `zkwid::challenge::mint(n_key, "did:web:zkwid.test", sub = H, repo = U, workload, 300 s)` produces an ES256 JWT with header `typ: zkwid-challenge+jwt`. Its claims are `Challenge`. - `nonce(token)` is `sha256(token)`. - `rkey(token)` is `base32-lower-nopad(sha256(token))`. ## ZK (`zkwid-zkvm`) - `ProveInput { stages, seed, nonce: challenge::nonce(token), aud: "did:web:zkwid.test" }` goes to `prove`, which returns `bincode(Receipt)`. - N calls `verify(receipt)`, which checks `receipt.verify(image_id())` and returns `Journal`. - `Journal.key` is the 33-byte compressed K256 public key, `KeyData::new(K256Public, key)`. Its did:key is `KeyData`'s `Display`. - `Journal.measurements[i]` is `sha256(stages[i])`. It equals `zkwid::measure::cid_digest(workload.measurements[i].wasm.ref.$link)`. - Dev mode: `RISC0_DEV_MODE=1` makes fake receipts. - The `zkwid-n/prod` feature enables `zkwid-zkvm/disable-dev-mode`, which enables `risc0-zkvm/disable-dev-mode`. - wasmi is pinned to 0.31 in both the guest and H. ## Stage ABI (`stages/*`, wasm32-unknown-unknown, no_std) - **Exports:** - `memory` - `input_ptr() -> i32` - `output_ptr() -> i32` - `run(len: i32) -> i32` (returns the output length; a trap means failure) - **Imports:** module `env`, `keygen(seed_ptr: i32, out_ptr: i32) -> i32`. It reads 32 bytes and writes a 33-byte compressed K256 public key, returning 33. - **Shim:** the input is a 32-byte seed. It calls `keygen` and outputs the 33-byte public key. - **Stage2:** the input is JSON `{ "instance": s, "createdAt": t }`. The output is the JSON body of an `at.zkwid.demo.log` record, `{ "$type": "at.zkwid.demo.log", "text": "hello from ", "createdAt": t }`. - **Build:** `cargo build --release --target wasm32-unknown-unknown --manifest-path stages//Cargo.toml`. The output is `stages//target/wasm32-unknown-unknown/release/.wasm`.