Specification version: 0.1 (Working Draft) Publication date: 2026-07-01 Canonical URL (planned): https://spec.attestto.com/vcp Editors: Eduardo Chongkan (Attestto) License: Specification text is licensed under Creative Commons Attribution 4.0 International (CC-BY 4.0). Reference code, JSON Schemas, and examples are licensed under Apache License 2.0. Reference implementation: github.com/Attestto-com/vcp-cli (@attestto/vcp on npm)
This specification defines the Verifiable Content Provenance (VCP) container: a compact, embed-first metadata block that carries an issuer DID, a content-type declaration, a canonical content hash, an optional recipient DID, an optional profile identifier, and an EdDSA signature over the canonical JSON encoding of the block. The block is designed to travel inside host file formats (PDF, JPEG, ISOBMFF containers, and others) so that legacy readers ignore it and only VCP-aware tools verify or act on it.
VCP is deliberately narrow. It is not a rights-expression language, not a DRM system, and not a licensing framework. It answers a single question: for this file, who issued it, when, to whom, and has it been modified since?
VCP is the natural carrier for IEAL identity-evidence claims and for domain-specific profiles (legal-document, medical-record, ID-evidence, media). This v0.1 draft specifies only the base container. Profiles are separate documents.
This is a Working Draft (v0.1) published for public review, peer critique, and standards-body engagement. It is not a final specification. Breaking changes may occur before v1.0.
Feedback is welcomed through:
Relationship to other Attestto specifications:
ieal metadata fieldContent provenance is currently addressed by format-specific standards: PAdES for PDF, C2PA for images and video, XMP for photographic metadata, JOSE for JSON payloads. Each solves the provenance question inside one carrier. Cross-carrier tooling has to re-implement signing, verification, and metadata for every format, and profile-level concerns (what fields matter for a legal document versus a captured biometric versus a music track) get mixed into the base carrier spec.
VCP factors the container mechanics out from the profile concerns:
The result is that a legal document, a photograph of an ID, an audio recording, and a plain-text file can all carry the same shape of envelope with the same verification path, and that adding a new host format is a carrier-only change.
The terms MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.
metadata field of the envelope; the input to the canonicalization and signing algorithm.A VCP envelope is a JSON object with exactly two top-level fields: metadata and signature.
{
"metadata": { ... },
"signature": {
"alg": "EdDSA",
"value": "<base64url signature>"
}
}
The metadata block MUST include the following fields:
| Field | Type | Description |
|---|---|---|
spec |
string (URI) | MUST equal https://spec.attestto.com/vcp/. |
spec_version |
string | MUST equal 0.1 for envelopes conforming to this document. |
issuer |
string (DID) | The DID of the entity that signed this envelope. |
content_type |
string | The IANA Media Type of the host file (e.g. application/pdf, image/jpeg). |
content_hash |
string | The hexadecimal SHA-256 hash of the host file, computed per the hash_scope rule. |
hash_scope |
string | Either host-file-minus-vcp-block (embed carriers) or host-file-unmodified (sidecar carrier). |
timestamp |
string | RFC 3339 timestamp of signature creation. |
The metadata block MAY include the following fields:
| Field | Type | Description |
|---|---|---|
subject |
string (DID) | The DID of the intended recipient or subject of the content. |
profile |
string (URI) | Identifier of a profile that layers additional required fields on this envelope. |
ieal |
object | An IEAL v0.1 claim describing the assurance level of the identity evidence contained. |
Implementations MUST reject envelopes containing unknown top-level metadata fields at the same time they reject unknown spec_version values. This forces additive changes into a new spec_version and prevents ambiguity.
The signature block MUST include:
| Field | Type | Description |
|---|---|---|
alg |
string | Signature algorithm identifier. For v0.1 the only permitted value is EdDSA. |
value |
string | The base64url-encoded raw signature output. |
The signature is computed over the UTF-8 bytes of the canonical JSON serialization (JCS RFC 8785) of the metadata object. Verification recomputes the canonicalization and calls the signature algorithm’s verify primitive with the issuer’s resolved public key.
Post-quantum hybrid signature suites (e.g. ML-DSA + Ed25519) are out of scope for v0.1 and will be added in a future version with an expanded alg registry.
Canonicalization has two layers.
The metadata block is serialized using JCS (RFC 8785) prior to signing. The signature is computed over the UTF-8 bytes of the JCS output.
The content_hash field commits to a canonical representation of the host file. Two hash_scope values are defined:
host-file-minus-vcp-block — the host file bytes with the VCP-owned embed region removed per the carrier’s rules. Used by all embed carriers.host-file-unmodified — the host file bytes as-is. Used only by the sidecar carrier where the host file is not modified.Each carrier specification MUST define precisely which byte range is treated as “the VCP-owned embed region” and how to remove it. The removal MUST be deterministic — two implementations MUST arrive at the same removed representation given the same input.
This section specifies three carriers. Additional carriers are the subject of follow-on specifications.
<host-file>.vcp.json next to the host file, sharing filename stem.hash_scope: MUST be host-file-unmodified.%PDF- magic prefix.%%EOF, delimited by the fixed strings \n%%vcp:begin\n and \n%%vcp:end\n. The envelope is base64url-encoded (using the URL-safe alphabet without padding) between the delimiters.hash_scope: MUST be host-file-minus-vcp-block.\n%%vcp:begin\n prefix and ending after the \n%%vcp:end\n suffix removed. All other bytes, including the original %%EOF and any legacy trailer content, MUST be preserved verbatim.%%EOF. Trailing bytes are ignored. This carrier is compatible with every conforming PDF reader that we have tested.pdf-pades carrier that integrates with the PDF /Sig field and /ByteRange mechanism is planned for a future revision.0xFF 0xD8).0xFF 0xE1) whose payload begins with the XMP namespace identifier http://ns.adobe.com/xap/1.0/\x00 followed by an XMP packet containing a vcp:envelope element in the namespace https://spec.attestto.com/vcp/v1.vcp:envelope element.hash_scope: MUST be host-file-minus-vcp-block.vcp:envelope element removed. Non-VCP APP1 segments (JFIF, EXIF, non-VCP XMP) MUST be preserved.The issuer and subject fields carry DIDs. Verifiers MUST support at least the did:key method for interoperability testing. Additional methods MAY be supported.
The did:key method encodes an Ed25519 public key directly in the identifier. No external resolution is required. This is the minimum bar for VCP interoperability testing and is REQUIRED to be supported by all verifiers.
Verifiers MAY support other DID methods including did:pki, did:sns, did:web, did:webvh, did:jwk. Resolution of these methods requires either a bundled resolver or the DIF Universal Resolver HTTP interface. If resolution fails, the verifier MUST report a resolution failure distinctly from a signature verification failure.
A profile is a supplementary specification identified by URI in the profile metadata field. Profiles layer domain-specific required and optional fields on top of the base envelope. Profiles MUST NOT contradict the base specification.
Example profiles under discussion (each will be published as a separate document):
https://spec.attestto.com/vcp/profiles/id-evidence/v1 — required IEAL claim, subject DID, and biometric-metadata pointer.https://spec.attestto.com/vcp/profiles/legal-doc/v1 — required jurisdiction, counterparty DIDs, and signed-by-signatures block.https://spec.attestto.com/vcp/profiles/medical-record/v1 — required consent scope and retention hint.An envelope MAY omit the profile field, in which case it is a base envelope and any additional metadata fields are informational.
A conforming signer:
metadata object contains all required fields in §2.1.A conforming verifier:
spec_version is not recognized.hash_scope and reject on mismatch.did:key method (§5.1).VCP defends against:
content_hash binds the sidecar to a specific host file).VCP does NOT defend against:
VCP v0.1 supports exactly one signature algorithm (EdDSA). This is intentional for the working-draft phase. A future revision will introduce an algorithm registry with hybrid post-quantum options. Envelopes signed under v0.1 will be verifiable indefinitely under the algorithm they were signed with; the registry addition will not invalidate existing signatures.
VCP v0.1 does not encrypt the host file. The envelope is a plaintext signed metadata block. Confidentiality of the content is out of scope for v0.1. A future revision will add a standalone .vcp envelope carrier with an encrypted payload for use cases where the content itself must be access-controlled.
VCP envelopes reveal:
Applications SHOULD NOT include personally identifiable information in the metadata block. Selective disclosure of subject attributes is a profile concern and SHOULD be handled by pointing to an SD-JWT or verifiable credential elsewhere rather than by embedding attribute values in the envelope.
Pairwise DIDs ([[pairwise-identifiers pattern from W3C VC-DATA-MODEL 2.0 §8.10]]) SHOULD be used for subject when the issuer-subject relationship should not be correlatable across contexts.
@attestto/vcp CLI — Node.js reference implementation of this specification. Implements all three v0.1 carriers, did:key verifier, and did:pki signer (verifier requires supplied public key pending resolver publication).