Request binding (transcript-v1)
Concept
A passport says who you are. Request binding proves what you are doing. transcript-v1 is a canonical, signed transcript of the request, so a passport can only be used for the exact request it was bound to. This is what makes a stolen passport useless for anything but an identical, in-window replay (which the replay layer then blocks too).
Implementation
- Lives in
pkg/proof(transcript.go). - The transcript binds: method, authority, path, query (normalized), selected headers (allow
list), nonce, body hash, audience,
route_id, passportjti, issued-at bucket, and key binding. - The proof is verified by the adapter/middleware before replay state is consumed.
- Deny reasons:
request_binding_mismatch— the request does not match the signed transcript (tampered path/nonce/route, etc.).invalid_request_proof— the proof signature is malformed or does not verify.
Example
The clearest end-to-end demonstration is the provider/gateway flow, which signs real requests and shows both allow and tamper-deny:
make demo-provider-gateway
It proves, among other things:
- a broker-issued, transcript-v1-bound request allows;
- changing the path after signing →
request_binding_mismatch; - a corrupted proof →
invalid_request_proof.
You can also produce signed request headers yourself (no broker) with trustplane sign, which
can print a ready-to-run curl via --curl:
# Conceptual shape — sign a request and emit the headers/curl the adapter accepts
trustplane sign \
--private-key "$CLIENT_KEY" \
--method GET \
--path /orders \
--audience acme.demo.orders \
--route-id acme.demo.orders.read \
--curl
Send the produced request to the adapter and it verifies; tamper with the path and it denies.
Cross-language conformance
transcript-v1 must produce the same canonical string and digest in every language, or proofs signed by a non-Go SDK would not verify. The conformance suite checks this:
make transcript-conformance
This runs Go, JavaScript (scripts/conformance/transcript_v1_conformance.js), and Python
(scripts/conformance/transcript_v1_conformance.py) over shared vectors and asserts identical
output. It is part of the v0.1 acceptance gate.
What to notice
- The binding covers the body hash, so you cannot swap the payload after signing.
- It covers the
route_id, so a proof foracme.demo.orders.readcannot be reused for a different route. - It covers an issued-at bucket and nonce, which (together with replay consume) bound the reuse window tightly.
→ Next: Replay protection.