Summary
A modular, transport-agnostic protocol for requesting, producing, coordinating, and verifying digital signatures over arbitrary payloads (documents, transactions, packages, attestations) with single-signer and multi-party modes, plus replay-proof sealed secret delivery using HPKE envelopes and authorization tokens.
Summary
The Signing Protocol provides a unified DIDComm-based approach to digital signature orchestration across heterogeneous media types including PDF documents, blockchain transactions, software packages, and cryptographic attestations. It supports single-signer workflows, threshold signatures (N-of-M), cryptographic aggregation (MuSig2, FROST, BLS), and contract-based multisig (e.g., Gnosis Safe, ERC-1271).
Beyond traditional signatures, the protocol includes sealed secret delivery using HPKE envelopes with replay-proof authorization tokens, enabling secure use cases like database password unlock, API key rotation, and ephemeral credential delivery with device binding and monotonic counter protection.
Goals
- Unified: One protocol to drive signing across heterogeneous media (PDF, CMS, JWT, EVM, PSBT, OCI, etc.) and sealed secret delivery
- Modular: Pluggable canonicalizers, signature suites, aggregators, envelopes, and policies
- Multisig: Support signature collection (N independent sigs), cryptographic aggregation (e.g., MuSig2/FROST/BLS), and contract/account-based multisig (e.g., Gnosis Safe, ERC-1271)
- Auditable: Strong consent mechanisms, digest pinning (WYSIWYS - What You See Is What You Sign), idempotency, receipts, and authorization tokens
- Replay-Proof: Monotonic counter-based replay protection for authorization tokens and sealed secrets
- Transport-agnostic: Works seamlessly over DIDComm v2 message routing and pickup
Non-Goals
This protocol does not define the cryptographic primitives themselves (e.g., ECDSA, EdDSA algorithms), nor does it replace existing signature standards. Instead, it orchestrates signature workflows using those standards as pluggable modules.
Motivation
Digital signature workflows today are fragmented across domains:
- Documents: Adobe PDF signatures, CAdES/PAdES standards
- Blockchain: Ethereum transaction signing, Bitcoin PSBT coordination, Safe multisig
- Supply Chain: Sigstore/cosign for container images
- Identity: Verifiable Credentials and DID authentication
Each domain uses proprietary APIs and coordination mechanisms. This protocol provides a universal coordination layer that:
- Abstracts signature mechanics behind pluggable modules (canonicalizers, suites, aggregators)
- Provides multisig orchestration for threshold and aggregated signatures
- Enforces security invariants like digest pinning and consent flows
- Enables auditability through receipts and idempotency
- Works offline-first via DIDComm message routing and pickup
Roles
requester: Party initiating a signing request. May be a document management system, blockchain application, or any service requiring signatures.signer: Party controlling a private key or smart account. The signer provides consent and produces signatures.coordinator: Orchestrates multi-signer sessions, collecting partial signatures and triggering aggregation. May be the same party as the requester or a separate orchestration service.observer: Read-only recipient for audit trails or UX mirroring. Observers receive session updates but do not participate in signing.
Role Cardinality
- A session MUST have exactly one
requester - A session MUST have at least one
signer - For multisig sessions (
mode.type=threshold), there MUST be acoordinator(may be same as requester) - A session MAY have zero or more
observerroles
Terminology
- Canonicalizer: Module that converts an input to canonical bytes and provides a digest method
- Signature Suite: Module that produces/verifies signatures for a class of payloads/keys
- Aggregator: Module that combines partial signatures and/or executes on-chain submissions to produce a final artifact
- Envelope: Module that seals secrets using KEM+KDF+AEAD (e.g., HPKE) with cryptographic binding to authorization tokens
- Policy: Module that enforces business/authorization constraints
- Session: A scoped interaction for signing a specific object (or set of objects)
- WYSIWYS: "What You See Is What You Sign" - the principle that signers must view content derived from the same canonical bytes they are signing
- Authorization Token: Server-signed ticket with replay protection (monotonic counter), device binding, and expiration
- Device Binding: Cryptographic binding of tokens/envelopes to a specific device key or DID
Normative keywords (MUST, SHOULD, MAY) are to be interpreted as in RFC 2119.
States
Single-Signer State Machine
[idle] → propose-signing (optional) → request-signing → consent (optional) →
partial-signature → provide-artifacts → ack → [completed]
↓
decline/problem-report → [failed]
States:
idle: No active signing sessionproposed: Capabilities exchanged, session parameters negotiatedrequested: Signing request received by signerconsented: Signer has approved and provided key binding proofsigned: Signature producedartifacts-provided: Final signed artifacts deliveredcompleted: Session acknowledged and finalizedfailed: Session declined or encountered errors
Events (Messages):
propose-signing: Capability discovery and negotiationrequest-signing: Initiate signing with object descriptor, suite, and constraintsconsent: Human approval with key binding proofpartial-signature: Submit signature (or signature fragment)provide-artifacts: Deliver final signed artifactsack: Receipt acknowledgmentdecline: Reject signing requestproblem-report: Error reporting
Threshold State Machine (N-of-M Collection)
[idle] → propose-signing (optional) → request-signing →
[consent x M (optional)] → [partial-signature x M] →
combine (threshold_met) → provide-artifacts → ack → [completed]
Additional States:
collecting: Gathering partial signatures from M signersthreshold-met: N of M required signatures collectedaggregating: Coordinator combining signatures
Additional Events:
combine: Announce aggregation status or trigger on-chain submission
Cryptographic Aggregation (FROST/MuSig2)
Similar to threshold state, but with multi-round partial-signature messages carrying commitments, nonces, and signature shares across multiple rounds until combine can aggregate to a single signature.
Rounds:
- Round 1: Commitment/nonce exchange
- Round 2+: Signature share computation
- Final: Aggregation to single signature
Contract/Account-based Multisig (e.g., Safe)
request-signing (includes tx descriptor/hash) →
[confirmations as partial-signature x N] →
combine (submit/execution) → provide-artifacts (txid/receipt) → ack
Smart Contract States:
pending: Transaction proposed but not confirmedconfirmed: Sufficient confirmations collectedexecuted: Transaction submitted to blockchainfinalized: Transaction included in block
Data Model
Signable Object Descriptor
A SignableObject describes what is being signed:
{
"id": "so_7c9b...",
"media_type": "application/pdf",
"canonicalization": {
"method": "pdf-incremental-update@1",
"parameters": {
"byte_range": [0, 123, 456, 789]
}
},
"digest": {
"alg": "sha-256",
"value": "mVxT...=="
},
"display_hints": {
"title": "NDA v3.1",
"preview_links": ["https://.../nda.pdf"],
"summary": "Non-Disclosure Agreement"
}
}
Required Fields:
id: Unique identifier for this signable objectmedia_type: MIME type of the payloadcanonicalization: Method and parameters for canonical byte representationdigest: Cryptographic digest over canonical bytes
Optional Fields:
display_hints: Human-readable metadata for consent UX
Invariants:
digestMUST be computed over canonical bytes as specified bycanonicalization- Signers MUST verify digest matches their local canonicalization before signing
Session
A Session describes the coordination mode and participants:
{
"session_id": "sess_b5f1...",
"mode": {
"type": "single"
}
}
For threshold/multisig:
{
"session_id": "sess_b5f1...",
"mode": {
"type": "threshold"
},
"threshold": {
"scheme": "n-of-m",
"n": 2,
"m": 3,
"signers": ["did:ex:s1", "did:ex:s2", "did:ex:s3"],
"aggregation": "none"
}
}
Mode Types:
single: One signer produces one signaturethreshold: N-of-M signature collection or aggregation
Threshold Schemes:
n-of-m: Collect N signatures from M designated signerscryptographic: MuSig2, FROST, BLS aggregationcontract: On-chain multisig (Safe, multi-owner accounts)
Suite & Constraints
The Suite specifies the signature algorithm and constraints:
{
"suite": "evm-eip712@1",
"key_binding": {
"controller": "eip155:1:0xAbC...",
"proof_purpose": "assertionMethod"
},
"constraints": {
"not_before": "2025-09-20T16:00:00Z",
"expires_time": "2025-10-20T16:00:00Z",
"intended_audience": ["did:example:acme"],
"use_limit": 1,
"policy_uri": "https://.../policies/pci"
}
}
Required Fields:
suite: Signature suite identifier from registry (§7.2)key_binding.controller: DID or blockchain address of signing key
Optional Constraints:
not_before: Signature not valid before this timeexpires_time: Signature expires after this timeintended_audience: DIDs for which signature is intendeduse_limit: Maximum number of times signature can be usedpolicy_uri: Reference to external policy document
Idempotency & Receipts
idempotency_keySHOULD be present inrequest-signingto prevent duplicate processingackSHOULD be a compact JWS over{received, session_id, receipt_jti, step_hash}for non-repudiation
Authorization Token
An Authorization Token is a server-signed ticket providing replay-proof authorization for sealed secrets, signing operations, or other constrained actions. It includes device binding and monotonic counter semantics.
{
"token": {
"typ": "signing-ticket",
"session_id": "sess_b5f1...",
"scope": "unlock",
"device": "did:example:device123",
"ctr": 42,
"exp": "2025-10-19T16:00:00Z",
"aud": ["did:example:agentA"],
"policy_hash": "sha-256:GW...",
"nonce": "b64...",
"cap": 1
},
"sig": {
"suite": "jws-ed25519@1",
"kid": "did:ex:coordinator#k1",
"value": "eyJ...sig..."
}
}
Required Fields:
typ: Token type (e.g.,signing-ticket)session_id: Session identifierscope: Authorization scope (unlock,sign,submit)device: Device DID or key identifierctr: Monotonic counter (per device)exp: Expiration timestamp (ISO 8601)cap: Capability/use limit (default 1)
Optional Fields:
aud: Intended audience DIDspolicy_hash: Cryptographic commitment to applied policynonce: Client-provided or server-generated nonce
Signature:
sig.suite: Signature suite identifiersig.kid: Key identifier (DID URL)sig.value: Signature over canonical serialization oftoken
Normative Rules:
- The tuple
(device, ctr)MUST be strictly monotonically increasing on both server and client expMUST be enforced by both parties; expired tokens MUST be rejectedtokenMUST be integrity-protected bysig(JWS or equivalent)- Clients MUST persist
last_seen_ctr(device)before consuming the token - Tokens with
ctr ≤ last_seen_ctrMUST be rejected (replay protection)
Message Reference
All messages are DIDComm v2 JWM (JSON Web Messages) envelopes. Threading uses thid (thread ID) and pthid (parent thread ID). Large payloads use DIDComm attachments with data.links + hash for integrity verification.
propose-signing
Purpose: Capability discovery and negotiation before initiating a signing session.
Sender: requester
Receiver: signer or coordinator
Message Type URI:
https://didcomm.org/signing/1.0/propose-signing
Body:
{
"session": {
"session_id": "sess_...",
"mode": {
"type": "threshold"
},
"threshold": {
"scheme": "n-of-m",
"n": 2,
"m": 3
}
},
"capabilities": {
"supported_suites": ["evm-eip712@1", "pades-b-lta@1", "psbt@2"],
"max_attachment_size": 10485760
}
}
Validation:
session.session_idMUST be unique and high-entropycapabilities.supported_suitesSHOULD list suite identifiers from registry (§7.2)
request-signing
Purpose: Initiate signing of an object with specified suite and constraints.
Sender: requester
Receiver: signer
Message Type URI:
https://didcomm.org/signing/1.0/request-signing
Body:
{
"session": {
"session_id": "sess_...",
"mode": {"type": "single"}
},
"object": {
"id": "so_...",
"media_type": "application/pdf",
"canonicalization": {"method": "pdf-incremental-update@1", "parameters": {...}},
"digest": {"alg": "sha-256", "value": "..."}
},
"suite": {
"suite": "pades-b-lta@1",
"key_binding": {"controller": "did:ex:signer#key1"}
},
"idempotency_key": "txn_9f27...",
"ui": {
"must_display_summary": true
}
}
Attachments:
- MAY carry full payloads (e.g., EIP-712 typed data, PSBT, PDF bytes) via
data.jsonordata.links+hash
Validation:
session,object,suiteare REQUIREDobject.digestMUST match canonicalized bytesidempotency_keySHOULD be unique per request
consent
Purpose: Human approval and key binding proof before signing.
Sender: signer
Receiver: requester or coordinator
Message Type URI:
https://didcomm.org/signing/1.0/consent
Body:
{
"session_id": "sess_...",
"object_id": "so_...",
"key_binding_proof": {
"type": "jws",
"kid": "did:ex:signer#k1",
"payload": "eyJpbnRlbnQiOiJzaWduIn0",
"signature": "..."
}
}
Validation:
key_binding_proofMUST be verifiable withsuite.key_binding.controller- Payload SHOULD contain intent statement (e.g., "I consent to sign object so_...")
partial-signature
Purpose: Submit a signature fragment, confirmation, or round data.
Sender: signer
Receiver: requester or coordinator
Message Type URI:
https://didcomm.org/signing/1.0/partial-signature
Body (Single-round):
{
"session_id": "sess_...",
"object_id": "so_...",
"signer": "did:ex:s2",
"suite": "evm-eip712@1",
"signature": {
"format": "eip-712",
"value": "0x...",
"public_key_hint": "eip155:1:0xABC..."
}
}
Body (Multi-round, e.g., MuSig2/FROST):
{
"session_id": "sess_...",
"object_id": "so_...",
"signer": "did:ex:s2",
"suite": "schnorr-musig2@1",
"round": 1,
"data": {
"nonceCommitment": "..."
}
}
Validation:
- For single-round:
signatureMUST be present and verifiable - For multi-round:
roundanddataspecify current round state signerMUST be insession.threshold.signers(if applicable)
combine
Purpose: Announce aggregation status, threshold satisfaction, or on-chain submission.
Sender: coordinator
Receiver: requester, signer(s), observer(s)
Message Type URI:
https://didcomm.org/signing/1.0/combine
Body:
{
"session_id": "sess_...",
"status": "threshold_met",
"aggregation_result": {
"type": "none",
"artifact_link": "https://...",
"digest": "sha-256:..."
}
}
For on-chain execution:
{
"session_id": "sess_...",
"status": "executed",
"aggregation_result": {
"type": "contract",
"txid": "0x...",
"chain_id": "eip155:1"
}
}
Aggregation Types:
none: Simple collection (bundle of N signatures)math: Cryptographic aggregation (MuSig2/FROST/BLS)contract: Smart contract multisig execution
provide-artifacts
Purpose: Deliver final signed artifacts (signed PDF, CMS, transaction receipts, finalized PSBT, OCI signatures) or sealed secrets with authorization tokens.
Sender: coordinator or signer (single-signer mode)
Receiver: requester
Message Type URI:
https://didcomm.org/signing/1.0/provide-artifacts
Body (Standard Artifacts):
{
"session_id": "sess_...",
"artifacts": [
{
"type": "pades-b-lta",
"digest": "sha-256:mVxT...",
"links": ["https://.../nda-signed.pdf"]
}
]
}
Body (Sealed Secret with Authorization Token):
{
"session_id": "sess_...",
"artifacts": [
{
"type": "sealed-secret@1",
"suite": "envelope-hpke@1",
"aad": {
"ticket_digest": "sha-256:..."
},
"ciphertext": "b64...",
"enc": {
"kem": "X25519",
"kdf": "HKDF-SHA256",
"aead": "AES-256-GCM",
"ek_pub": "b64..."
}
}
],
"token": {
"token": {
"typ": "signing-ticket",
"session_id": "sess_...",
"scope": "unlock",
"device": "did:example:device123",
"ctr": 42,
"exp": "2025-10-19T16:00:00Z",
"cap": 1
},
"sig": {
"suite": "jws-ed25519@1",
"kid": "did:ex:coordinator#k1",
"value": "eyJ...sig..."
}
}
}
Sealed Secret Fields:
type: MUST besealed-secret@1for envelope artifactssuite: Envelope suite identifier (e.g.,envelope-hpke@1)aad: Additional authenticated data MUST includeticket_digestciphertext: Base64-encoded encrypted payloadenc: Encryption metadata (KEM, KDF, AEAD, ephemeral public key)
Token Field:
token: Authorization token structure (see Data Model §Authorization Token)- MUST be present when
typeissealed-secret@1
Attachments:
- Artifacts MAY be attached directly or referenced via
links
Validation:
- Each artifact MUST include
type,digest, andlinksor inline data digestSHOULD be verified by requester- For
sealed-secret@1:aad.ticket_digestMUST matchsha-256(serialize(token.token)) - Receiver MUST verify
token.sigbefore attempting decryption - Receiver MUST enforce counter monotonicity (
ctr > last_seen_ctr)
issue-token
Purpose: Issue an authorization token with sealed secret (optional message type; can use provide-artifacts instead).
Sender: coordinator
Receiver: signer or requester
Message Type URI:
https://didcomm.org/signing/1.0/issue-token
Body:
Same structure as provide-artifacts with sealed secret. This message type exists to semantically separate authorization token issuance from artifact delivery in workflows where that distinction is important.
Usage Note:
- Use
issue-tokenwhen the primary purpose is delivering authorization credentials - Use
provide-artifactswithtype: sealed-secret@1when sealed secrets are one of multiple artifact types - Both are functionally equivalent for sealed secret delivery
ack
Purpose: Receipt and non-repudiation acknowledgment.
Sender: requester
Receiver: signer, coordinator
Message Type URI:
https://didcomm.org/signing/1.0/ack
Body:
{
"received": "provide-artifacts",
"session_id": "sess_...",
"receipt_jti": "rcpt_22a8..."
}
Receipt:
- MAY be a compact JWS over
{received, session_id, receipt_jti, step_hash} - Provides non-repudiation proof of delivery
decline
Purpose: Reject a signing request.
Sender: signer
Receiver: requester
Message Type URI:
https://didcomm.org/signing/1.0/decline
Body:
{
"session_id": "sess_...",
"reason": "User declined to sign"
}
problem-report
Purpose: Error reporting per DIDComm conventions.
Sender: Any role Receiver: Any role
Message Type URI:
https://didcomm.org/signing/1.0/problem-report
Body:
{
"code": "unsupported-suite",
"explain": "Signer does not support PAdES LTA."
}
Error Codes: See Design By Contract section.
Pluggable Modules
The protocol defines registries for canonicalizers, signature suites, aggregators, and policies. Implementations MUST reject unknown registry keys unless operating in permissive mode.
Canonicalizers
Canonicalizers convert inputs to canonical bytes and compute digests.
| Registry Key | Description | Parameters |
|---|---|---|
raw-bytes@1 |
Identity canonicalization; signs raw bytes | None |
pdf-incremental-update@1 |
PAdES byte ranges for incremental updates | byte_range: array of integers |
cms-detached@1 |
Detached CMS for CAdES | Digest context |
eip-712@1 |
EIP-712 typed data canonicalization | domain, types, message |
psbt@2 |
PSBT (BIP-174/370) as base64 bytes | None |
oci-manifest@1 |
OCI manifest for Sigstore/cosign | None |
Signature Suites
Signature suites produce and verify signatures.
| Registry Key | Description | Output Format |
|---|---|---|
jws-ed25519@1 |
Detached JWS with Ed25519 | JWS compact |
jws-secp256k1@1 |
Detached JWS with secp256k1 | JWS compact |
pades-b-b@1 |
PDF Advanced Electronic Signature (basic) | CMS blob |
pades-b-lta@1 |
PAdES with Long-Term Archival (timestamp/OCSP) | CMS blob + evidence |
cades-bes@1 |
CMS/CAdES Basic Electronic Signature | CMS blob |
evm-eth_sign@1 |
EVM eth_sign (deprecated) | 65-byte hex |
evm-personal_sign@1 |
EVM personal_sign | 65-byte hex |
evm-eip712@1 |
EVM EIP-712 structured data signing | 65-byte hex |
evm-safe-tx@1 |
Gnosis Safe transaction confirmation | Confirmation data |
erc-1271-check@1 |
ERC-1271 contract signature verification | Boolean verification |
psbt@2 |
PSBT input signing | Updated PSBT fragment |
schnorr-musig2@1 |
MuSig2 aggregated Schnorr (multi-round) | Aggregated signature |
frost-ed25519@1 |
FROST threshold EdDSA (multi-round) | Aggregated signature |
bls-aggregate@1 |
BLS signature aggregation | Aggregated signature |
sigstore-fulcio@1 |
Fulcio/OIDC key material + Rekor witness | Transparency log entry |
Aggregators
Aggregators combine partial signatures or execute on-chain submissions.
| Registry Key | Description | Output |
|---|---|---|
bundle-none@1 |
Bundle N detached signatures (co-sign) | Array of signatures |
pdf-co-sign@1 |
Merge incremental updates into final PAdES PDF | Signed PDF |
safe-exec@1 |
Verify Safe confirmations and execute | Transaction ID |
psbt-finalize@2 |
Finalize PSBT to hex transaction; optionally broadcast | Transaction hex + txid |
math-aggregate@1 |
Combine MuSig2/FROST/BLS partials to one signature | Single signature |
oci-multi-sign@1 |
Attach multiple signatures to OCI manifest | OCI signature bundle |
Policies
Policies enforce business/authorization constraints.
| Registry Key | Description |
|---|---|
time-window@1 |
Enforce not_before and expires_time |
audience@1 |
Enforce intended_audience |
usage-limit@1 |
Enforce use_limit |
vp-gate@1 |
Require Verifiable Presentation proving role/entitlement |
spend-limit@1 |
EVM field checks: token, amount, chainId |
Envelopes
Envelopes seal secrets using Key Encapsulation Mechanisms (KEM) + Key Derivation Functions (KDF) + Authenticated Encryption with Associated Data (AEAD).
| Registry Key | Description | Cryptographic Primitives |
|---|---|---|
envelope-hpke@1 |
RFC 9180 HPKE Base/Auth mode | KEM: X25519 (MUST), KDF: HKDF-SHA256 (MUST), AEAD: AES-256-GCM or ChaCha20-Poly1305 (MUST support ≥1) |
envelope-didcomm-auth@1 |
DIDComm v2 authenticated encryption as envelope | Uses DIDComm JWM authcrypt with AAD binding |
envelope-hpke@1 Specification:
- Modes: Base mode (MUST), Auth mode (MAY)
- Inputs:
- Recipient public key (X25519)
- Plaintext (secret to seal)
- AAD (additional authenticated data)
- AAD Requirements (MUST include):
ticket_digest:sha-256(serialize(token))session_id: Session identifierdevice: Device identifier (optional but recommended)
- Outputs:
ciphertext: Encrypted payloadek_pub: Encapsulated key (ephemeral public key)
- Metadata: Include
encobject with{kem, kdf, aead, ek_pub}for interoperability
envelope-didcomm-auth@1 Specification:
- Uses DIDComm v2 authenticated encryption (JWM authcrypt)
- AAD MUST include the same fields as
envelope-hpke@1 - Suitable for implementations that already have DIDComm crypto stack
Discovery:
Agents advertise supported envelopes in capabilities.supported_envelopes during propose-signing.
Security Requirements:
- Envelope AAD MUST cryptographically bind to the authorization token digest
- Implementations MUST reject envelopes where
aad.ticket_digest ≠ sha-256(serialize(token)) - Recipient MUST verify token signature before attempting decryption
- Plaintext secrets MUST be zeroized immediately after use
Basic Walkthrough
Scenario: Single-Signer PDF Signing
Participants:
- Requester: Document management system (
did:ex:dms) - Signer: Alice (
did:ex:alice)
Flow:
- Request
Requester sends request-signing to Alice:
{
"type": "https://didcomm.org/signing/1.0/request-signing",
"id": "msg1",
"from": "did:ex:dms",
"to": ["did:ex:alice"],
"thid": "thread1",
"body": {
"session": {
"session_id": "sess_abc123",
"mode": {"type": "single"}
},
"object": {
"id": "so_nda_v3",
"media_type": "application/pdf",
"canonicalization": {
"method": "pdf-incremental-update@1",
"parameters": {"byte_range": [0, 1234, 5678, 9012]}
},
"digest": {
"alg": "sha-256",
"value": "mVxT3jQ...=="
},
"display_hints": {
"title": "NDA v3.1",
"preview_links": ["https://dms.example/nda.pdf"]
}
},
"suite": {
"suite": "pades-b-lta@1",
"key_binding": {
"controller": "did:ex:alice#key1"
}
},
"idempotency_key": "txn_nda_001"
}
}
- Consent (Optional but Recommended)
Alice reviews the PDF preview, verifies the digest, and sends consent:
{
"type": "https://didcomm.org/signing/1.0/consent",
"id": "msg2",
"from": "did:ex:alice",
"to": ["did:ex:dms"],
"thid": "thread1",
"body": {
"session_id": "sess_abc123",
"object_id": "so_nda_v3",
"key_binding_proof": {
"type": "jws",
"kid": "did:ex:alice#key1",
"payload": "eyJpbnRlbnQiOiJzaWduIiwib2JqZWN0X2lkIjoic29fbmRhX3YzIn0",
"signature": "..."
}
}
}
- Sign
Alice's agent produces the PAdES-B-LTA signature and sends partial-signature:
{
"type": "https://didcomm.org/signing/1.0/partial-signature",
"id": "msg3",
"from": "did:ex:alice",
"to": ["did:ex:dms"],
"thid": "thread1",
"body": {
"session_id": "sess_abc123",
"object_id": "so_nda_v3",
"signer": "did:ex:alice",
"suite": "pades-b-lta@1",
"signature": {
"format": "pades-b-lta",
"value": "<base64-encoded CMS blob>",
"timestamp_token": "<RFC3161 timestamp>",
"ocsp_response": "<OCSP stapling>"
}
}
}
- Provide Artifacts
Requester (or Alice's agent) sends the final signed PDF:
{
"type": "https://didcomm.org/signing/1.0/provide-artifacts",
"id": "msg4",
"from": "did:ex:dms",
"to": ["did:ex:alice"],
"thid": "thread1",
"body": {
"session_id": "sess_abc123",
"artifacts": [
{
"type": "pades-b-lta",
"digest": "sha-256:9fX2...",
"links": ["https://dms.example/nda-signed.pdf"]
}
]
}
}
- Acknowledge
Alice's agent acknowledges receipt:
{
"type": "https://didcomm.org/signing/1.0/ack",
"id": "msg5",
"from": "did:ex:alice",
"to": ["did:ex:dms"],
"thid": "thread1",
"body": {
"received": "provide-artifacts",
"session_id": "sess_abc123",
"receipt_jti": "rcpt_xyz789"
}
}
Advanced Walkthroughs
Scenario 1: EIP-712 Single-Signer
Use Case: Web3 app requests user to sign an EIP-712 order
Key Differences:
object.media_type:application/eip-712canonicalization.method:eip-712@1suite:evm-eip712@1- Attachment
data.jsoncarries{types, domain, message} partial-signature.signature.value: 65-byte hex0x...
Scenario 2: PDF PAdES 2-of-3 Threshold
Participants:
- Requester: HR system
- Signers: Alice, Bob, Carol (need 2 of 3)
- Coordinator: HR system
Flow:
- Requester sends
request-signingto Alice, Bob, Carol withmode.type: "threshold"andthreshold: {scheme: "n-of-m", n: 2, m: 3, signers: [...], aggregation: "pdf-co-sign@1"} - Alice and Bob each send
partial-signaturewith their CMS blobs - Coordinator sends
combinewithstatus: "threshold_met"andaggregation_result.type: "pdf" - Coordinator sends
provide-artifactswith final co-signed PDF - All parties send
ack
Scenario 3: Gnosis Safe 2-of-3 Multisig
Use Case: Execute a Safe transaction requiring 2 confirmations
Key Differences:
suite:evm-safe-tx@1objectincludessafeTxHashand transaction detailspartial-signaturerepresents Safe confirmationscombineexecutes the transaction viasafe-exec@1aggregatorprovide-artifacts.artifacts[0].type:safe-executionwithtxid
Scenario 4: Bitcoin PSBT Multi-Party
Use Case: Coordinate a 2-of-3 multisig Bitcoin transaction
Key Differences:
object.media_type:application/psbtcanonicalization.method:psbt@2suite:psbt@2- Each
partial-signaturecarries an updated PSBT fragment combinerunspsbt-finalize@2aggregatorprovide-artifactsincludes hex transaction + txid
Scenario 5: Database Password Unlock (Sealed Secret)
Use Case: Secure, replay-proof delivery of database password to authorized device
Participants:
- Client Device: Database application (
did:ex:db-client, device counterctr=41) - Coordinator: Credential vault service (
did:ex:vault)
Flow:
- Request Authorization
Client sends request-signing requesting database password:
{
"type": "https://didcomm.org/signing/1.0/request-signing",
"from": "did:ex:db-client",
"to": ["did:ex:vault"],
"thid": "thread-unlock-1",
"body": {
"session": {
"session_id": "sess_unlock_db_prod",
"mode": {"type": "single"}
},
"object": {
"id": "so_db_prod_pw",
"media_type": "application/octet-stream",
"canonicalization": {"method": "raw-bytes@1"},
"digest": {"alg": "sha-256", "value": "placeholder"}
},
"suite": {
"suite": "jws-ed25519@1",
"key_binding": {
"controller": "did:ex:db-client#device-key-1"
}
},
"idempotency_key": "unlock_20251019_001"
}
}
- Policy Check & Token Creation
Coordinator:
- Validates client authorization (e.g., via policy or VP)
- Increments device counter:
ctr = 42(was 41) - Creates authorization token:
{
"token": {
"typ": "signing-ticket",
"session_id": "sess_unlock_db_prod",
"scope": "unlock",
"device": "did:ex:db-client#device-key-1",
"ctr": 42,
"exp": "2025-10-19T16:02:00Z",
"cap": 1
}
}
- Signs token with coordinator key
- Computes
ticket_digest = sha-256(serialize(token))
- Envelope Creation
Coordinator encrypts database password using HPKE:
# Pseudocode
AAD = {
"ticket_digest": "sha-256:9f2a...",
"session_id": "sess_unlock_db_prod",
"device": "did:ex:db-client#device-key-1"
}
recipient_pk = resolve_public_key("did:ex:db-client#device-key-1")
plaintext = "MySecureDBPassword123!"
(ciphertext, ek_pub) = HPKE.Seal(recipient_pk, plaintext, serialize(AAD))
- Deliver Sealed Secret
Coordinator sends provide-artifacts:
{
"type": "https://didcomm.org/signing/1.0/provide-artifacts",
"from": "did:ex:vault",
"to": ["did:ex:db-client"],
"thid": "thread-unlock-1",
"body": {
"session_id": "sess_unlock_db_prod",
"artifacts": [
{
"type": "sealed-secret@1",
"suite": "envelope-hpke@1",
"aad": {
"ticket_digest": "sha-256:9f2a..."
},
"ciphertext": "YjY0LWVuY29kZWQtY2lwaGVydGV4dA==",
"enc": {
"kem": "X25519",
"kdf": "HKDF-SHA256",
"aead": "AES-256-GCM",
"ek_pub": "ZXBoZW1lcmFsLXB1YmxpYy1rZXk="
}
}
],
"token": {
"token": {
"typ": "signing-ticket",
"session_id": "sess_unlock_db_prod",
"scope": "unlock",
"device": "did:ex:db-client#device-key-1",
"ctr": 42,
"exp": "2025-10-19T16:02:00Z",
"cap": 1
},
"sig": {
"suite": "jws-ed25519@1",
"kid": "did:ex:vault#signing-key",
"value": "c2lnbmF0dXJlLWRhdGE="
}
}
}
}
- Client Consumption
Client receives artifact and:
a. Verify token signature:
verify_jws(token.sig, coordinator_public_key) # MUST pass
b. Check expiration:
assert token.exp > now() # MUST be true
c. Check counter (replay protection):
last_seen_ctr = load_from_persistent_storage("did:ex:db-client#device-key-1")
# last_seen_ctr = 41
assert token.ctr > last_seen_ctr # 42 > 41 ✓
d. Verify envelope binding:
computed_digest = sha256(serialize(token.token))
assert artifacts[0].aad.ticket_digest == "sha-256:" + computed_digest # MUST match
e. Persist counter BEFORE decryption (critical!):
save_to_persistent_storage("did:ex:db-client#device-key-1", ctr=42)
f. Decrypt secret:
AAD = {
"ticket_digest": artifacts[0].aad.ticket_digest,
"session_id": token.session_id,
"device": token.device
}
device_sk = load_private_key("did:ex:db-client#device-key-1")
password = HPKE.Open(device_sk, artifacts[0].ciphertext, serialize(AAD), artifacts[0].enc.ek_pub)
# password = "MySecureDBPassword123!"
g. Use and zeroize:
db_connection = connect_to_database(username, password)
zeroize(password) # Clear from memory
h. Acknowledge receipt:
{
"type": "https://didcomm.org/signing/1.0/ack",
"from": "did:ex:db-client",
"to": ["did:ex:vault"],
"thid": "thread-unlock-1",
"body": {
"received": "provide-artifacts",
"session_id": "sess_unlock_db_prod",
"receipt_jti": "rcpt_unlock_42"
}
}
Security Analysis:
- Replay Attempt: If attacker intercepts and replays message, client rejects because
ctr=42 ≤ last_seen_ctr=42 - Envelope Transplant: If attacker tries to use envelope with different token, AAD verification fails
- Token Forgery: Cannot forge token without coordinator's private key
- Device Binding: Only device with correct HPKE private key can decrypt
- Short-Lived: Token expires in 2 minutes, limiting exposure window
- Single-Use: Counter +
cap:1enforce one-time use
Key Differences from Standard Signing:
- No traditional signature produced; authorization token is the "signature"
- Envelope (HPKE) used instead of signature suite
- Counter-based replay protection instead of time-only
- Secret delivered encrypted, not in plaintext artifacts
Profiles
Profile: PDF / PAdES
- Media Type:
application/pdf - Canonicalization:
pdf-incremental-update@1withbyte_rangeparameter - Suite:
pades-b-b@1(basic) orpades-b-lta@1(long-term archival with timestamps/OCSP) - Artifacts: Final PDF link + SHA-256 digest; optionally RFC3161 timestamp and OCSP/CRL evidence
Profile: EVM — EIP-712
- Media Type:
application/eip-712 - Canonicalization:
eip-712@1 - Suite:
evm-eip712@1 - Attachment:
data.jsonMUST carry{types, domain, message}per EIP-712 spec - Signature Output: 65-byte hex
0x...(r, s, v)
Profile: EVM — Safe Multisig
- Suite:
evm-safe-tx@1 - Object: Describes
safeTxDataand/orsafeTxHash - Partial Signatures: Safe owner confirmations
- Combine: MAY execute transaction and return
txidinaggregation_result
Profile: Bitcoin — PSBT
- Media Type:
application/psbt - Canonicalization:
psbt@2 - Suite:
psbt@2 - Partial Signatures: Updated PSBT fragments
- Combine:
psbt-finalize@2aggregator produces finalized hex transaction - Artifacts: Transaction hex + txid (if broadcast)
Profile: Supply Chain — OCI (Sigstore)
- Media Type:
application/vnd.oci.image.manifest.v1+json - Canonicalization:
oci-manifest@1 - Suite:
sigstore-fulcio@1 - Combine:
oci-multi-sign@1aggregator - Artifacts: Signature references (e.g., Rekor transparency log entries)
Profile: Sealed Secret (DB Unlock & Credential Delivery)
This profile enables replay-proof, device-bound delivery of secrets such as database passwords, API keys, or ephemeral credentials.
- Media Type:
application/octet-stream(or custom, e.g.,application/x-db-credential) - Canonicalization:
raw-bytes@1(identity) - Suite: Any signature suite for token signing (e.g.,
jws-ed25519@1) - Envelope:
envelope-hpke@1(MUST) orenvelope-didcomm-auth@1(MAY) - Mode:
single(one recipient device)
Flow:
-
Request: Client sends
request-signingwith:object.canonicalization.method:raw-bytes@1object.digest: Digest over placeholder or metadata (not the secret itself)suite.key_binding.controller: Client device DID/key- Optional:
idempotency_key, clientnonce
-
Policy Check: Coordinator validates authorization, enforces policies
-
Token Issuance: Coordinator:
- Increments device counter:
ctr = last_ctr(device) + 1 - Builds authorization token with
{session_id, scope: "unlock", device, ctr, exp, cap: 1} - Signs token:
sig = sign(serialize(token), coordinator_key)
- Increments device counter:
-
Envelope Creation: Coordinator:
- Computes
ticket_digest = sha-256(serialize(token)) - Builds AAD:
{ticket_digest, session_id, device} - Encrypts secret
Pusingenvelope-hpke@1:(ciphertext, ek_pub) = HPKE.Seal(recipient_pk, P, AAD)
- Computes
-
Delivery: Coordinator sends
provide-artifacts(orissue-token) with:artifacts[0].type:sealed-secret@1artifacts[0].suite:envelope-hpke@1artifacts[0].aad:{ticket_digest}artifacts[0].ciphertext: encrypted secretartifacts[0].enc:{kem, kdf, aead, ek_pub}token: signed authorization token
-
Consumption: Client:
- Verifies
token.sigagainst coordinator's public key - Checks
token.exp > now(not expired) - Checks
token.ctr > last_seen_ctr(device)(replay protection) - Verifies
aad.ticket_digest == sha-256(serialize(token))(binding) - Persists
last_seen_ctr(device) = token.ctr(MUST happen before decryption) - Decrypts:
P = HPKE.Open(recipient_sk, ciphertext, AAD, ek_pub) - Uses secret
P(e.g., unlocks database) - Zeroizes
Pfrom memory - Sends
ack
- Verifies
Security Properties:
- Replay Protection: Counter monotonicity enforced client-side and server-side
- Device Binding: Envelope sealed to specific device public key
- Transplant Resistance: AAD cryptographically binds envelope to specific token
- Single-Use:
cap: 1+ counter persistence prevents reuse - Short-Lived: Typical
expof 30-120 seconds - Non-Exportable: Device keys SHOULD be stored in TPM/SE/Keychain
Offline Batch Variant:
Coordinator MAY pre-mint K tokens with sequential counters {ctr, ctr+1, ..., ctr+K-1} and corresponding envelopes. Client caches them offline and consumes sequentially, persisting last_seen_ctr after each use.
Use Cases:
- Database password unlock (one-time retrieval)
- API key rotation (ephemeral credentials)
- Secure command execution (privilege escalation tokens)
- Hardware unlock (device-bound secrets)
Design By Contract
Preconditions
- Requester MUST provide valid
session,object, andsuiteinrequest-signing object.digestMUST match canonicalized bytes percanonicalization.method- Signer MUST control the key specified in
suite.key_binding.controller
Postconditions
- Upon successful
ack, requester has non-repudiation receipt - Final artifacts MUST be cryptographically verifiable against
object.digest - For threshold sessions,
combineMUST verify N-of-M threshold satisfaction
Invariants
session_idMUST remain constant throughout session lifecycleobject_idMUST remain constant for a given signable object- Message threading (
thid) MUST be consistent across all session messages
Side Effects
- Signing operations MAY trigger on-chain state changes (e.g., Safe execution, PSBT broadcast)
- Artifacts MAY be stored in external systems (IPFS, cloud storage, transparency logs)
Timeout & Error Handling
| Error Code | Description | Recovery |
|---|---|---|
unsupported-suite |
Signer does not support requested signature suite | Requester retries with different suite |
unsupported-media-type |
Media type not recognized | Requester provides different media type |
unsupported-canonicalization |
Canonicalization method not supported | Requester selects compatible method |
policy-failed |
Policy module rejected request | Review and adjust constraints |
authz-insufficient |
Signer lacks authorization | Update permissions or provide VP |
expired |
Request timestamp exceeded expires_time |
Requester resends with updated time |
not-yet-valid |
Current time before not_before |
Wait until valid time window |
mismatch-digest |
Computed digest does not match object.digest |
Requester recomputes and resends |
idempotency-conflict |
Duplicate idempotency_key with different request |
Requester uses new idempotency key |
threshold-not-reached |
Insufficient partial signatures collected | Wait for more signers or timeout |
aggregation-failed |
Aggregator could not combine signatures | Retry or change aggregation strategy |
attachment-too-large |
Attachment exceeds max_attachment_size |
Use data.links instead of inline |
artifact-missing |
Expected artifact not found | Coordinator resends provide-artifacts |
replay-token |
Token already consumed (replay attempt detected) | Request new token with fresh counter |
counter-stale |
Token counter ≤ last seen counter for device | Coordinator re-syncs device counter state |
envelope-unsupported |
Requested envelope suite not supported | Negotiate supported envelope via propose-signing |
envelope-verify-failed |
AAD binding verification failed | Check token digest matches AAD |
device-mismatch |
Token device identifier doesn't match recipient | Verify correct device key in request |
Timeout Recommendations:
- Requester SHOULD set
expires_timeconstraint (e.g., 24-48 hours) - Coordinator SHOULD timeout threshold sessions after reasonable period (e.g., 1 hour for online sessions)
- Multi-round protocols SHOULD timeout individual rounds (e.g., 30 seconds per round)
Security
Threat Model
Threats Addressed:
-
WYSIWYS Mismatch: Attacker shows signer different content than what is signed
- Mitigation: Digest pinning; signer UI MUST render from canonical bytes
-
Signer Confusion: Signer signs unintended content or for wrong purpose
- Mitigation: Display hints with human-readable summary; consent flow with key binding proof
-
Replay Attacks: Signature reused in different context
- Mitigation: Time windows (
not_before,expires_time),use_limit,intended_audience
- Mitigation: Time windows (
-
Token Replay: Authorization token reused after consumption
- Mitigation: Monotonic counters (
ctr) enforced client-side and server-side; client persistslast_seen_ctrbefore use
- Mitigation: Monotonic counters (
-
Envelope Transplant: Attacker transplants sealed secret envelope to different token
- Mitigation: AAD cryptographically binds envelope to token digest; verification MUST check
aad.ticket_digest == sha-256(token)
- Mitigation: AAD cryptographically binds envelope to token digest; verification MUST check
-
Double-Sign: Signer produces multiple conflicting signatures (e.g., blockchain slashing)
- Mitigation: Idempotency keys; signer tracks previous signatures
-
Coordinator Tampering: Malicious coordinator modifies signatures or session state
- Mitigation: All signatures include
session_idandobject_id; signers verify final artifacts
- Mitigation: All signatures include
-
Downgrade Attacks: Attacker forces weak signature suite or envelope during negotiation
- Mitigation:
propose-signingcapabilities discovery; signer rejects unsupported suites/envelopes
- Mitigation:
Security Requirements
- Digest Pinning (MUST):
object.digestcomputed over canonical bytes; Signer UI MUST render preview derived from same canonicalization (or prominently display digest) - Consent (SHOULD): Send
consentwithkey_binding_proofprior to signing - Key Binding (MUST): Include DID/key identifier; verify key control during session
- Replay Protection (MUST): Enforce time windows and
use_limit - Idempotency (SHOULD): Use
idempotency_keyand constraints.use_limit
Additional Security Requirements (Sealed Secrets & Tokens)
- Counter Monotonicity (MUST): Clients MUST persist
last_seen_ctr(device)before consuming token or decrypting envelope; tokens withctr ≤ last_seen_ctrMUST be rejected - Envelope Binding (MUST): Envelope AAD MUST include
sha-256(serialize(token)); receivers MUST verify binding before decryption - Token Verification (MUST): Verify
token.sigagainst coordinator's public key before any other operations - Expiration Enforcement (MUST): Both server and client MUST enforce
token.exp; expired tokens MUST be rejected - Key Pinning (SHOULD): Clients SHOULD pin coordinator
kidor enforce DID Document resolution policy - Secret Zeroization (MUST): Plaintext secrets MUST be zeroized from memory immediately after use
- Non-Exportable Keys (SHOULD): Device HPKE private keys SHOULD be stored in hardware security modules (TPM, Secure Enclave, Keychain, DPAPI) and marked non-exportable
- Audit Logging (SHOULD): Coordinators SHOULD log
{session_id, device, ctr, exp, kid, status}for all token issuance and consumption events - Crash Hygiene (MUST): Implementations MUST prevent secrets from appearing in logs, environment variables, or core dumps
Privacy & Confidentiality
- Confidentiality: Use DIDComm authenticated encryption; avoid leaking object contents unless necessary
- Minimize Personal Data: Prefer references (
links+hash) over inline payloads - Large Artifacts: Use attachments with
data.linksto avoid inline mega-payloads - Observer Privacy: Observers see session state but not necessarily full payload content
Mediation & Transport
- DIDComm Pickup v2: Compatible with offline-first workflows and mediation
- Message Expiration: Consider setting
expires_timeheaders for pickup mediators - Routing: Works transparently over DIDComm routing agents
Composition
Supported Goal Codes
| Goal Code | Notes |
|---|---|
sign.document |
Sign a document (PDF, Word, etc.) |
sign.transaction |
Sign a blockchain transaction |
sign.credential |
Sign a Verifiable Credential |
sign.package |
Sign a software package or container image |
sign.attestation |
Sign a cryptographic attestation or receipt |
Co-Protocol Support
This protocol MAY be composed with:
- Issue Credential v2: Signing protocol can sign Verifiable Credentials as final step
- Present Proof v2: Signers MAY be required to present VPs proving authorization (
vp-gate@1policy) - Workflow: Signing can be orchestrated as steps within larger workflow
- Payments: Signing sessions MAY require payment (e.g., notary services)
Discoverability
Agents SHOULD advertise support using DIDComm feature discovery (similar to Aries RFC-0031):
{
"protocol": "https://didcomm.org/signing/1.0",
"roles": ["signer", "requester", "coordinator"],
"suites": [
"pades-b-lta@1",
"evm-eip712@1",
"evm-safe-tx@1",
"psbt@2"
],
"envelopes": [
"envelope-hpke@1",
"envelope-didcomm-auth@1"
],
"max_attachment_size": 10485760
}
Capability Fields:
protocol: Protocol URIroles: Supported roles (signer, requester, coordinator, observer)suites: Supported signature suitesenvelopes: Supported envelope schemes (optional; required for sealed secret support)max_attachment_size: Maximum attachment size in bytes
Discovery Methods:
- DIDComm
discover-featuresprotocol - DID Document service endpoints with protocol metadata
- Out-of-band invitations with protocol capabilities
Implementations
| Name / Link | Implementation Notes |
|---|---|
| To be published | Reference implementations pending |
Versioning & Compatibility
- Protocol Version: Specified in message type URI (
…/signing/1.0/…) - Module Versions: Registry keys use suffix
@Nto denote major version (e.g.,eip-712@1) - Backward Compatibility:
- Minor version bumps (1.0 → 1.1): Add optional features, maintain compatibility
- Major version bumps (1.0 → 2.0): Breaking changes to message structure or semantics
Conformance
An implementation is conformant if it:
- Produces and consumes message types in §Message Reference per schemas
- Implements at least one Canonicalizer (e.g.,
raw-bytes@1) and one Suite (e.g.,jws-secp256k1@1) - Enforces digest pinning: verifies
object.digestmatches canonicalized bytes - Enforces idempotency: rejects duplicate
idempotency_keywith different request - Supports
problem-reportwith error codes from §Design By Contract
Optional Conformance (Sealed Secrets & Authorization Tokens):
An implementation supporting sealed secrets is conformant if it additionally:
- Implements at least one Envelope (e.g.,
envelope-hpke@1) - Enforces counter monotonicity: persists
last_seen_ctr(device)before token consumption; rejectsctr ≤ last_seen_ctr - Enforces envelope binding: verifies
aad.ticket_digest == sha-256(serialize(token))before decryption - Verifies token signature before any other operations
- Enforces token expiration on both client and server
- Zeroizes plaintext secrets from memory after use
- Supports error codes:
replay-token,counter-stale,envelope-unsupported,envelope-verify-failed,device-mismatch
Other Optional Conformance:
- Multi-signer orchestration (coordinator role)
- Multi-round cryptographic aggregation
- On-chain execution (Safe, PSBT broadcast)
- Advanced policies (VP-gate, spend limits)
- Offline batch token issuance
Test Fixtures
Reference test vectors (to be published):
- PDF Fixture: Byte ranges + expected digest + two CMS co-signatures
- EIP-712 Order: Typed data + expected signature
- Safe Transaction: Hash + two confirmations + txid on testnet
- PSBT: 2-of-3 multisig with expected final txid
- Sealed Secret (HPKE): Authorization token + HPKE envelope + expected plaintext + counter sequence demonstrating replay rejection
Future Considerations
Potential enhancements for v1.1+:
- Transparency Log Witness: Optional transparency log entries for all steps/artifacts (e.g., Rekor integration)
- Observer Subscription Stream: Standardized pub/sub for observers
- Hash Agility Registry: Support for SHA-256, SHA-512, BLAKE3 in
digest.alg - JSON-LD Profile: Support for Linked Data Proof suites (e.g., Ed25519Signature2020)
- Batch Signing: Sign multiple objects in single session
- Revocation: Protocol extension for signature revocation/invalidation
Endnotes
- EIP-712: Ethereum Improvement Proposal for typed structured data signing
- PAdES: PDF Advanced Electronic Signatures (ETSI EN 319 142)
- CAdES: CMS Advanced Electronic Signatures (ETSI EN 319 122)
- PSBT: Partially Signed Bitcoin Transaction (BIP-174, BIP-370)
- MuSig2: Multi-signature Schnorr protocol with two communication rounds
- FROST: Flexible Round-Optimized Schnorr Threshold signatures
- Gnosis Safe: Multi-signature smart contract wallet
- ERC-1271: Standard signature validation method for contracts
- Sigstore/Fulcio: Keyless signing with OIDC identities and transparency logs
- OCI: Open Container Initiative image format