Summary
A DIDComm v2 protocol for requesting and receiving zero-knowledge proofs that a named program executed correctly on inputs bound to a transaction context, without revealing private inputs.
Summary
The Proof of Execution (PoE) 1.0 protocol lets a Requester ask a Prover to run a named program and return a zero-knowledge proof that the program executed correctly on inputs bound to a transaction context — while keeping private inputs confidential. It is generic and supports many programs (e.g., device checks, policy evaluators, cryptographic attestations) with program-specific public schemas validated by the Requester.
Goals
- Generic request/response flow for program execution proofs
- Strong anti-replay via binding to
{nonce, context_hash, session_id} - Minimal disclosure:
proof-onlyby default; optional summaries or encrypted evidence references - Program capability negotiation when needed
- Local registry enforcement for verifying keys and program parameters
Roles
requester: Verifier relying on the PoE result (e.g., login, payments, issuance)prover: Holder/Agent that executes or delegates the program and returns the proofattester(optional): Compute service (possibly TEE-backed) that can generate PoE on Prover’s behalf
Security Model (Summary)
- Binding: Every PoE instance MUST bind to
{nonce, context_hash, session_id}to prevent replay/cross-use. - Freshness: Messages carry short TTLs; programs may enforce timing/sequence constraints in-proof.
- Privacy: Only the ZK proof and public outputs are revealed. Private inputs never leave the Prover unless policy demands by-reference encrypted evidence.
- Unlinkability: Use pairwise DIDs and ephemeral keys per thread.
Discovery
Provers advertise PoE support with Discover-Features 2.0. They should disclose supported program_ids and optional resource hints.
{
"type":"https://didcomm.org/discover-features/2.0/disclose",
"body":{
"disclosures":[
{"feature_type":"protocol","id":"https://didcomm.org/poe/1.0","roles":["prover"]},
{"feature_type":"goal-code","id":"poe/programs","claims":{"programs":[
{"program_id":"vendor.policy.eval.v1","max_runtime_s":60,"max_artifact_mb":8}
]}}
]
}
}
States
Requester
IDLE → REQUEST_SENT → (optional) CHALLENGE_SENT* ← SUBMIT_RECEIVED → COMPLETE | PROBLEM
Prover
IDLE ← REQUEST_POE ↔ (optional) PROPOSE/ACCEPT ↔ (optional) CHALLENGE ← → SUBMIT_POE ← COMPLETE | PROBLEM
Basic Walkthrough
- Requester sends
request-poenaming one or moreprogram_ids with public constraints and binds them to{nonce, context_hash, session_id}. - Prover may reply with
propose-poeto adjust capabilities or proceed directly. - For interactive programs, Requester MAY send a
challengedescribing steps. - Prover returns
submit-poewithpublicblock andzkproof. - Requester verifies registry, binding, proof, and policy, then responds with
completeorproblem-report.
Message Reference
All PoE messages use DIDComm v2 with type: https://didcomm.org/poe/1.0/<message-name> and standard headers (id, thid/pthid, from, to, expires_time).
request-poe
Message Type URI: https://didcomm.org/poe/1.0/request-poe
Purpose: Ask the Prover to run a program and return PoE.
From: requester → prover
{
"type": "https://didcomm.org/poe/1.0/request-poe",
"id": "req-123",
"pthid": "txn-abc123",
"body": {
"programs": [
{
"program_id": "vendor.policy.eval.v1",
"public_constraints": { "params_hash": "0x…32B" }
}
],
"bind_to_context": {
"nonce": "0x…32B",
"context_hash": "0x…32B",
"session_id": "0x…16B"
},
"inputs": {
"schema": "vendor.policy.eval.v1/inputs@1",
"by_value": { "params": { "ttl_s": 60, "policy_ref": "policy-abc@1" } }
},
"disclosure": "proof-only",
"expiry": "2025-11-11T14:35:00Z",
"transport_hints": { "webrtc_ok": true, "offline_ok": true },
"policy": { "max_runtime_s": 60, "max_artifact_mb": 8 }
}
}
Notes:
programsMAY include several options ordered by preference.inputsdescribe public inputs supplied by the requester; others may be computed by the Prover or Attester.disclosurecontrols summaries or encrypted evidence refs beyond the ZK proof.
propose-poe
Message Type URI: https://didcomm.org/poe/1.0/propose-poe
Purpose: Negotiate capabilities or parameters (optional).
From: prover → requester
{
"type": "https://didcomm.org/poe/1.0/propose-poe",
"id": "prop-1",
"thid": "req-123",
"body": {
"program": {
"program_id": "vendor.policy.eval.v1",
"public_constraints": { "params_hash": "0x…32B" }
},
"disclosure": "proof-only"
}
}
accept-poe / decline-poe
Message Type URI: https://didcomm.org/poe/1.0/accept-poe and …/decline-poe
Purpose: Confirm or decline the chosen program/params.
From: requester ↔ prover
{
"type": "https://didcomm.org/poe/1.0/accept-poe",
"id": "acc-1",
"thid": "req-123",
"body": {
"program_id": "vendor.policy.eval.v1",
"public_constraints": { "params_hash":"0x…32B" },
"inputs_digest": "0x…32B"
}
}
Parties MAY skip propose/accept if capabilities already match and proceed directly to submit-poe.
challenge
Message Type URI: https://didcomm.org/poe/1.0/challenge
Purpose: For interactive programs, carry step schedule or updates.
From: requester → prover
{
"type": "https://didcomm.org/poe/1.0/challenge",
"id": "chal-1",
"thid": "req-123",
"body": { "challenge_spec": { "steps":[{"action":"perform","payload":["taskA","taskB"]}], "ttl_s": 10 }, "sequence": 1 }
}
submit-poe
Message Type URI: https://didcomm.org/poe/1.0/submit-poe
Purpose: Deliver the ZK proof and optional summary/evidence.
From: prover → requester
{
"type": "https://didcomm.org/poe/1.0/submit-poe",
"id": "sub-1",
"thid": "req-123",
"body": {
"program_id": "vendor.policy.eval.v1",
"result": "pass",
"public": {
"schema": "vendor.policy.eval.v1/public@1",
"nonce": "0x…32B",
"context_hash": "0x…32B",
"session_id": "0x…16B",
"outputs_hash": "0x…32B",
"vk_hash": "0x…32B"
},
"zk": {
"scheme": "stark",
"circuit_id": "vendor-policy-eval-v1",
"vk_hash": "0x…32B",
"proof_b64": "base64(urlsafe)…"
},
"summary": { "evidence_summary": ["constraint_A_met","constraint_B_met"] }
},
"attachments": [
{ "id": "evidence-ref", "format": "by-reference", "media_type": "application/json",
"data": { "links": ["ipfs://…", "https://…/cap/…"], "alg": "A256GCM", "keyref": "eke-1" } }
]
}
Requirements:
publicMUST match the program’s public schema.zk.vk_hashMUST be in the Requester’s registry for thecircuit_id.- If
result = pass, the proof MUST verify and assert the program’s declared constraints/policy are satisfied.
complete / problem-report
Message Type URI: https://didcomm.org/poe/1.0/complete and …/problem-report
Purpose: Signal success (optionally with a receipt) or report a canonical error.
From: requester ↔ prover
{
"type": "https://didcomm.org/poe/1.0/problem-report",
"id": "err-1",
"thid": "req-123",
"body": { "code": "invalid_proof", "explain": "ZK verification failed or vk_hash not allowed", "details": { "circuit_id": "vendor-policy-eval-v1" } }
}
Registry & Schemas
Requesters maintain a local registry mapping program_id/circuit_id to allowed verifying keys and program parameters/hashes.
Example registry:
{
"vendor-policy-eval-v1": {
"vk_hash": ["0xVK_A", "0xVK_B"],
"params": ["0xPARAMS_A","0xPARAMS_B"],
"public_schema": "vendor.policy.eval.v1/public@1"
}
}
Example public schema (program-defined):
{
"type":"object",
"required":["nonce","context_hash","session_id","vk_hash"],
"properties":{
"nonce":{"type":"string"},
"context_hash":{"type":"string"},
"session_id":{"type":"string"},
"outputs_hash":{"type":"string","pattern":"^0x[0-9a-fA-F]{64}$"},
"vk_hash":{"type":"string"}
}
}
Requesters MUST reject any vk_hash or program parameter/hash not in the registry.
Transport & Packaging
- Encryption: DIDComm v2 Authcrypt or Anoncrypt
- Delivery: mediator-optional; Out-of-Band deep links or QR are fine
- Attachments: DIDComm v2 Attachments 2.0
- Streaming: Optional upgrade (e.g., WebRTC datachannel) for interactive challenges; the final proof still returns via DIDComm unless both parties agree otherwise
Verification Pipeline (Requester)
- Expiry: Reject if
expiryor TTL violated. - Registry: Check
program_id ∈ registry;vk_hashand any pinned parameters/hashes are allowed. - ZK Verify:
verify_zk(proof_b64, public, circuit_id, vk_hash)MUST pass. - Binding: Confirm
nonce,context_hash,session_idmatch the ones issued. - Program Policy: Enforce program-specific constraints and any additional contextual policy (e.g., device attestation, geo-velocity).
- Issue Receipt: Optionally issue a short-lived VC (e.g.,
ExecutionReceiptCredential) embedding aproof_hashfor audit.
Error Codes
program_not_supportedinputs_invalidexpired_challengepolicy_violationinvalid_proofvk_unknownparams_unknowncontext_mismatchtoo_largerate_limitedattester_unavailable
Every problem-report MUST include code and SHOULD include details.
Privacy & Retention
- Default
disclosure = proof-only. - If
proof+summary, include only aggregate fields (e.g., step booleans or aggregate metrics). - If
proof+evidence-ref, references MUST be E2EE (e.g., AES-GCM with DIDComm-negotiated key). - Server retention SHOULD be hashes/receipts only, with short TTLs (≤30 min) unless law/policy demands more.
Program Examples
WebAuthn presence as PoE (vendor.device.webauthn.v1)
- Public:
nonce,context_hash, RP ID hash, flags (UP/UV policy) - Proof: zkVM/zk-circuit proving assertion verified and flags met, bound to
nonce||context_hash - Result:
pass|fail
Additional programs can follow the same mold.
Conformance
- Nominal PASS: valid proof; binding matches; in-policy.
- Policy FAIL: valid proof but public outputs do not satisfy constraints →
result=failor reject per policy. - Replay FAIL: wrong
nonce/context_hash→ verify fails. - Tamper FAIL: integrity/public hash mismatch → verify/reject.
- Unknown VK/Params: registry rejects.
- Expired: TTL exceeded → reject before verify.
Versioning
- Protocol version is part of the PIURI (
/poe/1.0). - Program versions live in
program_id(e.g.,vendor.program.v1). - Verifiers MUST pin
vk_hashto acircuit_idand rotate via registry updates.
Example Generic Program
- Requester → Prover:
request-poewithprogram_id="vendor.policy.eval.v1",public_constraints.params_hash=0x…, program inputs, and{nonce, context_hash, session_id}. - Prover executes and generates proof.
- Prover → Requester:
submit-poewithpublicblock andzkproof,result="pass". - Requester verifies ZK + registry + policy and replies
complete(optionally minting a short-lived receipt VC).