~ 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

HostServesDID
zkwid.testN (zkwid-n)did:web:zkwid.test, service #zkwid of type ZkwidService
h.zkwid.testH stub (zkwid-h)did:web:h.zkwid.test, #atproto K256 key
pds.zkwid.test, *.pds.zkwid.testatproto-pds binarydid:web:pds.zkwid.test
plc.zkwid.testatproto_testkit fake PLCnone

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)

RouteModule
GET /.well-known/did.jsondid
GET /oauth-client-metadata.json, GET /login?handle=, GET /callbackoauth (N's own client, scope atproto repo:at.zkwid.key)
GET /{handle}/{rkey}/oauth-client-metadata.json, GET /{handle}/{rkey}/login, GET /{handle}/{rkey}/callbackoauth (per-workload client, scope zkwid::scope::client_scope(workload.scope))
POST /xrpc/at.zkwid.createChallenge, POST /xrpc/at.zkwid.createKey, POST /xrpc/at.zkwid.deleteKeykeys
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.