Guides
Implementation Guide
The handoff document. Audience: whoever (human or coding agent) implements M1–M5. The design documents say what; this says how to build it without drifting. When this guide and a design doc conflict, the design doc wins and the conflict is a bug — file it.
1. Ground rules
- No design changes in implementation PRs. A change to anything specified in docs
02–07 requires an ADR first (template:
docs/adr/template.md). - Crate boundaries are law. The "must never" column in
03 — Architecture §2 is enforced by review and by dependency
direction — e.g.
kult-crypto'sCargo.tomlhas no I/O crates, period. - Crypto code standards (in
kult-crypto,kult-protocol):#![forbid(unsafe_code)],#![deny(missing_docs)].- Every secret in a
zeroize::Zeroizing/ZeroizeOnDroptype; noDebug/Displayon secret types; no secret ever in a log or error message. - Constant-time comparison (
subtle) for every tag/token/key equality. - No panics on untrusted input: parsers return
Result, fuzzers prove it. - Dependencies pinned via lockfile;
cargo-denygate (licenses, advisories, dupes); no git dependencies.
- Errors are honest: failure states surface to the delivery engine and UI truthfully
(
queued/sent/delivered/failed), never faked. - Every milestone lands with its tests as defined in the acceptance criteria of 08 — Roadmap; CI = fmt + clippy (deny warnings) + tests + fuzz smoke (60 s per target) + cargo-deny.
2. Build order
Strictly: kult-crypto → kult-protocol + kult-store (parallel-safe) → kult-node
(with sneakernet transport) → libp2p transport → Meshtastic transport → kult-ffi → apps.
Each step compiles, tests, and demos without the steps after it.
3. API sketches (normative shape, not final signatures)
3.1 kult-crypto
pub struct Identity { /* IK_ed25519 + IK_x25519, cross-signed */ }
impl Identity {
pub fn generate(rng: &mut impl CryptoRngCore) -> Self;
pub fn public(&self) -> IdentityPublic; // → kult address
pub fn sign_prekeys(&self, spk: &X25519Public, pqspk: &MlKemPublic) -> PrekeySignatures;
}
pub struct PrekeyBundle { /* per 04 §3, self-authenticating */ }
impl PrekeyBundle { pub fn verify(&self) -> Result<VerifiedBundle, BundleError>; }
// Handshake (04 §3)
pub fn initiate(me: &Identity, bundle: &VerifiedBundle, rng: ...)
-> (Session, InitialMessage);
pub fn respond(me: &Identity, prekeys: &PrekeyStore, msg: &InitialMessage)
-> Result<Session, HandshakeError>;
// Double Ratchet (04 §4) — opaque, serializable-encrypted, zeroizing
impl Session {
pub fn encrypt(&mut self, plaintext: &[u8], ad: &[u8]) -> RatchetMessage;
pub fn decrypt(&mut self, msg: &RatchetMessage, ad: &[u8])
-> Result<Vec<u8>, RatchetError>; // enforces MAX_SKIP=1000, store cap 2000/TTL 30d
pub fn seal_state(&self, sk: &StorageKey) -> SealedState;
pub fn unseal_state(sealed: &SealedState, sk: &StorageKey) -> Result<Self, _>;
}
pub fn safety_number(a: &IdentityPublic, b: &IdentityPublic) -> SafetyNumber; // 04 §93.2 kult-protocol
pub struct Envelope { /* 04 §5 wire format */ }
impl Envelope {
pub fn encode(&self) -> Bytes; // fixed layout, LE
pub fn decode(b: &[u8]) -> Result<Self, CodecError>; // fuzz target #1
}
pub fn pad(plaintext: &[u8]) -> Padded; // buckets per 04 §5
pub fn fragment(env: &Envelope, mtu: usize) -> Vec<Envelope>; // type 0x04
pub struct Reassembler { /* 24h window, per-peer caps, NACK generation (05 §4.2) */ }
pub fn delivery_token(k_mailbox: &MailboxKey, epoch: Epoch) -> Token; // 04 §73.3 kult-transport
The trait from 05 — Transports §1, plus:
pub struct SneakernetTransport; // .kkb bundles — implement FIRST (M2): no networking,
// exercises the full envelope path end-to-end
pub struct Libp2pTransport; // M3: QUIC/TCP, Kademlia records, relay-v2 mailboxes
pub struct MeshtasticTransport; // M4: BLE/serial protobuf client, private PortNum,
// runtime MTU from radio config, duty-cycle accountingFor Meshtastic: use the published protobuf definitions via a generated client; do not hand-roll the serial framing. Airtime budget module is its own reviewed unit.
3.4 kult-store
pub struct Store { /* SQLite; every blob AEAD-sealed per 07 §2 */ }
// open(path, kek) → unwraps SK; domain key per table via HKDF
// messages / sessions / contacts / queue / media DAOs; no protocol logic3.5 kult-node
pub struct Node { /* composes store + transports + sessions */ }
// event-driven: Command (send, add_contact, accept_request, export_bundle…)
// → Event (message, receipt, key_changed, transport_status…)
// delivery engine: per-message state machine queued→sent→delivered, multipath,
// dedup by message id, retry with per-transport backoffkult-ffi exposes exactly Node's command/event API via UniFFI — nothing more.
4. Testing strategy (beyond per-milestone acceptance)
- KATs: primitive test vectors vendored under
crates/kult-crypto/tests/vectors/. - Property tests (
proptest): ratchet loss/reorder/dup within bounds ⇒ decrypts; outside bounds ⇒ typed failure. Padding round-trips. Fragment/reassemble = identity. - Fuzz targets (
cargo-fuzz): envelope decode, bundle decode, handshake message decode, sealed-state unseal. - Simulation harness (M3+): in-process multi-node network with scripted link conditions (latency, loss, partitions, MTU) — deterministic seed, replayable failures. This harness is how store-and-forward, NACK, and bridging logic get tested without radios on the desk.
- Hardware-in-loop (M4): two USB Meshtastic radios in CI-adjacent nightly job.
5. Review gates
Every PR: CI green + one review. Additionally:
| Touched | Extra gate |
|---|---|
kult-crypto |
Second reviewer; diff against spec section cited in PR description |
| Wire formats (envelope, bundle, tokens) | Version-bump check + fuzz corpus updated |
| Dependencies | cargo-deny diff reviewed; new crypto deps need an ADR |
| Storage schema | Migration + "copied-file leakage" checklist from 07 §2 |
6. Definition of done (any milestone)
Acceptance criteria in 08 — Roadmap demonstrably met, CI green, docs
updated where behavior is user-visible, no TODO without a tracking issue, and the demo
described in the milestone runs from a fresh clone with documented commands.