jcp-wire: a caller-signed challenge, and endpoint evidence bound to the answer #394

Merged
dcharlot merged 1 commit from feat/jcp-wire-evidence into main 2026-10-21 12:41:10 -04:00
Owner

Sweep 2026-10-19 item #5, wire side; the sandbox side merged in #392. A WireRequest had no challenge slot and a WireResponse carried no endpoint evidence. Freshness came only from the caller's now stamp, so a caller had no way to ask the platform an endpoint runs on to vouch for an answer.

What changes (SPEC §11)

  • WireRequest.challenge. 32 caller-chosen bytes, covered by the caller's signature (challenged_signable_bytes, sign_challenged). It is omitted when absent, so a request without one keeps its bytes and its signature.
  • WireResponse.evidence { kind, report }, omitted when absent. An endpoint configured with_attester answers an authenticated, challenged request with a report over:
    report_data = BLAKE3-XOF-64(derive_key = "jcp/1 wire evidence v1", challenge ‖ BLAKE3(receipt))
    
    The challenge makes it the caller's; the receipt makes it this answer's.
  • Forged requests get no evidence. A request denied bad-signature gets none, so a forgery can't make the platform produce reports on demand.
  • Repeats and restarts. A repeat gets its first evidence, and a restart rebuilds it.
  • WireResponse::verify_evidence checks against the challenge the caller sent, never one read back, through a caller-supplied EvidenceVerifier. jcp-wire stays free of any TEE crate, and a transport's own evidence (an exported authenticator on TLS) is a profile of the same slot.
  • Conformance class C28, with a check.

Verification (build host)

  • jcp-rs: 579 tests (--locked), and every conformance class passes, including C28.
  • Clippy and rustdoc -D warnings are clean on the touched crates.
  • Mutation testing: 9 mutants, all killed:
    • the challenge unsigned, or ignored at verification;
    • evidence given for forgeries, never, or unasked;
    • the challenge or the receipt left out of the binding;
    • missing evidence passing;
    • the verifier ignoring the caller's challenge.
  • Repo checks pass, including lockfiles.

🤖 Generated with Claude Code

Sweep 2026-10-19 item #5, wire side; the sandbox side merged in #392. A `WireRequest` had no challenge slot and a `WireResponse` carried no endpoint evidence. Freshness came only from the caller's `now` stamp, so a caller had no way to ask the platform an endpoint runs on to vouch for an answer. ## What changes (SPEC §11) - **`WireRequest.challenge`.** 32 caller-chosen bytes, covered by the caller's signature (`challenged_signable_bytes`, `sign_challenged`). It is omitted when absent, so a request without one keeps its bytes and its signature. - **`WireResponse.evidence { kind, report }`**, omitted when absent. An endpoint configured `with_attester` answers an authenticated, challenged request with a report over: ```text report_data = BLAKE3-XOF-64(derive_key = "jcp/1 wire evidence v1", challenge ‖ BLAKE3(receipt)) ``` The challenge makes it the caller's; the receipt makes it this answer's. - **Forged requests get no evidence.** A request denied `bad-signature` gets none, so a forgery can't make the platform produce reports on demand. - **Repeats and restarts.** A repeat gets its first evidence, and a restart rebuilds it. - **`WireResponse::verify_evidence`** checks against the challenge the caller **sent**, never one read back, through a caller-supplied `EvidenceVerifier`. jcp-wire stays free of any TEE crate, and a transport's own evidence (an exported authenticator on TLS) is a profile of the same slot. - **Conformance class C28**, with a check. ## Verification (build host) - **jcp-rs:** 579 tests (`--locked`), and every conformance class passes, including C28. - **Clippy and rustdoc** `-D warnings` are clean on the touched crates. - **Mutation testing:** 9 mutants, all killed: - the challenge unsigned, or ignored at verification; - evidence given for forgeries, never, or unasked; - the challenge or the receipt left out of the binding; - missing evidence passing; - the verifier ignoring the caller's challenge. - **Repo checks** pass, including lockfiles. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcp-wire: a caller-signed challenge, and endpoint evidence bound to the answer
All checks were successful
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 48s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m15s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m38s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m17s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 2m53s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m1s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m36s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 9m2s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m53s
ca3de8e3b3
Sweep 2026-10-19 item #5, wire side. A WireRequest had no challenge slot
and a WireResponse carried no endpoint evidence. Freshness came only from
the caller's `now` stamp, so a caller had no way to ask the platform an
endpoint runs on to vouch for an answer.

jcp-wire (SPEC §11):
- WireRequest.challenge: 32 caller-chosen bytes, covered by the caller's
  signature (challenged_signable_bytes, sign_challenged). It is omitted when
  absent, so a request without one keeps its bytes and its signature.
- WireResponse.evidence { kind, report }, omitted when absent. An endpoint
  configured with_attester answers an authenticated challenged request with
  a report over evidence_report_data = BLAKE3-XOF-64(derive_key "jcp/1 wire
  evidence v1", challenge ‖ BLAKE3(receipt)). The challenge makes it the
  caller's; the receipt makes it this answer's.
- No evidence for a request denied bad-signature, so a forgery cannot make
  the platform produce reports on demand. A repeat gets its first evidence,
  and a restart rebuilds it through respond(), so the attester is
  configured before with_journal.
- WireResponse::verify_evidence checks against the challenge the caller
  SENT, never one read back, through a caller-supplied EvidenceVerifier.
  jcp-wire stays free of any TEE crate, and a transport's own evidence (an
  exported authenticator on TLS) is a profile of the same slot.

SPEC §11: message shapes, the evidence binding and rules, and conformance
class C28 with a C28 check.

Tests: 4 new, plus C28. A challenged request gets evidence bound to its
answer; evidence comes only where asked and able, with an unchallenged
request byte-identical and signed over the old bytes; the challenge is
signed, so swapping or stripping it is refused with no evidence; a repeat
gets its first evidence and a restart rebuilds it. Mutation testing: 9
mutants, all killed.

Verified on the build host: jcp-rs 579 tests (--locked) and every
conformance class; clippy -D warnings (jcp-wire, -conformance, -threshold)
and rustdoc -D warnings (jcp-wire) clean. Repo checks pass, including
lockfiles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!394
No description provided.