K1 (#87) implements the rotation next-key commitment as a strict positional full-rotation check: revealed.len() == committed.len() and each revealed key hashes to the positionally-corresponding committed digest, with the full revealed index-set checked against the prior next-threshold.
keripy's actual semantics (verified against keripy/src/keri/core/eventing.py) are ondex-based, not positional:
Kever.exposeds (~L2843): for each signature, diger = self.ndigers[siger.ondex], then admit that ondex iff Diger(ser=siger.verfer.qb64b, code=diger.code) == diger.
- Then
self.ntholder.satisfy(indices=ondices) (~L2757) must hold over the admitted ondex set.
Consequences of the keripy model that K1's strict form does NOT support:
- Partial rotation — revealing only a satisfying subset of the committed next keys (no
revealed.len() == committed.len() requirement).
- Reordered / multi-code reveals — mapping by
siger.ondex rather than array position.
The strict form is conservative (it over-rejects vs keripy; it never over-accepts), so it is correct for K1's single-code full-rotation happy path where ondex == index, and safe to ship. This card adopts the ondex-based exposeds semantics.
Prerequisite
The fold must consume the signatures' dual-index (Siger::ondex) in the rotation commitment check. Siger::ondex() -> Option<u32> already exists on cesr's public API.
Scope
- Rewrite
keri/src/fold/rotation.rs commitment check to map each signature's ondex to prior.next_keys()[ondex], hash siger.verfer().qb64b under that digest's code, admit the ondex on match, and check satisfied_by(prior.next_threshold(), &admitted_ondices).
- Drop the
revealed.len() == committed.len() requirement.
- Add partial-rotation + reordered-reveal differential vectors (ties into K9 corpus).
Spawned from #87 (K1) Phase 6; keripy teardown 2026-07-06.
K1 (#87) implements the rotation next-key commitment as a strict positional full-rotation check:
revealed.len() == committed.len()and each revealed key hashes to the positionally-corresponding committed digest, with the full revealed index-set checked against the prior next-threshold.keripy's actual semantics (verified against
keripy/src/keri/core/eventing.py) areondex-based, not positional:Kever.exposeds(~L2843): for each signature,diger = self.ndigers[siger.ondex], then admit thatondexiffDiger(ser=siger.verfer.qb64b, code=diger.code) == diger.self.ntholder.satisfy(indices=ondices)(~L2757) must hold over the admittedondexset.Consequences of the keripy model that K1's strict form does NOT support:
revealed.len() == committed.len()requirement).siger.ondexrather than array position.The strict form is conservative (it over-rejects vs keripy; it never over-accepts), so it is correct for K1's single-code full-rotation happy path where
ondex == index, and safe to ship. This card adopts theondex-basedexposedssemantics.Prerequisite
The fold must consume the signatures' dual-index (
Siger::ondex) in the rotation commitment check.Siger::ondex() -> Option<u32>already exists on cesr's public API.Scope
keri/src/fold/rotation.rscommitment check to map each signature'sondextoprior.next_keys()[ondex], hashsiger.verfer().qb64bunder that digest's code, admit the ondex on match, and checksatisfied_by(prior.next_threshold(), &admitted_ondices).revealed.len() == committed.len()requirement.Spawned from #87 (K1) Phase 6; keripy teardown 2026-07-06.