This document defines a JSON Web Signature (JWS) profile and abstract operations for representing, extending, validating, and merging a directed linear history of signed JSON entries. A valid history is anchored by a root public key and MAY rotate its verification key over time.

This document is an unofficial, AI-assisted draft. It is not a standards-track publication and MAY change, contain errors, or be withdrawn at any time. It is published for implementation experimentation and review feedback.

Introduction

A history consumer often needs to verify what an issuer intended at a particular time, even when snapshots are exchanged across distributed systems. This document specifies a JOSE-based format and deterministic operations that preserve a single verified linear chain anchored at a root public key.

This specification is format- and algorithm-focused. API shapes used by a specific reference implementation are non-normative, but failure conditions are normative.

Entry payloads are intentionally schema-flexible. State at a point in time is resolved by applying extension members from root to that point in chain order.

Use Cases

Conventions

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.

JSON is defined by [[!RFC8259]]. JWT and NumericDate are defined by [[!RFC7519]]. JWS and base64url handling in this specification follow [[!RFC7515]]. JWK and JOSE algorithm identifiers are defined by [[!RFC7517]] and [[!RFC7518]].

Core Terms

JSON Web History (JWH)
A linear, issuer-scoped chain of signed entries represented as a [=JWH snapshot=] or [=JWH string=].
JWH Entry
A JSON object payload that contains protocol metadata members and MAY contain arbitrary extension members.
JWH Entry Token
A JWS Compact Serialization whose payload is a [=JWH entry=].
JWH Snapshot
A JSON array of [=JWH entry token=] values ordered from root to head.
JWH String
The JSON string serialization of a [=JWH snapshot=].
Reserved Entry Members
The member names jti, iss, nbf, aft, pk, and rot. They are protocol metadata and are excluded from [=resolved state=] accumulation.
Not Before (nbf)
The nbf member interpreted as an issuer time assertion. It records the UNIX-time NumericDate that the issuer perceived when authoring the entry and expresses when the issuer intends consumers to apply that entry, based on each consumer's own perceived UNIX time. This value supports deterministic inspection and asynchronous verification workflows; it is not proof of absolute clock truth.
Root Pointer
The string literal U+0000, used as aft in exactly one root [=JWH entry=].
Root Verification Key
The public JWK carried in root entry member pk. It verifies the root token and initializes the [=active verification key=].
Active Verification Key
The public key used to verify the next [=JWH entry token=] in root-to-head order. If a verified entry contains rot, that key becomes active for subsequent entries.
Resolved State
The JSON object produced for a point in history by applying non-reserved members from root to that point in chain order, where later values override earlier values for the same key.

JWS Profile

A [=JWH entry token=] MUST be a JWS Compact Serialization as defined by [[!RFC7515]].

The JWS Protected Header MUST contain:

The alg value MUST NOT be none. The signing or verification key used for processing a token MUST be a JWK compatible with the declared algorithm [[!RFC7517]] [[!RFC7518]].

Entry Model

Core members of a [=JWH entry=]
Member Type Requirements
jti string MUST be non-empty and unique within one [=JSON Web History=] [[!RFC7519]].
iss string MUST be non-empty and equal across all entries in one [=JSON Web History=] [[!RFC7519]].
nbf number MUST be a NumericDate [[!RFC7519]] and represent [=not before=].
aft string MUST be either [=root pointer=] or the jti of exactly one earlier entry in the same history.
pk JSON object (JWK) MUST be present when aft equals [=root pointer=] and MUST be a public JWK compatible with the declared alg [[!RFC7517]] [[!RFC7518]].
rot JSON object (JWK) OPTIONAL. When present, MUST be a public JWK and becomes the [=active verification key=] for the next entry in chain order.
* (extension members) JSON value MAY contain arbitrary JSON members. This specification imposes no fixed payload schema across history entries.

Implementations MUST preserve unknown extension members when parsing, validating, inspecting, and merging histories.

History Validity

A candidate [=JSON Web History=] is valid only if all of the following are true:

  1. It contains at least one [=JWH entry=].
  2. Exactly one entry has aft equal to [=root pointer=].
  3. Every non-root entry's aft references an existing jti in the same history.
  4. No two entries share the same jti.
  5. No parent entry has more than one child entry (that is, no forks are permitted).
  6. The chain formed by following aft links from root is acyclic and includes every entry exactly once.
  7. All entries have the same iss value.
  8. Root entry contains pk and it is a valid public JWK.
  9. In root-to-head chain order, every token verifies with the current [=active verification key=], initialized from root pk and updated only by verified rot members.

Abstract Operations

This specification defines the following abstract operations: [=create a JWH entry=], [=tokenize a JWH entry=], [=parse a JWH entry token=], [=verify a JWH entry token=], [=validate a JWH snapshot=], [=start a JWH=], [=extend a JWH=], [=inspect a JWH=], and [=merge JWH snapshots=].

Error Code

A machine-readable identifier for a specific failure condition. Conforming implementations MUST expose an [=error code=] for every operation failure defined in this specification.

Minimum required [=error code=] conditions
Code Failure condition
TOKEN_INVALID_COMPACT_JWS A [=JWH entry token=] is not a compact JWS with exactly three dot-separated parts.
TOKEN_INVALID_PROTECTED_HEADER A protected header is missing required members or violates this specification's JWS profile.
TOKEN_ALG_NONE_FORBIDDEN A token declares alg as none.
TOKEN_SIGNATURE_VERIFICATION_FAILED Signature verification fails for a token.
HISTORY_EMPTY_SNAPSHOT A [=JWH snapshot=] contains no entries.
HISTORY_ISSUER_MISMATCH Entries in one history do not share the same iss.
HISTORY_ROOT_KEY_MISSING Root entry is missing required pk.
HISTORY_ROOT_KEY_MISMATCH A supplied expected root key does not match root pk.
HISTORY_ROOT_KEY_INVALID Root pk is not a valid public JWK compatible with the chain's verification process.
HISTORY_ROTATION_KEY_INVALID A rot member is present but not a valid public JWK.
ENTRY_INVALID_CLAIMS_OBJECT Entry creation input claims is not a JSON object.
ENTRY_RESERVED_MEMBER_OVERRIDE Entry creation input attempts to set a [=reserved entry members=] name through extension members.
HISTORY_FORK_DETECTED More than one child references the same parent entry.
HISTORY_CHAIN_DISCONNECTED The chain is not a single connected root-to-head sequence.
HISTORY_MERGE_CONFLICTING_JTI Two snapshots contain the same jti with different payloads.

Create a JWH Entry

This operation takes iss, aft, and extension members object claims and returns a [=JWH entry=].

  1. If claims is not a JSON object, fail with ENTRY_INVALID_CLAIMS_OBJECT.
  2. Create a new JSON object entry.
  3. Set entry.jti to a new non-empty unique string.
  4. Set entry.iss to iss.
  5. Set entry.nbf to the current NumericDate.
  6. Set entry.aft to aft.
  7. For each own member name of claims:
    1. If name is one of [=reserved entry members=], fail with ENTRY_RESERVED_MEMBER_OVERRIDE.
    2. Set entry[name] to claims[name].
  8. Return entry.

Tokenize a JWH Entry

This operation takes a [=JWH entry=] and signing key and returns a [=JWH entry token=].

  1. Construct a protected header containing typ set to JWT and alg set to the key algorithm.
  2. Serialize header and payload as UTF-8 JSON and base64url encode each value [[!RFC7515]].
  3. Sign the JWS Signing Input and produce a JWS Compact Serialization [[!RFC7515]].
  4. Return the resulting compact JWS string.

Parse a JWH Entry Token

This operation takes a [=JWH entry token=] and returns decoded header, payload, signature bytes, and signing input without validating the signature.

  1. Split token by . into three parts.
  2. If there are not exactly three parts, fail.
  3. Decode and parse protected header JSON.
  4. Require typ to equal JWT.
  5. Require alg to be a non-empty string.
  6. Decode and parse payload JSON as a [=JWH entry=].
  7. Decode signature bytes.
  8. Return parsed components.

Verify a JWH Entry Token

This operation takes a [=JWH entry token=] and verification key and returns the verified [=JWH entry=].

  1. Run [=parse a JWH entry token=].
  2. If header alg is none, fail.
  3. If the verification key declares an algorithm and it differs from header alg, fail.
  4. Verify JWS signature using [[!RFC7515]].
  5. If signature verification fails, fail.
  6. Return the parsed payload entry.

Validate a JWH Snapshot

This operation takes a [=JWH snapshot=] (or [=JWH string=]) and optionally an expected [=root verification key=] and returns normalized history state.

  1. If input is a [=JWH string=], run [=parse a JWH string=] to obtain a [=JWH snapshot=].
  2. If the snapshot is empty, fail with HISTORY_EMPTY_SNAPSHOT.
  3. Initialize an empty map keyed by jti.
  4. Initialize issuer state as unset.
  5. For each token in snapshot order:
    1. Run [=parse a JWH entry token=].
    2. If issuer state is unset, set issuer state to entry.iss; otherwise require equality, or fail with HISTORY_ISSUER_MISMATCH.
    3. If jti is already present:
      1. If payload is equal, fail with HISTORY_DUPLICATE_JTI.
      2. Otherwise fail with HISTORY_CONFLICTING_JTI.
    4. Store the entry and token by jti.
  6. Apply [=history validity=] requirements that do not depend on signature verification.
  7. Compute root-to-head chain order from aft links.
  8. Ensure original token order is root-to-head chain order; otherwise fail with HISTORY_UNORDERED_SNAPSHOT.
  9. Let root be the first entry in chain order.
  10. If root is missing pk, fail with HISTORY_ROOT_KEY_MISSING.
  11. If root.pk is not a valid public JWK, fail with HISTORY_ROOT_KEY_INVALID.
  12. If an expected root verification key is provided and it does not equal root.pk, fail with HISTORY_ROOT_KEY_MISMATCH.
  13. Initialize [=active verification key=] from root.pk.
  14. For each entry in chain order:
    1. Run [=verify a JWH entry token=] using that entry's token and the current [=active verification key=].
    2. If entry has rot:
      1. If entry.rot is not a valid public JWK, fail with HISTORY_ROTATION_KEY_INVALID.
      2. Set [=active verification key=] to entry.rot.
  15. Return normalized validated state.

Parse a JWH String

This operation takes a [=JWH string=] and returns a [=JWH snapshot=].

  1. Parse the input as JSON.
  2. If parsing fails or the value is not a JSON array, fail with STRING_INVALID_JSON_ARRAY.
  3. Require each array item to be a non-empty string, or fail with STRING_INVALID_SNAPSHOT_TOKEN.
  4. Return the array as a [=JWH snapshot=].

Start a JWH

This operation takes iss, extension members object claims, a root signing key, and a root public key, and returns a [=JWH string=].

  1. Let entry be the result of [=create a JWH entry=] with aft set to [=root pointer=] and claims.
  2. Set entry.pk to the supplied root public key.
  3. Let token be the result of [=tokenize a JWH entry=] using entry and the root signing key.
  4. Let snapshot be an array containing token.
  5. Run [=validate a JWH snapshot=] on snapshot.
  6. Return JSON serialization of snapshot.

Extend a JWH

This operation appends a new entry to a valid history. It takes an existing history, extension members object claims, a signing key, and optional rotation key nextKey, and returns a [=JWH string=].

  1. Run [=validate a JWH snapshot=] on the input history.
  2. Let head be the last entry in validated chain order.
  3. Let entry be [=create a JWH entry=] with iss = head.iss, aft = head.jti, and claims.
  4. If nextKey is present:
    1. If nextKey is not a valid public JWK, fail with HISTORY_ROTATION_KEY_INVALID.
    2. Set entry.rot to nextKey.
  5. Let token be [=tokenize a JWH entry=] for entry.
  6. Append token to the snapshot.
  7. Run [=validate a JWH snapshot=] on the updated snapshot.
  8. Return JSON serialization of the updated snapshot.

Inspect a JWH

This operation returns the [=resolved state=] at time t.

  1. Run [=validate a JWH snapshot=].
  2. Select entries in validated chain order where entry.nbf <= t.
  3. If no entry remains, fail.
  4. Initialize an empty JSON object state.
  5. For each selected entry in chain order:
    1. For each own member name-value pair in the entry:
      1. If name is one of [=reserved entry members=], continue.
      2. Set state[name] to value.
  6. Return state.

Merge JWH Snapshots

This operation takes one or more [=JWH snapshot=] values and returns a single merged [=JWH snapshot=].

A merge MUST be accepted whenever the deduplicated token set forms one directed linear root-to-head graph and validates from root pk through any verified rot transitions.

  1. If no input snapshots are provided, fail with HISTORY_MERGE_EMPTY_INPUT.
  2. Initialize an empty map keyed by jti.
  3. For each input snapshot and each token in that snapshot:
    1. Run [=parse a JWH entry token=].
    2. If an entry with the same jti already exists:
      1. If payload differs, fail with HISTORY_MERGE_CONFLICTING_JTI.
      2. If payload is equal, ignore the duplicate token.
    3. Otherwise add the entry and token to merge state.
  4. Apply [=history validity=] requirements that do not depend on signature verification.
  5. Order merged entries from root to head using aft links.
  6. Let mergedSnapshot be tokens in that order. If a token is missing for any entry, fail with HISTORY_MERGE_MISSING_TOKEN.
  7. Run [=validate a JWH snapshot=] on mergedSnapshot.
  8. Return mergedSnapshot.

Reference Implementation Mapping

A reference TypeScript implementation can expose these operations as top-level functions and optional convenience classes. Class names and method names are not normative requirements of this specification.

Security Considerations

Implementations MUST follow JWT best current practices from [[!RFC8725]]. In particular, implementations MUST enforce algorithm allowlists and MUST reject alg=none.

Consumers MUST verify every token signature before relying on entry contents. Treating unverified payload data as trusted history state can enable forgery.

Consumers MUST derive verification keys from root pk and only from verified rot transitions.

Implementations MUST treat malformed or invalid histories as hard failures and MUST NOT auto-repair by dropping conflicting entries.

IANA Considerations

This document has no IANA actions.

This specification defines conformance requirements for producers and consumers of [=JSON Web History=] artifacts. Requirements are expressed in this document using the terminology defined in [[#conventions]].