Result Digest: Binding Tool Response Bytes Into the Receipt
Asqav binds the body of a tool response into the signed receipt via result_digest, a SHA-256 digest of the canonical-JSON tool output. The field pairs with the protectmcp:observation:result_bound receipt variant so an auditor can prove which exact response bytes the agent received without ever storing the response itself.
What is the result_digest field?
result_digest is an OPTIONAL string on POST /api/v1/agents/{id}/sign. The wire format is sha256:<64 lowercase hex>, the same form Asqav uses for every other digest field on the signing surface. When present, the digest is part of the signed envelope, which means a verifier with the response bytes can rehash and confirm a match without trusting the producer.
result_digest is designed for the protectmcp:observation:result_bound receipt variant. The wire-layer Pydantic guard result_bound_missing_result_digest enforces the forward pairing: a :result_bound receipt that arrives without the digest is rejected. This closes the false-attestation surface by guaranteeing that a result-bound receipt always carries the bytes it claims to bind.
How does Asqav implement Recommendation 7 with result_digest?
The NSA CSI states the logging requirement in the recommendations chapter:
All tool and model invocations should be logged, including the exact parameters, identities involved, and (where feasible) cryptographic hashes of results or output. These logs form the backbone of forensic response in the event of a breach or anomaly. NSA CSI U/OO/6030316-26, p.13
The parenthetical "where feasible, cryptographic hashes of results or output" is the exact gap result_digest fills. The Asqav signer never sees the response body. The SDK or proxy that observes the tool reply computes the digest locally, sends the 71-character token on the sign wire, and the cloud projects it into the signed envelope.
Storage cost is constant per receipt regardless of response size. The body itself never crosses the trust boundary, but the digest is an unsalted SHA-256, so a party holding it can hash candidate responses and test them against it. Where the response is drawn from a small set, treat the digest as disclosing which one it was.
How do I produce a result_bound receipt?
The Python SDK accepts result_digest as a keyword on Agent.sign. The caller computes the digest with the public canonicaliser asqav.canonical_json, which serialises the response under JCS so two producers with the same dict produce the same digest byte-for-byte.
JSON wire example
{
"action_type": "mcp:tool_response",
"receipt_type": "protectmcp:observation:result_bound",
"result_digest": "sha256:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730",
"capture_topology": "network_proxy"
}
Python SDK
import hashlib
import asqav
asqav.init()
agent = asqav.Agent.create("mcp-proxy-observer")
tool_response = {"rows": [{"id": 1, "name": "alice"}], "next_cursor": None}
digest = "sha256:" + hashlib.sha256(asqav.canonical_json(tool_response)).hexdigest()
receipt = agent.sign(
"mcp:tool_response",
{"tool": "users.list", "request_id": "req-9f3a"},
receipt_type="protectmcp:observation:result_bound",
result_digest=digest,
capture_topology="network_proxy",
)
print(receipt.signature_id)
curl
curl -X POST https://api.asqav.com/api/v1/agents/agt_proxy_001/sign \
-H "X-API-Key: $ASQAV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action_type": "mcp:tool_response",
"receipt_type": "protectmcp:observation:result_bound",
"result_digest": "sha256:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730",
"capture_topology": "network_proxy"
}'
How does a verifier check a result_bound receipt?
The result-digest match is an OFFLINE check the relying party runs with the response bytes it holds. The verifier rehashes those bytes, formats the result as sha256:<hex>, and compares to the value inside the signed envelope. The IETF Compliance Receipts draft documents the canonicalisation rules.
The cloud never receives the response bytes, so POST /api/v1/verify does not perform this comparison. That endpoint loads the persisted receipt and re-verifies the ML-DSA signature over the signed envelope. It confirms the result_digest token is authentic and untampered, but it does not rehash anything because it has nothing to rehash.
For audit packs, asqav audit-pack export includes the response-side rehash script in the tarball so an offline auditor can validate every :result_bound receipt without an Asqav account.
Why not store the response body itself?
Three reasons.
Privacy
Tool responses routinely carry PII, secrets, or customer data that the customer's compliance team has explicit policies against centralising. A 71-character digest is not the response body, but it is not a guarantee of confidentiality either: it is an unsalted SHA-256, so treat it as disclosing which response it was when the response is drawn from a small set. Storing the blob itself centralises the data outright.
Cost
Per-receipt storage is bounded. A 12 MB tool response stores as 71 bytes in signature_records, not 12 MB.
Trust model
The digest is signed. The response is not. A producer who tampers with the response cannot tamper with the digest without breaking the ML-DSA-65 signature. The verifier's trust is in the signed bytes, not in any storage layer.