open-standards/smart-byte/spec/identity_and_key_rotation.md
Transaction Science 9c8a38ca81 smart-byte spec: the envelope wire format; key-event logs as v1 verifies them
envelope_wire_format.md is new. It fixes what the treatise leaves open: the
envelope's members, what the issuer and each owner sign, the snapshot SAID
and the byte's identity, the history hash, the cargo registry, controller
signatures, and verification with an error class for each failure. It also
says which questions the cluster answers instead.

The identity spec now says:
- an inception placeholders `i` with `d`;
- an event has exactly its §3.3 members;
- thresholds run from 1 to the key count, with no key or digest listed twice;
- nothing rotates after an empty `n`;
- `dip` is not a v1 type.

Rotations follow KERI's rule: signed by the keys they reveal, under both the
committed threshold and their own. The previous rule, "signed by the previous
keys", let anyone holding the current keys rotate the controller away.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 23:00:45 -04:00

27 KiB
Raw Permalink Blame History

Identity and Key Rotation

Smart Byte Substrate — Spec, Section: Identity and Key Rotation. v1.0-draft, May 2026.


1. Why this section exists

Smart Byte envelopes (treatise §8) bind a byte's lifetime to ed25519 public keys held by issuers and owners, but the treatise does not specify how a controller rotates a signing key, how a verifier follows a controller across a rotation, or how the system recovers from key compromise without unilaterally revoking byte history. Those questions have a mature, well-reviewed answer in the Key Event Receipt Infrastructure (KERI) family of specifications. Smart Byte adopts KERI's cryptographic spine — Self-Addressing IDentifiers (SAIDs) and the key-event log format — without taking the wider Authenticated Chained Data Container (ACDC) layering, which the envelope already subsumes. The spine gives Smart Byte what the envelope is missing: a self-certifying identifier scheme and a pre-rotation-protected key history.


2. Self-Addressing IDentifiers (SAIDs)

2.1 Definition

A Self-Addressing IDentifier is the cryptographic digest of a canonically-encoded object that includes a placeholder for the SAID itself, computed in a way that lets any verifier reproduce the digest and confirm that the object's stated SAID matches its content. The procedure is:

  1. Construct the object with the SAID field (d in this spec) set to a fixed-width placeholder equal in length to the final SAID encoding. The placeholder is the canonical "all-zeros" string of the same byte length as the final identifier (KERI uses the literal ASCII character # repeated, which keeps the canonical encoding length-stable; Smart Byte follows that convention).
  2. Serialize the object using the canonical encoding rules in §2.3.
  3. Compute the digest of the canonical bytes using the algorithm named by the SAID's derivation prefix (§2.2).
  4. Encode the digest using the prefix-plus-bytewise-base32 encoding in §2.2.
  5. Replace the placeholder in the object with the resulting SAID.

After step 5, the object is self-certifying: any party in possession of the object can run steps 1–4 against a copy with the SAID field reset to the placeholder, compute the digest, and confirm that the object's d field matches. Any modification of any other field — by even a single bit — produces a different digest, and the verification fails. A party that holds the object as bytes it received first confirms they are the object's canonical encoding (§2.3) and refuses them otherwise; it never re-encodes received bytes into canonical form and verifies the result (§2.5, §2.6).

This is the same content-addressing pattern used for envelope identity in treatise §8.2; the SAID construction generalizes it to any object that needs a content-derived identifier including those that must reference themselves (key-event log entries, anchors, attestations).

2.2 Algorithm and encoding

Smart Byte's SAID derivation uses:

  • Hash function: BLAKE3 with 256-bit output. BLAKE3 is the same hash family the treatise selects for envelope identity (§8.2, SECURITY.md §1); reusing it here keeps a single cryptographic dependency across the substrate.
  • Encoding: a single ASCII prefix byte identifying the digest algorithm, followed by the 32-byte digest encoded in lowercase bytewise base32 (RFC 4648 §6) without padding. Bytewise base32 produces 52 ASCII characters for a 32-byte input; with the prefix the SAID is 53 ASCII characters total.
  • Prefix: the character E denotes BLAKE3-256. This extends KERI's derivation-code table — which originally allocated E to BLAKE3-256 in the family of self-addressing prefixes — and reserves additional single-byte prefixes for future digest algorithms (F is reserved for SHA3-256; G is reserved for a post-quantum hash, unallocated in v1).

A SAID is therefore a 53-character ASCII string of the form E + 52 base32 characters, fitting cleanly inside JSON, CBOR text, and URL components without escaping. The placeholder used during digest computation is the ASCII character # repeated 53 times.

2.3 Canonical encoding

The canonical encoding for SAID computation is CBOR (RFC 8949) with deterministic encoding rules:

  • Map keys are sorted lexicographically by their canonical CBOR byte representation, not by the corresponding string.
  • Integers use the shortest CBOR head that fits the value (a value of 0 is encoded as 0x00, never as 0x18 0x00).
  • Strings use the shortest length-prefix.
  • Floats are forbidden. Any value that would be a float in another encoding must be represented as a fixed-point integer pair.
  • CBOR tags are forbidden. Tagged values change parser behavior and undermine determinism.
  • Simple values other than false, true and null are forbidden. The data model has nothing else to decode them to.
  • Indefinite-length items are forbidden.
  • The encoded form is the only form over which the digest is computed. An object held in another format (JSON, MessagePack) is encoded to canonical CBOR before its digest is computed. CBOR bytes received from another party must already be canonical: a verifier refuses any other encoding and never re-encodes it (§2.5, §2.6).

These are the deterministic encoding rules from RFC 8949 §4.2.1 with the tag, float and simple-value prohibitions made explicit. They are also the rules used by envelope serialization in treatise §8 — the choice is intentional, so that a SAID computed over an envelope-shaped object yields the same identifier whether the SAID is being computed by Smart Byte's envelope-identity machinery or its key-event log machinery.

2.4 Worked example

Consider a minimal envelope-shaped object with two fields and a SAID placeholder. In canonical CBOR diagnostic notation, before SAID insertion, the object is:

{
  "d": "#####################################################",
  "n": 1
}

(The map is shown in sorted-key order; d precedes n lexicographically.)

The canonical CBOR encoding of this object, in hex, is:

A2                                        # map(2)
   61 64                                  # text(1) "d"
   78 35                                  # text(53)
      23 23 23 23 23 23 23 23 23 23
      23 23 23 23 23 23 23 23 23 23
      23 23 23 23 23 23 23 23 23 23
      23 23 23 23 23 23 23 23 23 23
      23 23 23 23 23 23 23 23 23 23
      23 23 23                            # 53 '#' bytes
   61 6E                                  # text(1) "n"
   01                                     # unsigned(1)

Concatenated: A2 61 64 78 35 + 53×23 + 61 6E 01, total 61 bytes:

a2616478352323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323616e01

BLAKE3-256 of those 61 bytes is:

56813033774b7fb781e0f8547eae6ec3ca24728c8af6ae9dd771d1e0258f6d39

The 32-byte digest encoded in lowercase bytewise base32 (no padding) yields 52 characters, prefixed with E to produce the 53-character SAID:

Ek2atam3xjn73papa7bkh5ltoypfci4umrl3k5hoxohi6ajmpnu4q

The final object, with the placeholder replaced, is:

{
  "d": "Ek2atam3xjn73papa7bkh5ltoypfci4umrl3k5hoxohi6ajmpnu4q",
  "n": 1
}

These are the values of vector said-008-spec-worked-example in ../conformance/v1/said_vectors.json, generated by the reference implementation and reproduced by an independent second implementation (../conformance/crosscheck/verify_v1.py).

A verifier holding the object's bytes confirms they are canonical, replaces d with 53 # characters, encodes the result canonically, computes BLAKE3-256, base32-encodes it with the E prefix, and confirms equality with the asserted d.

2.5 Test-vector hook

The full set of canonical worked examples — placeholder bytes, canonical CBOR bytes, BLAKE3 digest, final SAID — lives in ../conformance/v1/said_vectors.json. Implementers MUST pass every vector. The suite covers the empty-map case (said-006), the one-field case (said-007, a §3.4 per-key object), the worked example above (said-008), five objects the substrate addresses by SAID (said-001 to said-005: an issued envelope, its provenance — the byte's identity — the envelope after transfers, and a rotation and an interaction event), and two adversarial cases where a non-canonical CBOR encoding produces a different digest (said-009, said-010), to verify that implementations reject non-canonical input rather than silently re-encoding it or hashing it as received. said-011 is a canonical object that states a SAID its content does not hash to. The canonical-encoding rules of §2.3 are exercised one by one in ../conformance/v1/canonicalisation_vectors.json.

2.6 Receiving SAID-addressed objects

An object that arrives from another party — gossiped (treatise §11.4) or as the answer to a request by SAID (treatise §21.4) — is accepted in four steps, in this order:

  1. Its bytes are the canonical encoding of the object (§2.3). If they are not, the object is refused; it is never re-encoded.
  2. It hashes to the SAID it states in d.
  3. That SAID is the one it was announced or requested under.
  4. Only then: if the SAID is already held, the object is a duplicate and changes nothing.

Verification precedes deduplication, so a forged object announced under a SAID the receiver already holds is reported as a SAID mismatch — the substitution attempt is visible — instead of being absorbed as a duplicate. No first-come rule is needed: two different canonical objects cannot share a SAID short of a BLAKE3 collision. ../conformance/v1/gossip_vectors.json exercises each step.


3. Key-event log format

The Smart Byte key-event log is a sequence of self-addressing events that record the lifecycle of a controller — the cryptographic identity that signs envelopes' provenance and ownership transitions. The log is adopted from KERI §11 with simplifications appropriate to Smart Byte's envelope model.

3.1 Controller identifiers

A controller AID (Autonomic IDentifier) is the SAID of the controller's inception event with the prefix B substituted for E: B followed by the same 52 lowercase base32 characters. The substitution distinguishes a controller identifier from any other SAID, and tells a verifier that the referenced object is an inception event whose key history is recoverable by replaying the log.

An inception event carries its own AID in i, which is derived from the very SAID being computed. While the inception event is hashed, i therefore holds the 53-# placeholder exactly as d does (§2.1), and both are then stamped from the result.

The controller AID replaces raw ed25519 public keys in envelopes: the provenance's issuer and recipient and each transition's from and to are AIDs (envelope wire format §3, §5). An envelope signature names the establishment event whose key set made it, and is verified against that event's key set as §4 replay establishes it — so a byte stays verifiable across its owners' rotations.

3.2 Event types

There are four event types in v1. Each is a CBOR map with the field set described in §3.3.

  • Inception (icp) — the first event in a controller's log. Binds the controller AID (the SAID of the inception event, prefixed with B) to:

    • a set of signing keys (k),
    • a signing-key threshold (kt) — the number of valid signatures required for an event signed by this key set to be accepted,
    • a set of pre-rotated keys committed by digest (n),
    • a next-key threshold (nt) — the threshold that will apply to the next rotation.

    The pre-rotated commitment is the cryptographic core of KERI. The next key set is committed by hash at inception (or at the most recent rotation), so an adversary who compromises the current signing keys still cannot mint a new chain of events for the controller: minting requires the pre-images of the committed next-key digests, which were chosen by the controller and never exposed.

  • Rotation (rot) — replaces the current signing keys with the keys committed by the previous event's n field. A rotation event:

    • reveals a set of signing keys (k) whose per-key digests under the §3.4 derivation must match the previous event's n field, in order, exactly,
    • establishes a new signing-key threshold (kt),
    • commits a new set of pre-rotated keys (n),
    • establishes a new next-key threshold (nt).

    After a rot event commits, the keys revealed in its k field are the controller's current signing keys.

  • Interaction (ixn) — a signed anchor to other content. Carries an anchors array (a) referencing one or more external objects by SAID — envelopes, ownership transitions, external commitments, third-party attestations. Does not change the controller's keys; the signing-key set after an ixn event is identical to the set before. Interaction events are the substrate's mechanism for binding off-log content to the controller's verifiable history without spending a rotation.

  • Recovery (rec) — reserved. A future revision of this section will specify key-loss recovery semantics — the rules under which a controller who has lost the pre-images for an n commitment can re-establish control without a full chain reissue. v1 implementations MUST reject rec events. The reservation exists so the event-type field is forward-compatible; the protocol is not yet specified.

No other event type exists in v1. Delegated inception, which KERI defines, is not part of this specification.

3.3 Event field layout

Every event is a CBOR map with the following fields, in canonical-sorted order:

{
  "a":  [<anchors>],            # array; SAIDs of anchored objects; empty for icp and rot
  "d":  "<SAID of this event>", # SAID, computed per §2 over the event with d as placeholder
  "i":  "<controller AID>",     # the inception event's SAID with the 'B' prefix substitution
  "k":  [<signing keys>],       # array of signing-key encodings; ed25519 keys carry the
                                # one-byte algorithm identifier from treatise §8.3
  "kt": <signing threshold>,    # unsigned integer; minimum number of valid signatures
                                # against keys in k for an event signed by this key set
                                # to be accepted
  "n":  [<next-key digests>],   # array of SAIDs of next-key public keys, in the same
                                # order as the future rot event's k field
  "nt": <next threshold>,       # unsigned integer; the kt that will apply at next rotation
  "p":  "<SAID of prior event>",# the d of the immediately preceding event; empty string
                                # for icp (the first event has no predecessor)
  "s":  <sequence number>,      # unsigned integer; 0 for icp, incremented by exactly 1
                                # for each subsequent event in the log
  "t":  "icp" | "rot" | "ixn" | "rec",
  "v":  "SBYTE10JSON"           # version string; SBYTE + major-version digit +
                                # minor-version digit + serialization tag
}

Notes on individual fields:

  • v is SBYTE10JSON: Smart Byte v1.0 with canonical CBOR encoding. The JSON tag is held over from KERI for forward-compatibility with a future JSON-serialization variant.
  • t is one of the four lowercase tokens above.
  • s starts at 0 for icp and increments by exactly 1 per event — no gaps, no reuse.
  • p is the empty string for icp and the previous event's d for every other event.
  • kt is an unweighted integer threshold; KERI's weighted-threshold extension is reserved for a later revision. It is at least 1 and at most the number of keys in k.
  • Each entry in k is a byte string: the one-byte algorithm identifier from treatise §8.3 (0x01 for ed25519; the remaining range is reserved for post-quantum schemes such as ML-DSA) followed by the public key. Order matters; the same key in a different position produces a different n commitment and breaks the chain. No key appears twice: one holder of a key listed twice could meet a two-key threshold alone.
  • nt is committed in advance so an adversary cannot lower the threshold without revealing pre-rotated keys.
  • Each entry in n is the SAID of the corresponding next signing key per §3.4, and no entry appears twice. An empty n signals the controller does not intend to rotate further; thereafter only ixn events are accepted.
  • nt is 0 when n is empty, and otherwise at least 1 and at most the number of entries in n.
  • a is populated for ixn and empty for icp and rot.
  • An event has exactly these members, each of the type given. A member this section does not define — or a member written in another form, such as a key as an array of integers rather than a byte string — makes the event malformed: its bytes must be the event's own canonical encoding, so that its SAID covers exactly what it says.

3.4 Per-key SAID derivation

Each entry in n is the SAID of a tiny CBOR map containing the corresponding next signing key, computed per §2 with the placeholder rules. Specifically, for each next-key K_i (the algorithm-prefixed key byte string of §3.3 the controller intends to use after the next rotation), the entry n[i] is:

SAID({"d": "<placeholder>", "k": h'<K_i>'})

with d initialized to the 53-# placeholder, the map canonically encoded per §2.3, the digest computed per §2.2, and d replaced with the resulting SAID. This per-key SAID is what appears in the previous event's n array; at rotation, the verifier recomputes the per-key SAID for each k[i] in the new event and confirms it matches n[i] from the previous event, position-wise.

A per-key SAID is used rather than a raw key digest so that adding fields to the per-key envelope in a later revision does not change the v1 derivation rule.

3.5 Signatures on events

Who signs depends on the event:

  • Inception. Signed by the keys it reveals in its own k: the inception is self-signed.
  • Rotation. Signed by the keys it reveals. Those keys must match the previous establishment event's n commitment, and the signatures must meet both that event's nt and the rotation's own kt. Only the holder of the pre-rotated keys can rotate, so an adversary who has compromised the current keys cannot. Nor can the committed threshold be lowered without the pre-rotated keys.
  • Interaction. Signed by the current key set.

The signatures are carried outside the event map, in a wrapper that pairs the canonical CBOR-encoded event with an array of (key-index, signature) pairs. Each signature is the key's one-byte algorithm identifier followed by the signature (0x01 and 64 bytes for Ed25519) over the event's canonical encoding. The wrapper is not part of the SAID computation; the event's d covers the event body only.

This separation lets a verifier confirm the event's SAID independently of signature verification, and lets multiple signing parties append signatures incrementally without changing the event's identifier.


4. Verifier algorithm

Given a sequence of events and a current controller AID, a verifier accepts the log if and only if every check below passes for every event in order from s: 0 to the latest event seen.

  1. Decoding and SAID verification. Confirm the event's bytes are canonical (§2.3) and reject them otherwise (§2.6). Confirm t is icp, rot or ixn — rec is reserved (§3.2), and no other type exists — and that the event has exactly the §3.3 members, each of its type, and v is SBYTE10JSON. Then replace the event's d field — and, for an inception, its i field (§3.1) — with the 53-character placeholder, encode canonically, compute BLAKE3-256, base32-encode with the E prefix, and confirm equality with the asserted d. Reject the event if the recomputed SAID does not match. For an inception, also confirm i is the AID its d names.

  2. Sequence verification. Confirm the event's s is exactly the previous event's s + 1. For the inception event, confirm s is 0. Reject the event on any gap, repeat, or out-of-order value.

  3. Prior-event verification. Confirm the event's p equals the previous event's d. For the inception event, confirm p is the empty string. Reject the event on mismatch.

  4. Controller AID verification. Confirm the event's i equals the inception event's d with the digest-algorithm prefix replaced by B. Reject the event on mismatch (an event purporting to be in this controller's log but bearing a different AID is malformed and may indicate a confused or hostile log assembler).

  5. Type-specific structural check.

    • For icp: confirm it is the first event, a is the empty array, and its key fields are well-formed: k holds at least one key and no key twice, 1 <= kt <= len(k), n holds no digest twice, and nt is 0 if n is empty and otherwise 1 <= nt <= len(n) (§3.3).
    • For rot: confirm the previous establishment event's n is not empty — a controller that committed to no next keys cannot rotate. Confirm the keys in k have per-key SAIDs (per §3.4) matching that n array, in order; reject the event if any per-key SAID mismatches or if len(k) != len(n). Confirm a is the empty array, and that its key fields are well-formed as for icp.
    • For ixn: confirm k equals the previous event's k, kt equals the previous event's kt, n equals the previous event's n, nt equals the previous event's nt. An interaction event does not change keys, so the unchanged fields must be carried forward verbatim. Reject the event on any divergence.
    • For rec: reject. v1 does not define recovery semantics, and any rec event in a v1 log is malformed.
  6. Signature verification. Verify the event's signatures over its canonical encoding, against the key set and threshold §3.5 assigns it:

    • icp: the keys in its own k, threshold kt.
    • rot: the keys in its own k, threshold the greater of the previous establishment event's nt and its own kt.
    • ixn: the current key set — the inception's or the most recent rot's — and its kt.

    Every listed signature must be valid, no key index may appear twice, and at least the threshold of keys must have signed. Reject the event otherwise.

  7. Replay determinism. After all checks pass, update the verifier's notion of the controller's current signing key set, signing threshold, next-key commitment, and next threshold according to the event's k, kt, n, nt fields. For ixn events, these fields are unchanged from the previous event (per §3.5 above) and the update is a no-op.

Replay also records the key state each establishment event put in force. An envelope's signatures name the establishment event whose keys made them and are verified against that event's key state (envelope wire format §7), so a byte signed under keys since rotated away stays verifiable — the property that lets a byte's ownership chain span a rotation without a break. Whether the named event was the one in force at a transfer's frame is for the cluster to decide (envelope wire format §9).

A verifier MUST treat any failed check as terminal: events with later sequence numbers are not considered part of the log until the rejected event is supplied in a valid form (a different event with a different SAID). v1 does not specify a protocol for replacing a rejected event — that is the recovery problem reserved for rec.


5. Explicit non-adoption: ACDC chaining

The Authenticated Chained Data Containers (ACDC) specification extends KERI's cryptographic spine with a property-graph model over chained containers — each carrying issuer, schema, edges, and rules — and is the right answer for verifiable-credential applications like the GLEIF vLEI deployments (Provenant, Veridian). Smart Byte envelopes already supply the structural primitives ACDC contributes: provenance is the issuer attestation (§8.3), cargo is the payload (§8.5), and the ownership chain is the cryptographically-bound transition chain (§8.4). An ACDC layer on top of the envelope would be either redundant or competing, and either would erode the envelope's role as the substrate's only object (§8.1). The KERI cryptographic spine — SAIDs and the key-event log — is adopted because it solves what the envelope does not. The ACDC container model is not adopted. A future revision may revisit this decision if a substrate-layer use case for property-graph attestations emerges that the envelope cannot accommodate.


6. Implementation reference

The Rust reference implementation of this section lives in the smart-byte-rs workspace:

  • smart-byte-rs/crates/smart-byte-core/src/canonical.rs — the canonical CBOR encoding of §2.3, and a strict decoder that refuses non-canonical input instead of re-encoding it.
  • smart-byte-rs/crates/smart-byte-core/src/said.rs — SAID derivation and verification (§2.1, §2.2), placeholder substitution, and per-key SAID derivation (§3.4).
  • smart-byte-rs/crates/smart-byte-core/src/inbox.rs — receiving SAID-addressed objects (§2.6).
  • smart-byte-rs/crates/smart-byte-conformance — the conformance-vector harness referenced in §2.5. It generates ../conformance/v1 from the reference modules and checks the published pack under cargo test.
  • smart-byte-rs/crates/smart-byte-keri-witness — the key-event log: event type definitions, the wrapper that pairs events with signatures, the verifier algorithm in §4, and helpers for emitting icp, rot, and ixn events. Its verifier reports every establishment event's key state, which is what envelope signatures are checked against. Outside spec v1 it also carries extensions — a witness pool with receipts and watchers, delegated inception, and recovery that counts only with its witnesses' receipts — which its default, spec-v1 verifier refuses.

Where the reference crate and this section disagree, this section governs. The conformance vectors are the authoritative reference, independent of any single language's implementation: ../conformance/v1/said_vectors.json for §2, and ../conformance/v1/kel_vectors.json for §3–§4.


7. Citations

  • Smith, Samuel M. Key Event Receipt Infrastructure (KERI). Whitepaper, v2.54, 2021–2024. https://github.com/SmithSamuelM/Papers
  • Smith, Samuel M., et al. Authentic Chained Data Containers (ACDC) Specification. IETF Internet-Draft, draft-ssmith-acdc. Current revision: 2024–2025.
  • Trust over IP Foundation, KERI/ACDC Working Group. KSWG-ACDC Specification. https://trustoverip.github.io/tswg-acdc-specification/
  • Bormann, Carsten, and Paul Hoffman. Concise Binary Object Representation (CBOR). RFC 8949, IETF, December 2020.
  • Josefsson, Simon, and Ilari Liusvaara. Edwards-Curve Digital Signature Algorithm (EdDSA). RFC 8032, IETF, January 2017.
  • Josefsson, Simon. The Base16, Base32, and Base64 Data Encodings. RFC 4648, IETF, October 2006.
  • O'Connor, Jack, et al. BLAKE3: One Function, Fast Everywhere. 2020. https://github.com/BLAKE3-team/BLAKE3-specs
  • Global Legal Entity Identifier Foundation (GLEIF). vLEI Ecosystem Governance Framework. Production deployments via Provenant and Veridian, 2024–2026.

Cross-references within this spec are to other files in ./ (e.g. treatise_v1_parts_I_II_III_combined.md, ../conformance/v1/said_vectors.json); all paths are relative to this file.