Summary
A DIDComm-based coordination protocol that lets agents create, share, and retire end-to-end encrypted data vaults (EDV) on heterogeneous backends (e.g., S3, Arweave). It handles who gets access, for how long, and to which objects, while all actual bytes flow over the EDV HTTP API as JOSE/JWE ciphertext with encrypted indexes for equality querying.
Summary
Vaults 1.0 is a DIDComm-based coordination protocol that lets agents create, share, and retire end-to-end encrypted data vaults (EDV) on heterogeneous backends (e.g., S3, Arweave). It handles who gets access, for how long, and to which objects, while all actual bytes flow over the EDV HTTP API as JOSE/JWE ciphertext with encrypted indexes for equality querying. It purposely does not handle signing—pair it with your existing Signing 1.0 protocol via content references (digest + location + capability). (identity.foundation)
This draft also defines an **optional Threshold Decryption ** that allows data (or its content-encryption key) to become decryptable only after N-of-M approvals, coordinated via DIDComm and Signing 1.0.
Goals / Non-Goals
Goals
- Provision ephemeral or durable shared vaults among agents.
- Delegate least-privilege capabilities (time/size/path caveats).
- Support multi-recipient encryption and encrypted equality indexes.
- Coordinate N-of-M approval-gated release and true threshold decryption (cryptographic N-of-M) for decryption events.
Non-Goals
- No signing, canonicalization, threshold signature orchestration, or business policy logic (defer to Signing 1.0).
- No duplicate CRUD—agents use the EDV HTTP API for data path. (identity.foundation)
Roles
- Requester: initiates a shared vault for a workflow (controller by default).
- Participant: granted constrained access (read/write/replicate).
- Vault Host: EDV server fronting storage (S3, Arweave, etc.).
External References (normative)
- EDV v0.1: model, sequence/OCC, encrypted indexes, HTTP API. (identity.foundation)
- JWE (RFC 7516): content encryption, general JSON serialization (multi-recipient). (IETF Datatracker)
- HPKE (RFC 9180): KEM+KDF+AEAD envelopes for sealed secrets / key delivery. (IETF Datatracker)
Backend notes
- S3: deletable objects.
- Arweave: immutable "permanent" storage; practical deletion via cryptographic erasure (destroy keys). (SwissBorg Academy)
- IPFS: decentralized storage
Data Model
VaultDescriptor
{
"vault_id": "urn:edv:zA1...:vault123",
"controller": "did:example:controller",
"base_url": "https://edv.example.com/edvs/vault123",
"zcap_root": { "...": "root-capability" },
"encryption": { "content": "JWE+ECDH-ES", "index_hmac": "HMAC-SHA-256" },
"retention": { "kind": "ephemeral|durable", "expires_at": "2025-10-20T00:00:00Z" },
"backend": { "type": "s3|arweave|...", "hints": { "region": "us-east-1" } },
"limits": { "max_docs": 200, "max_bytes": 104857600, "max_doc_bytes": 10485760 },
"index_schema": ["workflow_id","digest","stage"]
}
EDV documents carry
id,sequence, and a JWE payload; searchable encrypted indexes are computed client-side. (identity.foundation)
ContentRef (to bridge to Signing 1.0)
{
"vault_id": "urn:edv:...:vault123",
"doc_id": "z19abc...",
"digest": "sha256-BASE64URL(...)",
"capability": { "...": "zcap granting read (and optionally write)" }
}
Protocol Overview
Control plane: DIDComm v2 messages for negotiation, capability distribution, lifecycle (create/join/seal/tombstone), optional replication.
Data plane: EDV HTTP for CRUD/queries; attach ZCAP invocation; encrypt/decrypt at the edge; use JWE general serialization for multi-recipient. (identity.foundation)
Decryption events: (optional) may be approval-gated (N-of-M) or use true cryptographic threshold decryption per the in this spec. In both cases, keys are never published on immutable backends; key release or partial-decrypt shares occur off-chain via DIDComm.
Messages
All messages are DIDComm v2
type=https://didcomm.org/vaults/1.0/<name>withid,pthid(parent thread = workflow), and optionalthidthreading. (identity.foundation)
propose
Propose creating a shared vault.
Body
{
"purpose": "workflow:pdf-signing#42",
"participants": ["did:example:alice","did:example:bob"],
"constraints": {
"retention": "ephemeral",
"ttl_seconds": 86400,
"limits": { "max_docs": 200, "max_bytes": 104857600, "max_doc_bytes": 10485760 }
},
"backend_prefs": ["s3","arweave"],
"index_schema": ["workflow_id","digest","stage"]
}
Expected reply: offer (from provisioner/host or a peer offering to host).
Workflow binding convention: When a vault is provisioned for a Workflow 1.0 instance, the purpose field SHOULD use the format workflow:<template_id>#<instance_id> (e.g., workflow:student-id-issuance#4b6f...). This enables the vault host and participants to correlate the vault with its owning workflow. The Workflow protocol's start message references the resulting vault via vault_ref. Retention and TTL constraints SHOULD align with the expected workflow duration.
offer
Return a ready vault.
Body
{
"vault_descriptor": { "...": "VaultDescriptor" },
"provisioner": "did:example:host"
}
grant-access
Controller delegates a capability to a participant (ZCAP-LD).
Body
{
"to": "did:example:bob",
"capability": {
"...": "zcap with caveats (verb=read|write|query|replicate, path=/docs/pdf-42-*, expires_at, max_bytes, non_delegable)"
}
}
Capabilities and caveats per ZCAP-LD; delegation chains MUST verify on the server. (w3c-ccg.github.io)
Problem reports: cap-invalid, cap-expired, cap-unauthorized.
notify (optional)
Lightweight announcement of interesting changes (new doc, sealed, replicated).
Body
{
"kind": "doc-created|doc-updated|sealed|replicated",
"doc_id": "z19abc...",
"indexed": { "workflow_id": "HMAC(...)", "stage": "HMAC(...)" }
}
replicate (optional)
Ask a mirror host to replicate ciphertext to another EDV (e.g., S3 → Arweave).
Body
{
"target": { "type": "arweave", "endpoint": "https://edv.mirror/edvs/vaultX" },
"scope": { "prefix": "pdf-42-", "include_indexes": true }
}
Reply: replicate-receipt with list of digests and target doc IDs.
seal
Rotate capabilities to read-only; freeze the workspace.
Body
{ "mode": "read-only" }
tombstone
Retire a vault.
- For S3/ordinary EDV: delete documents + indexes.
- For Arweave: perform cryptographic erasure (destroy keys) and emit a finalization record listing affected digests—data stays immutable but unreadable without keys. (SwissBorg Academy)
Body
{
"strategy": "delete|destroy-keys",
"evidence_doc_id": "z1finalnote..."
}
Flows
Capability discovery (pre-flight)
Use Discover Features 2.0 to check support for vaults/1.0, max sizes, backends, replication ability. (didcomm.org)
Two-party ephemeral vault for PDF signing (happy path)
-
Alice → Bob + Host:
propose(purpose, ttl, limits, backends). -
Host → Alice:
offer(VaultDescriptor + root zcap). -
Alice → Bob:
grant-access(write to/docs/pdf-42-*, TTL 24h). -
Data path (EDV):
- Alice PUT encrypted PDF (
stage=src). - Signing 1.0 sends
ContentRefto Bob; Bob GET + verify digest; Bob PUT partial signature (stage=partial). - Aggregator PUT final signature + receipt (
stage=final).
- Alice PUT encrypted PDF (
-
Alice → All:
seal(read-only). -
(Optional)
replicateto Arweave archive vault; thentombstonethe working vault. (identity.foundation)
State Machine (controller view)
| State | Event | Next State | Notes |
|---|---|---|---|
NEW |
offer accepted |
ACTIVE |
VaultDescriptor stored; root zcap held by controller. |
ACTIVE |
grant-access |
ACTIVE |
Participants can perform EDV ops per caveats. |
ACTIVE |
seal |
SEALED |
Rotate caps to read-only; no more writes. |
SEALED |
replicate (opt.) |
SEALED |
Mirror ciphertext; collect receipts. |
SEALED |
tombstone |
RETIRED |
Delete or cryptographically erase. |
EDV Usage (normative)
- Encryption: Agents MUST encrypt content as JWE before upload; multi-party access MAY use General JSON Serialization for multiple recipients. (IETF Datatracker)
- Indexes: Agents SHOULD compute equality-only indexes via HMAC and upload with each document to enable queries with minimal leakage. (identity.foundation)
- Concurrency: Agents MUST respect
sequencefor optimistic concurrency control and retry on conflict. (identity.foundation)
Authorization (normative)
- Capabilities MUST be expressed as ZCAP-LD documents, bound to vault endpoints and caveated by:
expires_at, verbs (read|write|query|replicate), path/prefix, and size/byte limits; optionally non-delegable. (w3c-ccg.github.io) - Hosts MUST verify capability chains and caveats for each EDV request (out of band from DIDComm messages).
Threshold Decryption (Optional)
Purpose: Make decryption happen only after N-of-M approvals. Two modes are supported:
- Approval-gated release (policy-level) — N approvals collected via DIDComm / Signing 1.0; coordinator releases the CEK as a sealed secret (HPKE) to the authorized device.
- True cryptographic threshold decryption — The CEK (or data) is encrypted under a threshold public key; any N holders produce partial decrypt shares that combine to recover the CEK (or plaintext). No single party can decrypt alone.
This covers (2). Mode (1) already works without changes (use Signing 1.0 threshold collection +
sealed-secret@1delivery).
Threshold KEM Header (attached to the object descriptor)
{
"threshold_kem": {
"scheme": "threshold-kem@1",
"t": 2,
"n": 3,
"pub": "base64url(public_key_bytes)",
"params": {
"kem": "ECIES-X25519",
"kdf": "HKDF-SHA256",
"aead": "AES-256-GCM"
},
"cipher_cek": "base64url(...)", // CEK encrypted under the threshold public key
"aad": {
"session_id": "sess_...",
"object_id": "so_...",
"kem_params_hash": "sha-256:...",
"policy_hash": "sha-256:..." // optional but recommended
}
}
}
If you encrypt the data directly under the threshold key, use
cipher_datainstead ofcipher_cek. The rest of the flow is identical; the aggregator yields plaintext rather than CEK.
Message Reuse
We reuse the existing DIDComm messages; we only define what goes in them.
request (decrypt session using threshold)
Use vaults/1.0 to set up access + transport, and Signing 1.0 (recommended) to orchestrate the threshold session:
{
"type": "https://didcomm.org/signing/1.0/request-signing",
"body": {
"session": { "session_id": "sess_...", "mode": { "type": "threshold" } },
"object": { "...": "Signable (or Decryptable) Object + threshold_kem header" },
"suite": { "suite": "threshold-kem@1" },
"constraints": {
"not_before": "...", "expires_time": "...",
"intended_audience": ["did:ex:device"], "use_limit": 1
}
}
}
partial-signature (carries partial decrypt share)
{
"type": "https://didcomm.org/signing/1.0/partial-signature",
"body": {
"session_id": "sess_...",
"object_id": "so_...",
"signer": "did:ex:holder2",
"suite": "threshold-kem@1",
"kind": "partial-decrypt",
"data": {
"share": "base64url(partial_decrypt_share)",
"aad_hash": "sha-256:..." // MUST equal hash(serialize(threshold_kem.aad))
}
}
}
combine (threshold met)
{
"type": "https://didcomm.org/signing/1.0/combine",
"body": {
"session_id": "sess_...",
"status": "threshold_met",
"aggregation_result": {
"type": "threshold-decrypt@1",
"n": 2, "m": 3
}
}
}
provide-artifacts (deliver CEK or plaintext)
CEK delivery (recommended) sealed to the device using HPKE (RFC 9180) and bound to a one-use authorization token (counter, expiry, device):
{
"type": "https://didcomm.org/signing/1.0/provide-artifacts",
"body": {
"session_id": "sess_...",
"artifacts": [
{
"type": "sealed-secret@1",
"suite": "envelope-hpke@1",
"aad": { "ticket_digest": "sha-256:..." },
"ciphertext": "base64url(HPKE(CEK))",
"enc": { "kem": "X25519", "kdf": "HKDF-SHA256", "aead": "AES-256-GCM", "ek_pub": "..." }
}
],
"token": {
"token": {
"typ": "signing-ticket",
"session_id": "sess_...",
"scope": "decrypt",
"device": "did:ex:device#k1",
"ctr": 42,
"exp": "2025-10-19T16:02:00Z",
"cap": 1
},
"sig": { "suite":"jws-ed25519@1","kid":"did:ex:coord#k1","value":"..." }
}
}
}
(Data-direct variant: stream plaintext or attach a reference after the same token checks.)
Registry Additions (Threshold Decrypt)
Suites
| Registry Key | Description | Inputs | Outputs |
|---|---|---|---|
threshold-kem@1 |
Threshold public-key encryption for CEK (ElGamal/ECIES/HPKE-style). | KEM header (cipher_cek or cipher_data), holder’s private share, aad_hash |
partial_decrypt_share |
Implementations MUST commit to
aad_hash = sha256(serialize(aad))to prevent cross-session replay. The concrete math (curve/KEM) is pluggable; publish parameters viakem_params_hash.
Aggregators
| Registry Key | Description | Input | Output |
|---|---|---|---|
threshold-decrypt@1 |
Combines N partial decrypt shares into CEK (or plaintext). | N partial-signature(kind=partial-decrypt) with consistent aad_hash + common KEM header |
CEK (then sealed-secret@1) or plaintext |
** Problem Reports (delta)**
share-invalid— malformed or fails combine/validationshare-duplicate— duplicate signer/indexthreshold-not-reached— insufficient valid sharesaggregation-failed— combine failed integrity checks
Security Requirements (delta)
- No single holder can decrypt alone; a single share reveals nothing.
- Binding: Shares MUST commit to
aad_hash(session/object/policy binding). - Replay-proof release: CEK/plaintext delivery MUST be bound to an authorization token (device DID, monotonic
ctr, expiry). - Verification: Aggregator MUST verify AEAD tag (or CEK checksum) after combine; reject otherwise.
- DKG: Secure DKG ceremony is out-of-scope for messages; deployments MUST pin
kem_params_hash.
Security Considerations
- Client-side encryption: Providers see only ciphertext and HMAC'd indexes; plaintext lives only at the edge. (identity.foundation)
- Transport: DIDComm authcrypt recommended for authenticity + confidentiality of control messages. (identity.foundation)
- Capabilities: Prefer short TTLs, tight path scoping, byte limits, and non-delegability where appropriate. Rotate caps at seal. (w3c-ccg.github.io)
- Leakage: Encrypted equality indexes leak existence and equality; avoid sensitive tags; no range/prefix queries. (identity.foundation)
- Immutability backends: For Arweave, treat "delete" as cryptographic erasure (destroy keys) and publish a finalization record listing affected digests. (SwissBorg Academy)
- Threshold Decrypt : Never publish keys on immutable backends. Shares, tokens, and sealed secrets travel only via DIDComm.
- Workflow-scoped vaults: When a vault is bound to a Workflow 1.0 instance, the vault TTL SHOULD be synchronized with the workflow's expected duration. If the workflow completes, the Coordinator or Processor SHOULD trigger
seal; if canceled,tombstone. Key material used for vault encryption SHOULD be wiped promptly after the vault is sealed or tombstoned. - Transient data: Workflow
$transientfields (single-cycle ephemeral values) MUST NOT be stored in vaults. They exist only in volatile memory during a single workflow advance cycle. - Guard resolution timing: When a Workflow Processor resolves vault-backed
$refpointers for guard evaluation, the decrypted plaintext MUST be held only in memory for the duration of the evaluation and MUST NOT be cached, logged, or persisted.
Privacy Considerations
- Minimize metadata in DIDComm headers and EDV indexes.
- Consider group encryption via JWE general serialization to avoid separate per-recipient blobs. (IETF Datatracker)
- Workflow field indexing: When vault documents store workflow context fields, avoid indexing field names or values that could leak the workflow's purpose or the subject's identity. Use opaque identifiers (e.g.,
doc_idbased on random values, not field names) and limit encrypted indexes to structural keys likeworkflow_idandstage. Do not indexsecret-level field names or values. - Role-grouped documents: Grouping sensitive workflow fields into EDV documents by role-access pattern (rather than one document per field) reduces the number of EDV queries observable by the vault host, minimizing metadata leakage about field-level access patterns.
Interop Notes
-
Discover Features 2.0 SHOULD advertise: supported backends (
s3,arweave, …), max object size, index support, replication availability, and whetherthreshold-decryptis supported. (didcomm.org) -
Composition with Signing 1.0:
- Use Signing 1.0 threshold sessions to collect approvals and carry partial-decrypt shares.
- Use Signing 1.0 sealed-secret profile (HPKE envelopes bound to tokens) for key delivery.
-
Composition with Workflow 1.0:
- Workflow templates declare a
sensitivitymap that classifies context fields. Fields withstorage: "vault"are stored in a Vaults 1.0 EDV and referenced via$refpointers in workflow messages. - The Coordinator provisions the vault before sending Workflow
start. Thepropose.purposefield uses the conventionworkflow:<template_id>#<instance_id>. - Vault lifecycle is bound to workflow lifecycle:
completetriggersseal,canceltriggerstombstone. - Agents that support both protocols SHOULD advertise
vault-contextin their Discover Features 2.0 capabilities.
- Workflow templates declare a
Worked Example
Create & grant
// Alice → Host
{ "type":"https://didcomm.org/vaults/1.0/propose", "body": { "purpose":"workflow:pdf-signing#42", "participants":["did:ex:bob"], "constraints":{"ttl_seconds":86400}, "backend_prefs":["s3"], "index_schema":["workflow_id","digest","stage"] } }
// Host → Alice
{ "type":"https://didcomm.org/vaults/1.0/offer", "body": { "vault_descriptor": { "...": "VaultDescriptor" } } }
// Alice → Bob
{ "type":"https://didcomm.org/vaults/1.0/grant-access", "body": { "to":"did:ex:bob", "capability": { "...": "zcap w/ path=/docs/pdf-42-*, verb=read|write, expires_at=..." } } }
Use with Signing 1.0
// Sign-request (separate protocol) carries a ContentRef
{ "type":"https://didcomm.org/signing/1.0/request", "body": { "content_ref": { "vault_id":"urn:edv:...:vault123","doc_id":"pdf-42-src","digest":"sha256-...","capability":{ "...": "read-cap" } }, "suite":"PAdES", "policy":{ "...": "digest pinning etc." } } }
Threshold Decrypt (CEK-wrap) — 2-of-3
// Object descriptor carries threshold_kem header with cipher_cek
// Requester starts a Signing 1.0 threshold session:
{ "type":"https://didcomm.org/signing/1.0/request-signing",
"body": { "session": { "session_id": "sess_dec1", "mode": {"type":"threshold"} },
"object": { "...": "includes threshold_kem" },
"suite": { "suite":"threshold-kem@1" },
"constraints": { "intended_audience": ["did:ex:alice#device"], "use_limit": 1, "expires_time": "..." } } }
// Two holders return partial-decrypt shares:
{ "type":"https://didcomm.org/signing/1.0/partial-signature",
"body":{ "session_id":"sess_dec1","object_id":"so_xyz","signer":"did:ex:holder1",
"suite":"threshold-kem@1","kind":"partial-decrypt","data":{"share":"...","aad_hash":"sha-256:..."}} }
// Coordinator combines shares → CEK, then delivers as sealed secret + token:
{ "type":"https://didcomm.org/signing/1.0/provide-artifacts",
"body": { "session_id":"sess_dec1",
"artifacts":[{"type":"sealed-secret@1","suite":"envelope-hpke@1","aad":{"ticket_digest":"sha-256:..."},"ciphertext":"...","enc":{"kem":"X25519","kdf":"HKDF-SHA256","aead":"AES-256-GCM","ek_pub":"..."}} ],
"token":{ "...": "device-bound, ctr, exp, cap=1" } } }
Implementation Hints
-
Use or adapt an existing EDV server (e.g., TrustBloc EDV) and plug different backends under it. (GitHub)
-
Client library should expose:
- EDV client (encrypt/JWE, HMAC indexes, sequence handling).
- ZCAP issuance/delegation helpers.
- DIDComm handlers for
propose,offer,grant-access,seal,tombstone,replicate. - Threshold holders: API to compute
partial_decrypt_sharegiven the KEM header + private share. - Aggregator:
threshold-decrypt@1to combine N shares, verify integrity (AEAD/CEK checksum), and emit sealed-secret + token.
-
For multi-party read, prefer JWE general serialization (one ciphertext, many recipients) where policy allows. For immutable archives (e.g., Arweave), keep keys off-chain and gate key release with this extension.
Security & Compliance Checklist
- DIDComm messages authcrypted, with correct
thid/pthid. (identity.foundation) - Capabilities scoped to verb + path + TTL + size; non-delegable when needed. (w3c-ccg.github.io)
- EDV sequence checked on updates; retry on conflict. (identity.foundation)
- Equality indexes only; avoid sensitive attribute leakage. (identity.foundation)
- Arweave "deletion" via key destruction documented with finalization record. (SwissBorg Academy)
- Keys never published on immutable backends; key release/partials only via DIDComm.
- Authorization token enforced (device, counter, expiry) for CEK/plaintext delivery.
-
aad_hashbinding validated for all partial-decrypt shares.
Change Log
- v1.0-draft — Initial publication
- v1.0-draft+wf — Added workflow binding conventions (
purposeformat), workflow-specific security/privacy guidance, and Workflow 1.0 composition notes.
References
EDV v0.1 spec; DIDComm v2; Discover Features 2.0; Message Pickup 3.0; JWE (RFC 7516); ZCAP-LD; DIDComm v2 announcement; Arweave permanence & crypto-erasure background; HPKE (RFC 9180). (identity.foundation)