open-standards/smart-byte/smart-byte-rs/crates/smart-byte-libp2p/tests/two_node_pubsub.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

97 lines
3 KiB
Rust

//! Two localhost nodes: A publishes on topic T; B receives.
use std::time::Duration;
use libp2p::futures::StreamExt;
use libp2p::swarm::SwarmEvent;
use smart_byte_core::{Aid, Cargo, Envelope, JouleCost, Said, Signer, SigningKey};
use smart_byte_libp2p::{
Libp2pNode, NodeConfig,
behaviour::default_gossipsub_config,
transport::{next_envelope_on, publish_envelope, subscribe_topic},
};
fn fixture_envelope(seed: &str) -> Envelope {
let issuer = Signer::single(
Aid::of_inception(Said::hash(seed.as_bytes())),
0,
SigningKey::from_bytes(&[0x42; 32]),
);
Envelope::issue(
&issuer,
Aid::of_inception(Said::hash(b"holder")),
[1; 16],
Cargo::opaque(seed.as_bytes().to_vec()),
JouleCost::measured(11),
)
}
async fn spawn_node() -> Libp2pNode {
let cfg = NodeConfig {
listen_addrs: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
bootstrap_peers: vec![],
identity: libp2p::identity::Keypair::generate_ed25519(),
enable_mdns: false,
enable_relay: false,
enable_dcutr: false,
gossipsub_config: default_gossipsub_config(),
};
Libp2pNode::new(cfg).await.expect("spawn node")
}
#[tokio::test]
async fn two_nodes_pubsub_envelope() {
let mut node_a = spawn_node().await;
let mut node_b = spawn_node().await;
let a_addr = node_a
.listen_addrs
.first()
.cloned()
.expect("a listening");
// B dials A so they form a connection.
node_b
.swarm
.dial(a_addr)
.expect("dial succeeds");
// Drive both swarms briefly so identify/gossipsub mesh has a chance
// to form before we publish.
let topic = "smart-byte/test/envelopes";
subscribe_topic(&mut node_a, topic).expect("subscribe a");
subscribe_topic(&mut node_b, topic).expect("subscribe b");
let warmup_until = tokio::time::Instant::now() + Duration::from_secs(3);
while tokio::time::Instant::now() < warmup_until {
tokio::select! {
_ = node_a.swarm.select_next_some() => {}
_ = node_b.swarm.select_next_some() => {}
_ = tokio::time::sleep(Duration::from_millis(50)) => {}
}
}
let env = fixture_envelope("two-nodes");
publish_envelope(&mut node_a, topic, &env)
.await
.expect("publish");
// Keep driving A while we wait for B to receive.
let received = tokio::time::timeout(Duration::from_secs(10), async {
loop {
tokio::select! {
ev = node_a.swarm.select_next_some() => {
let _: SwarmEvent<_> = ev;
}
got = next_envelope_on(&mut node_b, topic, Duration::from_secs(8)) => {
return got;
}
}
}
})
.await
.expect("not timed out")
.expect("received envelope");
let (received_env, from_peer) = received;
assert_eq!(received_env.said(), env.said());
assert_eq!(from_peer, node_a.local_peer_id);
}