~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/zkwid

0
0
main
96 lines · 5.8 KB Raw History
# 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/<rkey>` 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/<base32(sha256(challenge))>` is `zkwid::lexicon::Key`. It is written only by N through N's own OAuth session (client `store::N_CLIENT`).
- `at.zkwid.demo.log/<tid>` 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/<name>` 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 = <instance did:key>`, 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 <instance>", "createdAt": t }`.
- **Build:** `cargo build --release --target wasm32-unknown-unknown --manifest-path stages/<name>/Cargo.toml`. The output is `stages/<name>/target/wasm32-unknown-unknown/release/<name>.wasm`.