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]>
118 lines
3.9 KiB
Rust
118 lines
3.9 KiB
Rust
//! Request-response: A asks B for an envelope by SAID; B serves it
|
|
//! directly (no DHT involved).
|
|
|
|
use std::time::Duration;
|
|
|
|
use libp2p::futures::StreamExt;
|
|
use libp2p::request_response::{self, Message};
|
|
use libp2p::swarm::SwarmEvent;
|
|
use smart_byte_core::{Aid, Cargo, Envelope, JouleCost, Said, Signer, SigningKey};
|
|
use smart_byte_libp2p::{
|
|
EnvelopeResponse, Libp2pNode, NodeConfig, SmartByteBehaviourEvent,
|
|
behaviour::default_gossipsub_config,
|
|
};
|
|
|
|
fn fixture_envelope() -> Envelope {
|
|
let issuer = Signer::single(
|
|
Aid::of_inception(Said::hash(b"req-resp")),
|
|
0,
|
|
SigningKey::from_bytes(&[0x42; 32]),
|
|
);
|
|
Envelope::issue(
|
|
&issuer,
|
|
Aid::of_inception(Said::hash(b"holder")),
|
|
[1; 16],
|
|
Cargo::opaque(b"reqresp-payload".to_vec()),
|
|
JouleCost::measured(3),
|
|
)
|
|
}
|
|
|
|
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 request_response_fetch() {
|
|
let mut node_a = spawn_node().await;
|
|
let mut node_b = spawn_node().await;
|
|
|
|
let b_addr = node_b
|
|
.listen_addrs
|
|
.first()
|
|
.cloned()
|
|
.expect("b listening");
|
|
|
|
// A dials B.
|
|
node_a.swarm.dial(b_addr).expect("dial");
|
|
|
|
// Warmup so the connection is fully established.
|
|
let warmup = tokio::time::Instant::now() + Duration::from_secs(2);
|
|
while tokio::time::Instant::now() < warmup {
|
|
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();
|
|
let env_for_b = env.clone();
|
|
|
|
// Send the request from A. We then concurrently:
|
|
// - drive A's swarm and read the response.
|
|
// - drive B's swarm and serve the response when the inbound
|
|
// request arrives.
|
|
let req = smart_byte_libp2p::EnvelopeRequest { said: env.said() };
|
|
let request_id = node_a
|
|
.swarm
|
|
.behaviour_mut()
|
|
.req_resp
|
|
.send_request(&node_b.local_peer_id, req);
|
|
|
|
let result = tokio::time::timeout(Duration::from_secs(10), async {
|
|
loop {
|
|
tokio::select! {
|
|
ev_b = node_b.swarm.select_next_some() => {
|
|
if let SwarmEvent::Behaviour(SmartByteBehaviourEvent::ReqResp(
|
|
request_response::Event::Message {
|
|
message: Message::Request { request: _, channel, .. },
|
|
..
|
|
})) = ev_b
|
|
{
|
|
let _ = node_b
|
|
.swarm
|
|
.behaviour_mut()
|
|
.req_resp
|
|
.send_response(channel, EnvelopeResponse { envelope: Some(env_for_b.clone()) });
|
|
}
|
|
}
|
|
ev_a = node_a.swarm.select_next_some() => {
|
|
if let SwarmEvent::Behaviour(SmartByteBehaviourEvent::ReqResp(
|
|
request_response::Event::Message {
|
|
message: Message::Response { request_id: rid, response },
|
|
..
|
|
})) = ev_a
|
|
&& rid == request_id
|
|
{
|
|
return response;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.await
|
|
.expect("not timed out");
|
|
|
|
let env_back = result.envelope.expect("envelope returned");
|
|
assert_eq!(env_back.said(), env.said());
|
|
env_back.check_saids().expect("SAIDs check");
|
|
}
|