open-standards/smart-byte/smart-byte-rs/crates/smart-byte-conformance/src/world.rs
Transaction Science 35bb8c9910 smart-byte: the envelope wire format, v1 key-event logs, and their vectors
smart-byte-core
- `wire`: canonical CBOR for any serde type. `from_slice` refuses bytes that
  are not the decoded object's own encoding. Serde otherwise accepts an array
  of integers for a byte string, a byte string for text, and an array for a
  struct, each a second encoding the SAID does not cover.
- Controller AIDs. Controller signatures are checked against the key states
  replayed logs establish. One strict Ed25519 rule.
- The envelope: attested provenance, ownership chain and joule cost. Cargo
  carries a registered type tag.

smart-byte-keri-witness
- Events are §2 SAIDs over canonical CBOR.
- The default verifier is spec v1. It replays one event at a time and says
  which event it refused. It refuses `dip`, which skipped the pre-rotation
  check entirely, plus zero thresholds, duplicate keys, rotations after an
  empty `n`, and extension members.
- Extension mode, fixed:
  - Witness receipts carried their own key, so anyone could receipt in any
    witness's name. Witnesses are now named by their keys, and receipts sign
    everything they claim.
  - Duplicity evidence was gathered from unverified receipts.
  - Recovery accepted any keys; it now needs its witnesses' receipts.
  - OOBIs were signed by an embedded key; they are now controller signatures.
  - Configuration traits were never enforced, so they are gone.

VC, BBS+, CRDT, mDL, lockstep, libp2p and the CLI move to the new envelope.

smart-byte-conformance
- kel_vectors.json: 25 vectors.
- envelope_vectors.json: 18 vectors, checked against the logs' key states.
- The SAID and signature vectors are rebuilt on the real objects, so all the
  files describe one set of parties.
- Planted defects in every file are caught on their own vectors.

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

354 lines
11 KiB
Rust

//! The parties the pack is built from: three controllers, the key-event logs
//! that establish their keys, and the smart bytes they issue and transfer.
//!
//! * The **issuer** has one key and has never rotated.
//! * **Alice** has one key, has rotated once, and has anchored the byte she
//! was issued in an interaction event.
//! * The **custodian** holds three keys, any two of which sign, and has
//! rotated once.
//!
//! The issuer issues Alice a USD byte; it passes Alice → custodian → Alice →
//! custodian, and Alice's second transfer is signed with the key she rotated
//! to. Every key derives from a published seed, so every signature in the
//! pack can be reproduced: Ed25519 is deterministic.
use std::collections::HashMap;
use smart_byte_core::sign::Signer as _;
use smart_byte_core::{Aid, Cargo, Envelope, JouleCost, KeyBook, Said, Signer, SigningKey};
use smart_byte_keri_witness::events::{
InceptionEvent, InteractionEvent, KeyEvent, PublicKey, RotationEvent, Threshold,
finalize_event, per_key_said,
};
use smart_byte_keri_witness::{LogVerifier, SignedEvent, VERSION_STRING};
/// The seed of RFC 8032 §7.1 test 1: the issuer's first key.
pub const RFC8032_TEST1_SEED: &str =
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
/// The seed of RFC 8032 §7.1 test 2: Alice's first key.
pub const RFC8032_TEST2_SEED: &str =
"4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb";
/// The names the pack gives the three controllers' logs.
pub const ISSUER_LOG: &str = "kel-001-single-key-inception";
pub const ALICE_LOG: &str = "kel-002-rotation-then-interaction";
pub const CUSTODIAN_LOG: &str = "kel-003-two-of-three-rotation";
pub fn hex32(s: &str) -> [u8; 32] {
hex::decode(s).unwrap().try_into().unwrap()
}
/// A key the pack signs with.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Key {
pub seed: [u8; 32],
}
impl Key {
/// The key named `label`. The issuer's and Alice's first keys are RFC
/// 8032's test keys, so an implementation can check its Ed25519 against
/// the RFC; every other seed is BLAKE3 of the label.
pub fn named(label: &str) -> Key {
let seed = match label {
"issuer-0" => hex32(RFC8032_TEST1_SEED),
"alice-0" => hex32(RFC8032_TEST2_SEED),
_ => Said::hash(format!("smart-byte/conformance/v1 key {label}").as_bytes()).0,
};
Key { seed }
}
pub fn signing(&self) -> SigningKey {
SigningKey::from_bytes(&self.seed)
}
/// The key as an event writes it: `0x01` and the Ed25519 public key.
pub fn public(&self) -> PublicKey {
PublicKey::new(0x01, self.signing().verifying_key().as_bytes())
}
/// Its next-key commitment (identity spec §3.4).
pub fn digest(&self) -> Said {
per_key_said(&self.public()).unwrap()
}
/// `0x01` and the Ed25519 signature over `message`.
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
[&[0x01][..], &self.signing().sign(message).to_bytes()].concat()
}
}
pub fn keys(labels: &[&str]) -> Vec<Key> {
labels.iter().map(|l| Key::named(l)).collect()
}
/// One signature on a logged event, with the seed of the key that made it
/// (`None` for a signature no seed reproduces).
#[derive(Clone, Debug)]
pub struct EventSignature {
pub index: u32,
pub signature: Vec<u8>,
pub seed: Option<[u8; 32]>,
}
/// An event as a log carries it: its bytes and the signatures over them.
#[derive(Clone, Debug)]
pub struct LoggedEvent {
pub bytes: Vec<u8>,
pub signatures: Vec<EventSignature>,
}
impl LoggedEvent {
/// `event`, signed by `signers` as `(key index, key)`.
pub fn signed(event: &KeyEvent, signers: &[(u32, Key)]) -> Self {
Self::signed_bytes(event.to_cbor().unwrap(), signers)
}
/// `bytes` — which need not be an event — signed by `signers`.
pub fn signed_bytes(bytes: Vec<u8>, signers: &[(u32, Key)]) -> Self {
let signatures = signers
.iter()
.map(|(index, key)| EventSignature {
index: *index,
signature: key.sign(&bytes),
seed: Some(key.seed),
})
.collect();
Self { bytes, signatures }
}
pub fn wire(&self) -> SignedEvent {
SignedEvent {
bytes: self.bytes.clone(),
signatures: self
.signatures
.iter()
.map(|s| (s.index, s.signature.clone()))
.collect(),
}
}
/// The event, for one the reference decodes.
pub fn event(&self) -> KeyEvent {
KeyEvent::decode(&self.bytes, false).expect("a well-formed event")
}
}
pub fn wire_log(log: &[LoggedEvent]) -> Vec<SignedEvent> {
log.iter().map(LoggedEvent::wire).collect()
}
fn public_keys(k: &[Key]) -> Vec<PublicKey> {
k.iter().map(Key::public).collect()
}
fn digests(n: &[Key]) -> Vec<Said> {
n.iter().map(Key::digest).collect()
}
/// An inception event, stamped.
pub fn inception(k: &[Key], kt: u64, n: &[Key], nt: u64) -> KeyEvent {
finalize_event(KeyEvent::Inception(InceptionEvent {
v: VERSION_STRING.into(),
d: Said::default(),
i: Aid::default(),
s: 0,
p: String::new(),
kt: Threshold(kt),
k: public_keys(k),
nt: Threshold(nt),
n: digests(n),
a: vec![],
b: vec![],
bt: Threshold(0),
}))
.unwrap()
}
/// A rotation following `prior`, stamped.
pub fn rotation(prior: &KeyEvent, k: &[Key], kt: u64, n: &[Key], nt: u64) -> KeyEvent {
finalize_event(KeyEvent::Rotation(RotationEvent {
v: VERSION_STRING.into(),
d: Said::default(),
i: *prior.controller(),
s: prior.sequence() + 1,
p: prior.said(),
kt: Threshold(kt),
k: public_keys(k),
nt: Threshold(nt),
n: digests(n),
a: vec![],
br: vec![],
ba: vec![],
bt: Threshold(0),
}))
.unwrap()
}
/// An interaction following `prior`, carrying the key fields in force.
pub fn interaction(
prior: &KeyEvent,
k: &[Key],
kt: u64,
n: &[Key],
nt: u64,
anchors: Vec<Said>,
) -> KeyEvent {
finalize_event(KeyEvent::Interaction(InteractionEvent {
v: VERSION_STRING.into(),
d: Said::default(),
i: *prior.controller(),
s: prior.sequence() + 1,
p: prior.said(),
kt: Threshold(kt),
k: public_keys(k),
nt: Threshold(nt),
n: digests(n),
a: anchors,
}))
.unwrap()
}
/// A controller and its log.
#[derive(Clone, Debug)]
pub struct Party {
pub aid: Aid,
pub log: Vec<LoggedEvent>,
}
impl Party {
/// A signer under establishment event `establishment`, signing with
/// `keys` as `(key index, key)`.
pub fn signer(&self, establishment: u64, keys: &[(u64, Key)]) -> Signer {
Signer {
aid: self.aid,
establishment,
keys: keys.iter().map(|(i, k)| (*i, k.signing())).collect(),
}
}
}
/// The 16-byte nonce named `label`.
pub fn nonce(label: &str) -> [u8; 16] {
Said::hash(format!("smart-byte/conformance/v1 nonce {label}").as_bytes()).0[..16]
.try_into()
.unwrap()
}
/// The pack's parties and bytes.
#[derive(Clone, Debug)]
pub struct World {
pub issuer: Party,
pub alice: Party,
pub custodian: Party,
/// The USD byte as issued to Alice.
pub issued: Envelope,
/// The same byte after three transfers, ending with the custodian.
pub transferred: Envelope,
/// An empty opaque byte, issued to the custodian.
pub opaque: Envelope,
}
impl World {
pub fn build() -> World {
let k = |l: &str| Key::named(l);
// The issuer: one key, never rotated.
let issuer_icp = inception(&[k("issuer-0")], 1, &[k("issuer-1")], 1);
let issuer = Party {
aid: *issuer_icp.controller(),
log: vec![LoggedEvent::signed(&issuer_icp, &[(0, k("issuer-0"))])],
};
// Alice: incepted, issued a byte, rotated, then anchored the byte.
let alice_icp = inception(&[k("alice-0")], 1, &[k("alice-1")], 1);
let alice_aid = *alice_icp.controller();
let issued = Envelope::issue(
&issuer.signer(0, &[(0, k("issuer-0"))]),
alice_aid,
nonce("env-001"),
Cargo::usd_micros(100_000_000),
JouleCost {
measured_microjoules: 1200,
estimated_microjoules: 1500,
},
);
let alice_rot = rotation(&alice_icp, &[k("alice-1")], 1, &[k("alice-2")], 1);
let alice_ixn = interaction(
&alice_rot,
&[k("alice-1")],
1,
&[k("alice-2")],
1,
vec![issued.identity()],
);
let alice = Party {
aid: alice_aid,
log: vec![
LoggedEvent::signed(&alice_icp, &[(0, k("alice-0"))]),
LoggedEvent::signed(&alice_rot, &[(0, k("alice-1"))]),
LoggedEvent::signed(&alice_ixn, &[(0, k("alice-1"))]),
],
};
// The custodian: two of three keys sign, before and after rotating.
let c = |n: usize| k(&format!("custodian-{n}"));
let custodian_icp = inception(&[c(0), c(1), c(2)], 2, &[c(3), c(4), c(5)], 2);
let custodian_rot = rotation(
&custodian_icp,
&[c(3), c(4), c(5)],
2,
&[c(6), c(7), c(8)],
2,
);
let custodian = Party {
aid: *custodian_icp.controller(),
log: vec![
LoggedEvent::signed(&custodian_icp, &[(0, c(0)), (1, c(1))]),
LoggedEvent::signed(&custodian_rot, &[(0, c(3)), (2, c(5))]),
],
};
let transferred = issued
.transfer(&alice.signer(0, &[(0, k("alice-0"))]), custodian.aid, 10)
.transfer(&custodian.signer(0, &[(0, c(0)), (2, c(2))]), alice.aid, 11)
.transfer(&alice.signer(1, &[(0, k("alice-1"))]), custodian.aid, 12);
let opaque = Envelope::issue(
&issuer.signer(0, &[(0, k("issuer-0"))]),
custodian.aid,
[0; 16],
Cargo::opaque(Vec::new()),
JouleCost::default(),
);
World {
issuer,
alice,
custodian,
issued,
transferred,
opaque,
}
}
/// The three logs, by the names the pack gives them.
pub fn logs(&self) -> [(&'static str, &Party); 3] {
[
(ISSUER_LOG, &self.issuer),
(ALICE_LOG, &self.alice),
(CUSTODIAN_LOG, &self.custodian),
]
}
/// The key states the three logs establish.
pub fn key_book(&self) -> KeyBook {
let mut book = KeyBook::new();
for (name, party) in self.logs() {
LogVerifier::new()
.verify_encoded(&wire_log(&party.log), &HashMap::new())
.unwrap_or_else(|r| panic!("{name} is refused at event {}: {}", r.index, r.error))
.add_to(&mut book);
}
book
}
}