| Internet-Draft | PQC Evidence Records | September 2026 |
| Alhemeiri | Expires 10 March 2027 | [Page] |
This document describes an evidence-record format for the long-term, verifiable preservation of digitally-signed data across the migration to post-quantum cryptography. It builds on the Evidence Record Syntax (ERS) of RFC 4998 and adds an explicit algorithm-agility extension: a record is a chain of signed attestations in which each link re-witnesses the data under a fresh signature primitive and commits to the prior link, so that the authenticity of the data survives the cryptographic break of any single primitive. It specifies the canonical hashing that makes a record reproducibly verifiable across independent implementations, the authenticated temporal binding that places each link in time (an append-only transparency log à la RFC 6962, whose signed inclusion receipt is not-after evidence), and the verification procedure. A per-link beacon anchor records not-before evidence: from wire version 3 it is authenticated against the beacon's hash chain, with the beacon's classical pulse signature as defence in depth; the Security Considerations say which assumption each check rests on.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 10 March 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
A signature proves authenticity now. Preserving that proof for decades raises two problems RFC 4998 (ERS) [RFC4998] already addresses — the signing key or algorithm may weaken, and the timestamp authority may change — and one it does not: the signature algorithm itself may be catastrophically broken by a quantum computer. NIST's FIPS 203/204/205 [FIPS203] [FIPS204] [FIPS205] (and draft 206) standardise post-quantum primitives, but a record signed today only with a classical or single post-quantum algorithm has no path forward if that algorithm falls.¶
This document profiles ERS for that setting. The core idea, following RFC 4998's Archive Timestamp chain, is to keep extending a record under fresh primitives before the old one is broken, with each extension cryptographically bound to its predecessor, so a verifier can establish an unbroken sequence in which every link was sound at the time it was made.¶
This document describes a format with a complete reference implementation, but the security argument has not undergone independent cryptographic review.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This profile preserves ERS's central abstraction — an ordered chain of timestamps over a data object, each renewing the evidence — and departs from it in three deliberate ways:¶
Primitive agility is first-class. An ERS ArchiveTimestamp renews evidence chiefly to outrun hash weakening; here each link also changes the signature primitive and records which primitive was in force, so the chain spans a primitive break, not only a hash-strength downgrade.¶
Two serialisations. The normative interchange form is CBOR [RFC8949]; an ASN.1 DER form aligned to [RFC4998] is provided for IETF conformance (Section 4.3). The chain hash is computed over a canonical JSON form (Section 4.2), independent of either, so cross-implementation byte-equality does not depend on CBOR/DER encoder quirks.¶
Explicit temporal binding. Each link's authenticated temporal evidence is an append-only transparency-log inclusion receipt (a signed tree head plus inclusion proof — not-after evidence), verifiable offline and enforced by the verifier when a witness-trust policy is supplied (Section 5). A per-link beacon anchor records not-before evidence: from wire version 3 it carries the beacon pulse record, the certificate it was signed under and, optionally, the successor pulse, and the verifier authenticates it against the beacon's hash chain with the beacon's classical pulse signature as defence in depth (Section 5, Section 8). Wire versions 1 and 2 carry the anchor unauthenticated; a verifier reports that distinctly and never upgrades it.¶
This document is a profile and extension within the ERS family, not a new
evidence format. [RFC4998] defines the Evidence Record Syntax whose Archive
Timestamp chain the agility extension above builds on. [RFC6283] (XMLERS) is
the XML serialisation of that same syntax; the two-serialisation approach here
— a CBOR interchange form beside a DER form (Section 4.3) — follows its
precedent of one evidence model carried in more than one encoding. [RFC5276]
specifies how evidence records are conveyed by the Server-Based Certificate
Validation Protocol (SCVP); this profile leaves that transport untouched, and a
record in the DER form of Section 4.3 is a candidate payload for it. The
EvidenceRecord type referenced in Section 4.3 is the ERS type whose
current ASN.1 module is [RFC9169], a restatement of the RFC 4998 and RFC 5276
modules in modern ASN.1 syntax with identical wire encoding; implementers
working from ASN.1 tooling take the type from that module. Outside the IETF,
BSI TR-03125 (TR-ESOR) [TR-03125] is a national technical guideline for
preservation services whose evidence handling is built on ERS; a record in this
profile is intended to be usable by such a service as an ERS evidence record
carrying an additional, explicitly labelled agility extension, rather than as a
competing format.¶
Content address. A hash-based identifier of the protected data: a SHA-3-256 Merkle root over fixed-size chunks together with the total byte length.¶
Chain entry / link. One renewal layer: a signature over the content address under a stated primitive, an anchor, the hash of the prior link, and an optional witness receipt.¶
Anchor. Per-link not-before evidence: beacon id, epoch, root, self-bound
to the link by witnessed_hash == HASH(signature.bytes), and, from wire
version 3, one or more beacon witnesses (the pulse record, its certificate,
and optionally the successor pulse) against which the verifier authenticates
it (Section 5). In wire versions 1 and 2 the anchor is unauthenticated
metadata and is reported as such (Section 8).¶
Beacon witness. One beacon's evidence for a link: the full pulse record
with the output hashes it commits to, the DER certificate named by the
pulse's certificateId, and optionally the next pulse of the same chain,
whose previous field commits forward to the anchored pulse.¶
Witness receipt. A signed transparency-log tree head plus an inclusion proof showing the link's signature was logged — the authenticated not-after temporal evidence, enforced when a witness-trust policy is supplied.¶
data ──► content address ──► chain entry v1
(primitive A, anchor, witness)
│ prior_chain_hash
▼
chain entry v2
(primitive B, anchor, witness)
│
▼ … extend before A breaks
¶
A record is { content_address, chain[] }. Verification (Section 5) walks
the chain from v1 outward, requiring each link's signature to verify over the
content address under its stated primitive, each non-initial link to commit to
its predecessor's chain hash, and (when a trust policy is supplied) each link to
carry a valid witness receipt from a trusted witness with monotonic time.¶
EvidenceRecord = {
content_address: { root: 32 octets, byte_length: uint },
chain: [ ChainEntry, ... ]
}
ChainEntry = {
version: uint, ; 1 = initial, >1 = extension
signature: {
algorithm: tstr, ; e.g. "SLH-DSA-SHAKE-256s", "ML-DSA-87"
algorithm_version: uint,
bytes: bstr,
public_key: bstr,
signed_at_unix: uint
},
anchor: {
beacon_id: tstr,
epoch: uint,
witnessed_at_unix: uint,
beacon_root: 32 octets,
witnessed_hash: 32 octets, ; = SHA3-256(signature.bytes)
? chain_index: uint, ; wire_v >= 2
? certificate_id: tstr, ; wire_v >= 2, see below
? witnesses: [+ BeaconWitness] ; wire_v == 3 only
},
prior_chain_hash: 32 octets / null, ; null iff version == 1
inclusion: WitnessReceipt / null,
? wire_v: uint, ; 1 (absent), 2, or 3
? not_before: NotBefore / null ; wire_v >= 2, see below
}
NotBefore = {
chain_index: uint,
pulse_index: uint,
output_value: 64 octets
}
BeaconWitness = {
source: tstr, ; e.g. "nist-beacon-v2"
pulse: Pulse, ; the full beacon v2 pulse record
certificate_der: bstr, ; the certificate the pulse names
successor: Pulse / null ; next pulse of the same chain
}
¶
certificate_id is the SHA-512 of the DER certificate the pulse names;
not_before is the pulse bound in the signed message
(Section 6); a Pulse is the beacon's v2 pulse record with its
hex fields in lower case; certificate_der lets the classical check be
repeated offline.¶
witnesses is REQUIRED on a wire version 3 link and MUST NOT appear on any
other: a decoder MUST reject a lower-version link that carries it (a legacy
anchor presented as authenticated) and a version 3 link without it. The
first witness is the pulse the signed message binds (Section 6);
any further witness is an independent beacon and is OPTIONAL.¶
The signature is computed over a domain-separated message (Section 6) binding the content address, the signing time, and the algorithm identifier, so a signature made for one protocol context cannot be replayed in another.¶
prior_chain_hash for link n+1 is SHA3-256 of the canonical JSON encoding
of link n. The canonical JSON form is RECOMMENDED for all cross-implementation
hashing and is defined as:¶
a JSON object with keys sorted lexicographically, no insignificant whitespace
("," and ":" separators);¶
octet-string fields encoded as lower-case hex with a _hex key suffix;¶
exactly these fields per link:¶
{
"anchor":{"beacon_id":<tstr>,"beacon_root_hex":<hex>,
"epoch":<uint>,"witnessed_at_unix":<uint>,
"witnessed_hash_hex":<hex>},
"prior_chain_hash_hex":<hex|null>,
"signature":{"algorithm":<tstr>,"algorithm_version":<uint>,
"bytes_hex":<hex>,"public_key_hex":<hex>,
"signed_at_unix":<uint>},
"version":<uint>
}
¶
A wire version 2 link adds, in the same sorted-key form, "wire_v":2,
"not_before" ({"chain_index","output_value_hex","pulse_index"} or null) and
"certificate_id"/"chain_index" inside "anchor". A wire version 3 link
adds those same keys with "wire_v":3 and, inside "anchor",
"witnesses_hex": the SHA3-256 of the deterministic CBOR ([RFC8949]
Section 4.2) of the array [[source, pulse-json, successor-json-or-null,
certificate_der], ...] where each pulse is its canonical JSON (sorted keys,
no whitespace, lower-case hex). The witness list is therefore committed by
the chain hash and cannot be exchanged after the link is chained.¶
A conforming implementation MUST produce byte-identical canonical JSON for the
same link; this is what lets independent Python, JavaScript, and Rust verifiers
compute the same chain hash. Note the witness receipt (inclusion) is not
part of the chain hash — it is supplementary evidence bound to the link by
witnessed_hash = SHA3-256(signature.bytes), so attaching or detaching it does
not alter the chain.¶
For IETF conformance an equivalent ASN.1 DER serialisation is defined. The evidence record is carried in a ContentInfo-style wrapper that identifies it by the Wathīqa enterprise OID (Section 7):¶
WathiqaEvidenceRecord ::= SEQUENCE {
contentType OBJECT IDENTIFIER, -- 1.3.6.1.4.1.66038.1.1
content EvidenceRecord } -- SEQUENCE { version,
-- contentAddress…, chain }
¶
The inner EvidenceRecord carries the post-quantum AlgorithmIdentifier OIDs of
Section 7 and a version INTEGER giving the DER profile (3 for
the wrapped form). The form is byte-deterministic (definite-length,
DER-canonical) and round-trips with the CBOR form. A decoder distinguishes the
wrapped form from a legacy bare EvidenceRecord by the first inner element's tag
(OBJECT IDENTIFIER vs INTEGER); a conformant decoder MUST reject a record whose
contentType is not the Wathīqa evidence OID, and MUST reject a bare record
claiming the wrapped profile version. The detailed ASN.1 module is tracked in the
implementation (asn1_der.py, authoritative; Rust der.rs in lock-step); a
future revision will inline it.¶
WitnessReceipt = {
witnessed_hash: 32 octets, ; = SHA3-256(link signature.bytes)
leaf_index: uint,
sth: SignedTreeHead,
inclusion_proof: [ 32 octets, ... ]
}
SignedTreeHead = { log_id, tree_size, root, timestamp_unix,
signature }
¶
The Merkle tree follows [RFC6962] hashing (leaf = SHA3-256(0x00 || data),
node = SHA3-256(0x01 || left || right)), with SHA-3-256 as the hash. A receipt
verifies offline: check the STH signature, then check the inclusion proof connects
witnessed_hash to the STH root.¶
A verifier is given the protected data D, the record, and OPTIONALLY a set of
trusted witness public keys W. It MUST:¶
Recompute the content address of D and check it equals record.content_address.¶
For each link, in chain order, with prev = the previous link (none for v1):
a. Verify signature over the content address under signature.algorithm and
the evidence signing context (Section 6). Reject on failure.
b. If version == 1: require prior_chain_hash == null. Else: require
prior_chain_hash == SHA3-256(canonicalJSON(prev)).
c. If W is supplied (temporal-binding enforcement): require inclusion to be
present and valid; require inclusion.witnessed_hash == SHA3-256(signature.bytes);
require inclusion.sth.signature to be by a key in W; and require STH
timestamps to be non-decreasing along the chain.
d. Beacon anchor. Require anchor.witnessed_hash == SHA3-256(signature.bytes).
If wire_v == 3, for the first witness require ALL of: the pulse is
well-formed and outputValue == SHA-512(serialization || signatureValue);
certificateId == SHA-512(certificate_der) and that certificate is pinned
by the verifier; the pulse signature verifies under it; anchor.epoch,
chain_index, certificate_id, beacon_root and witnessed_at_unix
all derive from the pulse; and the link's not_before names exactly this
pulse. If a successor is carried, require it to be the next index of the
same chain, to authenticate the same way, and its previous value to equal
the anchored outputValue. Any further witness must authenticate under its
own pinned certificates and lie within one beacon period of the anchor
time. Report the verdict: AUTHENTICATED, AUTHENTICATED_NO_SUCCESSOR,
CHAIN_CONFIRMED (an optional online re-fetch agreed), or INVALID. If
wire_v < 3, report LEGACY_UNAUTHENTICATED and perform no further anchor
check. An INVALID anchor MUST fail the link; a LEGACY_UNAUTHENTICATED
anchor MUST NOT be upgraded and fails the link only under a policy that
requires authenticated anchors.¶
Accept iff every link passed and the chain is contiguous from v1.¶
Acceptance means: there exists an unbroken sequence of attestations in which each primitive was applied while sound, the data was unchanged throughout, and (under a trust policy) each step was publicly logged by a trusted witness in monotonic time. Which primitives a relying party still trusts at verification time is the relying party's policy input, not a property of the record.¶
Every signature commits to a context tag so a signature for one protocol cannot be replayed in another. The base message layout is:¶
context-utf8 || 0x00 || algorithm-utf8 || 0x00 || root(32) ||
byte_length(8, big-endian) || signed_at_unix(8, big-endian) ||
extra
¶
extra is empty for every v1 context. Evidence-record links produced from
wire profile v0.3 onward sign under wathiqa.evidence.v2 with a fixed-width
extra tail:¶
signer_id(32)
-- SHA3-256 of the signer's public key
prior_flag(1) || prior(32)
-- 0x00 + zeros = genesis; 0x01 + hash = link
nb_flag(1) || nb_chain(8, BE) || nb_pulse(8, BE) || nb_output(64)
-- 0x00 + zeros = no not-before binding;
-- 0x01 = NIST Beacon 2.0 pulse claim
¶
A chain entry declares its rule on the wire via wire_v (absent = 1, the
legacy rule, which verifiers MUST support indefinitely; 2 = the rule above;
any other value MUST be rejected at decode). Mixed chains are legal: a v1
record extended under v0.3 gains v2 entries. The not-before pulse claim is a
signed CLAIM; its authentication is the (optional) pulse-signature
verification of Section 8. Other contexts are unchanged:
wathiqa.evidence.v1 (legacy record links), wathiqa.sth.v1
(transparency-log tree heads), wathiqa.witness_roster.v1 (witness-key
succession), and others per protocol.¶
Signature primitives are identified by the NIST CSOR OIDs for ML-DSA
([FIPS204]) and SLH-DSA ([FIPS205]); FN-DSA/Falcon awaits FIPS 206. The
Wathīqa-specific structures are identified under the IANA Private Enterprise
Number arc 1.3.6.1.4.1.66038 (PEN 66038, granted to Wathīqa). The FN-DSA
algorithm OID remains the one outstanding value, pending FIPS 206. An
implementation MUST treat the algorithm string and the OID as equivalent
identifiers of the same primitive and MUST reject a record whose algorithm it
does not recognise (rather than accepting it unverified).¶
Agility window. The scheme protects data only if each link is added before the prior primitive is broken; an operational renewal policy (out of scope for this document) is REQUIRED. A record left un-renewed past a primitive's break is no stronger than that primitive.¶
Temporal trust over decades. The witness keys in W must themselves survive
rotation; this profile recommends a quorum-authorised, append-only witness-key
succession pinned to a single long-lived anchor. Without it, W is
unmaintainable on a century scale.¶
Beacon anchor: what it proves, and under which assumption. A wire
version 3 link carries an authenticated beacon anchor (Section 4.1): the
full beacon pulse record with the five output hashes it commits to, the
certificate the pulse was signed under, and optionally the successor pulse.
The link's signed message binds the pulse's outputValue (Section 6), a
512-bit value that was unpredictable before the pulse was released, so the
signature is not-before the pulse. Two assumptions are involved and MUST
be kept apart. The beacon's own pulse signature is classical (RSA,
[NIST-BEACON]); verifying it under the pinned certificate is defence in
depth at sealing time and a repeatable offline check afterwards, but a
post-quantum adversary who breaks RSA can forge a pulse record with any
timestamp. The post-quantum claim therefore rests on the beacon's hash
chain: every pulse's outputValue is the SHA-512 of its signed serialization
and signature, every pulse commits to its predecessor's outputValue, and
the successor carried in the anchor commits forward to the anchored pulse.
What a verifier can establish from the record alone is that the link commits
to a pulse value that is internally consistent with a chain context; placing
that value in the chain the beacon actually published requires either the
classical signature or an independently obtained copy of the chain (a live
fetch, or an archive kept by the verifier). Implementations SHOULD keep such
a copy for records they expect to verify beyond the life of the beacon's
RSA keys, and MUST report the anchor verdict distinctly (Section 5): a
legacy unauthenticated anchor MUST be reported as such and MUST NOT be
upgraded to authenticated by any verification step.¶
Beacon certificate rotation. A pulse names its certificate by
certificateId; a verifier MUST pin certificates out of band and MUST fail
closed when the named certificate is not pinned or the signature does not
verify under it. Rotation at the beacon can be inconsistent in practice: on
2026-09-05 the reference implementation observed live pulses naming a newly
issued certificate while carrying signatures that verified under neither
the new nor the previous key. The sealing rule, verify the pulse before
binding it and refuse otherwise, exists for exactly that situation.¶
Fail-closed verification. A verifier MUST treat any malformed field, unrecognised algorithm, or unverifiable proof as verification failure, never as success.¶
Canonical-form attacks. Because the chain hash is over a canonical form, implementations MUST reject inputs that do not round-trip to that exact form (e.g. non-minimal numbers, duplicate keys), to deny an attacker a second pre-image via encoding ambiguity.¶
The Wathīqa structures of Section 4 are identified under IANA Private
Enterprise Number 66038 (1.3.6.1.4.1.66038), already granted — no further
OID registration is requested for them.¶
Still outstanding (a separate IANA process, not yet filed): registration of
the two media types application/wathiqa-evidence+cbor and
application/wathiqa-evidence+der.¶
A complete reference implementation (Python, authoritative for wire formats), plus JavaScript and Rust verifiers that reproduce the canonical chain hash byte-for-byte, exists and is cross-validated by a shared conformance-vector suite. This draft documents that implementation's formats; divergences are implementation bugs, not intended profile variation.¶
Authenticated beacon anchors (wire version 3, Section 4.1): the anchor carries the pulse record, its certificate and optionally the successor pulse; the verifier authenticates it (Section 5 step 2d) and reports a distinct verdict; legacy anchors are reported, never upgraded.¶
Security Considerations rewritten to state which assumption each anchor check rests on: the beacon's pulse signature is classical; the post-quantum not-before claim rests on the hash chain, and on an independently held copy of it for verification beyond the life of the beacon's keys.¶
Canonical chain hash extended for wire version 3 (Section 4.2).¶
This was the first public revision. The following changes were made in the pre-submission revisions that preceded it:¶
Wire profile v0.3: chain entries carry wire_v (version gate); the v2
signed message binds the signer identity, the prior chain hash, and an
optional NIST-pulse not-before claim (Section 6). v1 records remain
verifiable indefinitely.¶
Anchor structure gains chain_index and certificate_id (beacon chain and
pulse-certificate pinning for authenticated re-verification).¶
ASN.1 DER profile v4 (per-entry wire version, optional NotBefore SEQUENCE, extended anchor); v1–v3 remain decodable, only v4 is emitted.¶
The machine-checked model now proves the strengthened per-link property: acceptance implies an honest attestation of (document, algorithm, POSITION), or a prior primitive break.¶