Status: Public Beta v0.2 · ProofSpec v0.1 · API v0.2 · Privacy-first · No blockchain

Protocol — ProofSpec

Open, normative specification to prove when data existed and that it has not changed. Hash locally, receive a proof file, verify later — without revealing your data.

↑ Top

Overview

ProofSpec defines how to create, sign, and verify digital proofs of existence in a privacy-first way.

Model: hash locally → receive a signed .tproof.json proof file → verify later (offline or via stateless verification) without access to the original data.

This document is normative for issuers and verifiers that claim to be ProofSpec v0.1 compliant.

ProofSpec vs TimeProofs

File date ≠ proof

What it proves / What it does NOT prove

It proves:

It does NOT prove:

Normative language & scope

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Conformance

The following roles are defined:

A ProofSpec-compliant Issuer:

A ProofSpec-compliant Verifier:

Clients that claim ProofSpec compliance SHOULD keep the full .tproof.json proof for long-term verification.

Canonicalization

Data model

FieldTypeNotes
versionstringProtocol / implementation version. REQUIRED.
canonicalstringDeterministic message string signed by issuer. REQUIRED.
hashobject{ algorithm:"SHA-256", value:"<64-hex>" }. REQUIRED.
timestampobject{ issuedAt, issuer, nonce }. REQUIRED.
proofobject{ algo, signature, keyId, publicKey? }. REQUIRED.
metaobjectOptional. MUST NOT contain personal data or secrets.

ProofSpec v0.1 is designed for portable proofs (.tproof.json) and stateless verification.

Proof object (example)

{
  "version":"timeproofs-0.2",
  "canonical":"<hash>|<issuedAt>|<issuer>|<nonce>",
  "hash": { "algorithm":"SHA-256", "value":"<64-hex>" },
  "timestamp": {
    "issuedAt":"2025-01-01T00:00:00.000Z",
    "issuer":"https://api.timeproofs.io",
    "nonce":"<hex>"
  },
  "proof": {
    "algo":"Ed25519",
    "signature":"<hex>",
    "publicKey":"<base64 SPKI DER>",
    "keyId":"tp-v0-2-main"
  },
  "meta": { "type":"event" }
}

The signature algorithm and encoding are issuer-specific. The TimeProofs reference implementation uses a deterministic, verifiable signature scheme and includes a keyId for key lifecycle.

Signing

Issuers MUST sign a deterministic canonical message. The reference canonical string is:

<hash>|<issuedAt>|<issuer>|<nonce>
// Browser / Worker signature verify (example, WebCrypto)
// Note: algorithm support depends on platform. Use a verifier that matches the issuer's algo.
function b64ToBytes(b64){
  const bin = atob(b64);
  const out = new Uint8Array(bin.length);
  for (let i=0;i<out.length;i++) out[i]=bin.charCodeAt(i);
  return out;
}
function hexToBytes(hex){
  const h=String(hex||"").toLowerCase().trim();
  if(!/^[a-f0-9]+$/.test(h) || h.length%2) return null;
  const out=new Uint8Array(h.length/2);
  for(let i=0;i<out.length;i++) out[i]=parseInt(h.slice(i*2,i*2+2),16);
  return out;
}

Issuers SHOULD implement key lifecycle via keyId and publish public keys for independent verification.

Some deployments enforce key freeze: verifiers trust only a pinned issuer public key and ignore the embedded publicKey.

Validation

JSON schema

{
  "$schema":"https://json-schema.org/draft/2020-12/schema",
  "type":"object",
  "required":["version","canonical","hash","timestamp","proof"],
  "properties":{
    "version":{"type":"string"},
    "canonical":{"type":"string","minLength":10},
    "hash":{
      "type":"object",
      "required":["algorithm","value"],
      "properties":{
        "algorithm":{"type":"string","enum":["SHA-256"]},
        "value":{"type":"string","pattern":"^[a-f0-9]{64}$"}
      },
      "additionalProperties":true
    },
    "timestamp":{
      "type":"object",
      "required":["issuedAt","issuer","nonce"],
      "properties":{
        "issuedAt":{"type":"string","format":"date-time"},
        "issuer":{"type":"string","minLength":3},
        "nonce":{"type":"string","minLength":8}
      },
      "additionalProperties":true
    },
    "proof":{
      "type":"object",
      "required":["algo","signature","keyId"],
      "properties":{
        "algo":{"type":"string"},
        "signature":{"type":"string"},
        "publicKey":{"type":"string"},
        "keyId":{"type":"string"}
      },
      "additionalProperties":true
    },
    "meta":{"type":"object","additionalProperties":true}
  },
  "additionalProperties":true
}

Test vectors

// sha256("TimeProofs")
7c040f8633b8823d72ed63da9b3b2dfe9846e912c66a64c9433ec9c4815d76c2

Signature vectors depend on issuer keys. Validate real proofs using a verifier that trusts the issuer’s public key(s).

Verification

In stateless mode, verification requires the full .tproof.json proof file (a hash alone is not sufficient).

POST /verify
Content-Type: application/json

{ "bundle": <.tproof.json> }

→ {"ok":true,"valid":true,...}

Verifiers SHOULD validate structure, recompute/compare the canonical message, and verify the signature.

.tproof.json bundle

This portable proof is designed for offline inspection and long-term storage. It MUST NOT contain original content, secrets, or personal data.

{
  "version":"timeproofs-0.2",
  "canonical":"7c04...76c2|2025-01-01T00:00:00.000Z|https://api.timeproofs.io|a1b2c3...",
  "hash": { "algorithm":"SHA-256", "value":"7c04...76c2" },
  "timestamp": {
    "issuedAt":"2025-01-01T00:00:00.000Z",
    "issuer":"https://api.timeproofs.io",
    "nonce":"a1b2c3..."
  },
  "proof": {
    "algo":"Ed25519",
    "signature":"<hex>",
    "publicKey":"<base64 SPKI DER>",
    "keyId":"tp-v0-2-main"
  },
  "meta": { "type":"document" }
}

Mapping

ProofSpec uses a self-describing proof format. The main correspondences are:

ConceptFieldNotes
Hashhash.valueLowercase SHA-256 64-hex.
Issuance timetimestamp.issuedAtISO-8601 UTC.
Issuer idtimestamp.issuerStable issuer identifier (typically HTTPS origin).
Anti-replaytimestamp.nonceRandom, unpredictable.
Signed messagecanonicalSigned by the issuer.
Signatureproof.signatureEncoding depends on issuer implementation.
Key idproof.keyIdIdentifies signing key.

Reserved fields

Names starting with _tp_ are reserved for the specification and MUST NOT be used for custom metadata.

Errors

{"ok":false,"error":"invalid_hash"}
// Examples:
// {"ok":false,"error":"missing_bundle"}
// {"ok":false,"error":"missing_signature"}
// {"ok":false,"error":"invalid_signature"}
// {"ok":false,"error":"bad_request"}

Operational policy

Integration details and reproducible snippets are documented in API Docs.

Checklist

Versioning

This document describes ProofSpec v0.1 (Public Beta site v0.2). Additive changes SHOULD prefer minor/patch bumps; breaking changes MUST bump the major version.

Issuers and verifiers SHOULD clearly expose which ProofSpec version(s) they support.