Skip to content

Builders emit only Identifier::Basic prefixes — cannot construct a self-addressing KEL chain (write/read parity gap) #68

Description

@joeldsouzax

Surfaced by the P2.2 (#32) examples research pass. Correctness / write-path–read-path parity gap.

Summary

The event builders cannot construct a rotation/interaction event whose identifier prefix ("i") matches the self-addressing identifier its own inception produced. The deserializer accepts self-addressing KELs that the builders can never emit. This blocks building a genuine icp → ixn → rot KEL (and a self-addressing delegated inception) through the public API — the deferred #32 examples #5 (KEL chain) and #6 (delegated inception).

Reproduction (all from source)

The crate models identifiers correctly as a sum type:

// src/keri/identifier.rs:15
pub enum Identifier<'a> {
    Basic(Prefixer<'a>),          // key-coded, non-transferable
    SelfAddressing(Saider<'a>),   // digest-coded (SAID), transferable → rotatable
}
  • Read path is correct: src/serder/deserialize.rs:384 (parse_qb64_identifier) maps a digest-coded "i"Identifier::SelfAddressing (line 399) and a key-coded "i"Identifier::Basic (line 390). So it accepts self-addressing rotation/interaction events.
  • Write path is not: the builders take Prefixer (= Matter<VerKeyCode>), never Identifier:
    • src/serder/builder/rot.rs:82 pub fn prefix(self, prefix: Prefixer<'static>)
    • src/serder/builder/ixn.rs:62 pub fn prefix(self, prefix: Prefixer<'static>)
    • src/serder/builder/dip.rs:98 pub fn delegator(self, delegator: Prefixer<'static>)
    • witness fields: icp.rs:133, dip.rs:134, plus rot.rs witness_additions/removals — all Vec<Prefixer<'static>>
  • rot.build() (src/serder/builder/rot.rs:253) does RotationEvent::new(prefix.into(), …), and From<Prefixer> for Identifier (src/keri/identifier.rs:51) yields Identifier::Basic. Every builder-produced rot/ixn "i" is therefore Basic.
  • An InceptionBuilder produces a self-addressing prefix: SerializedEvent::prefix() returns Option<&Saider<'static>> (digest-coded) — src/serder/serialize.rs:90. Saider = Matter<DigestCode> vs Prefixer = Matter<VerKeyCode> (src/core/primitives/mod.rs:43,46) are distinct types with no From<Saider> for Prefixer.

Result: you cannot feed an inception's Saider prefix into RotationBuilder::prefix(). The repo's own tests sidestep this by fabricating an unrelated key prefix: rot.rs make_prefixer() builds Matter<VerKeyCode> from vec![3u8; 32] — no relationship to any inception. So the tests never chain a real KEL, and the write path can only emit Basic prefixes the read path treats as non-transferable.

This is a Mandatory-Rule-3 violation: "Read-path and write-path must enforce the same invariants the same way."

Proposed cleanup

  • Builders accept the identifier as impl Into<Identifier<'static>> (or Identifier<'static>) for RotationBuilder::prefix, InteractionBuilder::prefix, DelegatedInceptionBuilder::delegator, and witness fields — so a self-addressing prefix is expressible.
  • Add an ergonomic bridge from inception output to the next event: e.g. From<&SerializedEvent> for Identifier, or SerializedEvent::identifier() -> Identifier, so chaining doesn't require re-parsing JSON.
  • Revisit the Prefixer = Matter<VerKeyCode> alias — structurally it cannot hold a digest prefix; the "identifier prefix" concept is Identifier, not Prefixer.
  • Add a round-trip/sequence test: build icp → ixn → rot where each event's "i" equals the inception SAID, serialize + deserialize each, assert the chain is consistent. (This is the test that would have caught the gap.)
  • Unblocks P2.2 · Runnable examples #32 examples test: build cesr no_std + wasm32 (on-device KERI target) #5 (KEL chain) and chore: re-freeze public API + git-dep versioning #6 (delegated inception).

Notes

Breaking change acceptable pre-1.0 — call out in PR + CHANGELOG. This is a parity item (keripy emits self-addressing KELs).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdevxDeveloper experience: API, prelude, examples, errors

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions