Skip to content

Evidence packs

Public verification surfaces deliberately withhold the values a fingerprint commits to. That is correct for privacy and incomplete for verification: a Class V verifier reading a public certificate record can recompute the disclosure_hash, but not the certificate fingerprint itself, because business_id and subject_hash are not published.

An evidence pack closes that gap. It is a single JSON document containing exactly the committed facts, handed to a party who is already entitled to see them — the subject, the recipient, an auditor under engagement, or opposing counsel under discovery — and verifiable entirely offline.

Why the public record cannot simply publish more

Section titled “Why the public record cannot simply publish more”

The obvious “fix” is to publish subject_hash alongside the fingerprint. SP/1 forbids this.

subject_hash is SHA-256 over the canonical JSON of the submitted fields, and those fields are low-entropy: a name, an email, a phone number. Publishing the hash creates a confirmation oracle — anyone who can guess a person’s exact field values can confirm them against the published digest, turning a privacy surface into a lookup. Hubs MUST NOT publish subject_hash, and MUST NOT publish any digest computed over subject values alone.

Verification of the full fingerprint is therefore an entitled operation, not a public one. The evidence pack is how that entitlement is exercised.

A pack is a JSON object. pack_version is "sp1-evidence/1".

{
"pack_version": "sp1-evidence/1",
"kind": "consent_certificate",
"issued_by": "https://api.pryvc.com",
"exported_at": "2026-07-30T12:00:00.000Z",
"record": {
"cert_id": "cc_...",
"business_id": "biz_...",
"business_name": "Example Co",
"entities": ["Example Co", "Example Partners LLC"],
"purpose": "Discuss insurance options",
"origin_domain": "example.com",
"page_url": "https://example.com/quote",
"timestamp": "2026-01-15T12:00:00.000Z",
"disclosure_text": "I agree to be contacted by ...",
"subject_fields": { "email": "jane@example.com", "first_name": "Jane" }
},
"derived": {
"disclosure_hash": "375bcf...",
"subject_hash": "a7e5df...",
"fingerprint": "93aa61..."
},
"policies": [
{
"kind": "privacy",
"url": "https://example.com/privacy",
"captured_at": "2026-01-15T12:00:02.000Z",
"content_hash": "",
"content_text": ""
}
],
"anchor": {
"ledger": "engrave",
"tx_id": 4,
"idx": 0,
"value_hash": "sha256:…",
"root": "sha256:…",
"tree_size": 21,
"anchored_at": "2026-01-15T12:15:00.000Z"
},
"chain": {
"expected_first_prev_hash": "",
"rows": [{ "prev_hash": "", "event": {}, "row_hash": "" }]
}
}
  • record MUST contain every field the relevant fingerprint composition commits to, under the same key names the fingerprint spec uses. A pack that omits a committed fact is malformed, not merely incomplete.
  • derived MUST be present. A verifier MUST recompute every value in it and MUST NOT trust the supplied values — they exist so a mismatch localizes the discrepancy, not so verification can be skipped.
  • policies[].content_text MUST be the exact captured text whose SHA-256 is content_hash.
  • chain is OPTIONAL. When present, rows MUST be contiguous and ordered, and expected_first_prev_hash MUST be either the genesis value or a row hash the verifier can independently corroborate — typically one covered by anchor.
  • anchor is OPTIONAL but SHOULD be present. Inclusion proofs MUST be verified against the ledger’s signed tree head with a pinned key, never by asking the ledger to attest to itself (see anchoring).
  • Packs MUST NOT contain hub credentials, API keys, or session material.

A conforming Class V verifier, given only the pack:

  1. Recompute disclosure_hash from record.disclosure_text; compare to derived.disclosure_hash.
  2. Recompute subject_hash as SHA256(canonical_json(record.subject_fields)); compare to derived.subject_hash.
  3. Rebuild the fingerprint composition from recordsorting entities, and substituting the two recomputed hashes — then SHA256(canonical_json(composition)) and compare to derived.fingerprint.
  4. For each policy, recompute SHA256(content_text) and compare to content_hash.
  5. If chain is present, verify continuity and every row_hash.
  6. If anchor is present, verify the inclusion proof against a pinned tree head.

Every step is local. No network access is required at any point, which is the property that makes a pack usable years later, in a proceeding, after the issuing hub has changed hands or ceased to exist.

Hubs MUST make packs available to the subject of a record and to the recipient that holds it, authenticated. Hubs MAY offer auditor-scoped export. Hubs MUST NOT expose pack export on an unauthenticated surface, for the confirmation-oracle reason above.

A pack is a disclosure of personal data to whoever receives it. Hubs SHOULD record pack export as an audit event.

The conformance suite ships a pack verifier:

Terminal window
python -m sp1verify pack evidence-pack.json

It performs every step above and reports which facts were checked and which were absent — a pack that verifies its fingerprint but carries no anchor is a different evidentiary object from one that carries both, and a verifier should say so rather than print a single green tick.