net-mesh 0.36.0

High-performance, schema-agnostic, backend-agnostic event bus
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//! Fold-layer wire envelope + codec.
//!
//! Defines the on-the-wire [`SignedAnnouncement<P>`] shape (one
//! per fold-channel emission, generic over the per-fold
//! [`FoldKind::Payload`](super::FoldKind::Payload)) and the
//! postcard codec that encodes / decodes / signs / verifies it.
//!
//! The on-wire form is postcard. The signing form — the bytes the
//! Ed25519 signature commits to — is a separate postcard
//! serialization of every field EXCEPT the signature itself, in
//! field-declared order. Keeping the two forms distinct lets the
//! verifier reconstruct the signing bytes from a received
//! envelope without re-encoding the signature into them.
//!
//! Postcard is chosen because it's field-deterministic (struct
//! `#[derive(Serialize)]` emits fields in declaration order,
//! stable across builds), imposes no length-prefix tax on
//! fixed-width fields, and is already a workspace dependency
//! (meshdb, RedEX disk format).
//!
//! The verifier rejects: signatures whose length is not
//! [`SIGNATURE_LEN`]; the all-zero [`placeholder_signature`]
//! sentinel (placeholder envelopes have no business reaching
//! dispatch); and tampered envelopes (the signature won't verify
//! against the recomputed signing bytes). See
//! `docs/internal/plans/SCALING_MULTIFOLD_PLAN.md` § Wire format for the
//! authoritative field semantics and on-wire ordering.

use serde::{de::DeserializeOwned, Deserialize, Serialize};

use super::state::NodeId;
use super::FoldError;

/// Ed25519 signature size in bytes (64). The on-wire signature
/// is a fixed-length slice stored as a `Vec<u8>` so the derived
/// `Serialize`/`Deserialize` impls work without a
/// `serde-big-array` dependency.
pub const SIGNATURE_LEN: usize = 64;

/// Per-envelope metadata grouped to keep the `sign` /
/// `placeholder` constructor signatures narrow. All three fields
/// are wire-envelope members; defaults match the most common
/// publisher pattern (current wall-clock micros, default TTL via
/// `FoldKind::DEFAULT_TTL`, no flag bits set).
#[derive(Debug, Clone, Copy, Default)]
pub struct EnvelopeMeta {
    /// Publisher's wall-clock micros-since-epoch at emission.
    /// Receivers use this for diagnostics, not for ordering —
    /// `generation` is the load-bearing anti-reorder signal.
    pub announced_at: u64,
    /// Per-announcement TTL override. `None` falls through to
    /// [`super::FoldKind::DEFAULT_TTL`].
    pub ttl_secs: Option<u32>,
    /// Bit flags. See [`SignedAnnouncement::flags`] for the
    /// reserved layout.
    pub flags: u8,
}

/// Sentinel signature bytes — all-zero, [`SIGNATURE_LEN`] wide.
/// The verifier rejects this unconditionally; it carries the
/// "envelope is well-formed, signature is a placeholder" marker
/// through tests and synthetic in-process producers that don't
/// have a keypair handy.
pub fn placeholder_signature() -> Vec<u8> {
    vec![0u8; SIGNATURE_LEN]
}

/// One signed announcement on a fold channel. The `P` parameter
/// is the per-fold payload type
/// ([`super::FoldKind::Payload`]).
///
/// Postcard-encoded with field-ordered structs; the signature is
/// Ed25519 over the canonical encoding of every other field.
/// `subnet_id` is intentionally NOT a member here — it lives on
/// the underlying `NetHeader.subnet_id` so the wire envelope and
/// the header don't carry duplicate scoping state.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedAnnouncement<P> {
    /// Fold this announcement targets — [`super::FoldKind::KIND_ID`].
    /// The dispatch layer routes on this and rejects announcements
    /// whose `kind` is unregistered.
    pub kind: u16,
    /// Class within the fold. For capability, this is the
    /// capability-class hash; for routing, a tier identifier;
    /// for reservation, a pool identifier. The fold's channel
    /// name is derived as
    /// `format!("{}{}", FoldKind::CHANNEL_PREFIX, class)` —
    /// subscribers either subscribe to a per-class channel
    /// (default) or to a fold-wide channel and filter on
    /// `class` at the matcher.
    pub class: u64,
    /// Publisher of this announcement. The
    /// [`SignedAnnouncement::signature`] commits to the
    /// publisher's cryptographic identity; the routing-layer
    /// `node_id` here is what folds index against.
    pub node_id: NodeId,
    /// Monotonic per-`(node_id, kind, class)` counter. The
    /// default [`super::FoldKind::merge`] orders applies on
    /// this; the publisher persists it across restarts so the
    /// sequence never goes backward. `0` is reserved as an
    /// "uninitialized" sentinel — see
    /// [`super::state::FoldError::InvalidGeneration`].
    pub generation: u64,
    /// Publisher's local micros-since-epoch at emission. Used by
    /// metrics + diagnostics; NOT consulted for ordering — the
    /// `generation` field is the load-bearing ordering signal.
    pub announced_at: u64,
    /// Per-announcement TTL override. `None` falls through to
    /// [`super::FoldKind::DEFAULT_TTL`].
    pub ttl_secs: Option<u32>,
    /// Bit flags. The reserved layout is:
    ///
    /// - bit 0: join (new membership).
    /// - bit 1: leave (publisher is voluntarily releasing the key).
    /// - bit 2: update (in-place mutation of existing entry).
    /// - bits 3..7: reserved for future use, must be zero.
    ///
    /// Folds free to ignore flags they don't recognize.
    pub flags: u8,
    /// Domain-specific payload — the actual data the fold cares
    /// about. Owned, not borrowed: the runtime moves it into the
    /// [`super::state::FoldEntry::payload`] field on accept.
    pub payload: P,
    /// Ed25519 signature over the canonical encoding of every
    /// other field. Stored as a `Vec<u8>` of length
    /// [`SIGNATURE_LEN`] so the derived serde impls work without
    /// a fixed-array codec dependency.
    pub signature: Vec<u8>,
}

impl<P> SignedAnnouncement<P> {
    /// Construct an announcement with the
    /// [`placeholder_signature`] sentinel. Tests and in-process
    /// producers that don't have a keypair handy use this; the
    /// dispatch layer rejects placeholder-stamped envelopes on
    /// the slow path.
    pub fn placeholder(
        kind: u16,
        class: u64,
        node_id: NodeId,
        generation: u64,
        meta: EnvelopeMeta,
        payload: P,
    ) -> Self {
        Self {
            kind,
            class,
            node_id,
            generation,
            announced_at: meta.announced_at,
            ttl_secs: meta.ttl_secs,
            flags: meta.flags,
            payload,
            signature: placeholder_signature(),
        }
    }
}

/// Errors the wire codec surfaces. The dispatch layer routes them
/// to logs + metrics; the caller of [`SignedAnnouncement::decode`]
/// sees them via `Result`.
#[derive(Debug, thiserror::Error)]
pub enum WireError {
    /// Postcard refused to decode the byte buffer (truncated,
    /// schema-incompatible, etc.).
    #[error("wire decode failed: {0}")]
    Decode(#[from] postcard::Error),

    /// The signature on a decoded envelope has the wrong length.
    /// Production signatures are exactly [`SIGNATURE_LEN`] bytes
    /// (Ed25519); anything else is malformed by construction.
    #[error("signature length {0} != expected {expected}", expected = SIGNATURE_LEN)]
    BadSignatureLength(usize),

    /// Decoded envelope carries the [`placeholder_signature`]
    /// sentinel. Dispatch rejects this — the envelope was
    /// constructed without signing, so verification would be
    /// vacuous and the `node_id` claim is unauthenticated.
    #[error("placeholder (all-zero) signature reached the dispatch path")]
    PlaceholderSignature,

    /// Underlying Ed25519 verifier rejected the signature: the
    /// envelope was tampered with, or claims a publisher whose
    /// public key doesn't match the signing key.
    #[error("signature verification failed")]
    InvalidSignature,

    /// The publisher's `EntityId` bytes aren't a valid Ed25519
    /// public key (not on-curve / malformed encoding). Returned
    /// when the dispatch layer is handed an `EntityId` that
    /// didn't round-trip through a known publisher.
    #[error("publisher public key bytes are not a valid Ed25519 point")]
    InvalidPublicKey,

    /// The envelope's `node_id` claim doesn't match the node id
    /// derived from the signature-verified publisher. A valid
    /// signature only proves the publisher signed *these bytes* —
    /// without this check a peer could sign an envelope claiming
    /// any other node's id, and `Fold::apply` keys all state on
    /// `node_id`, so the forged entry would land in the victim's
    /// capability/reservation state (cross-node injection). The
    /// publisher *is* the node; the two must agree.
    #[error("envelope node_id {claimed} does not match publisher node_id {publisher}")]
    NodeIdMismatch {
        /// `node_id` field decoded from the envelope.
        claimed: NodeId,
        /// Node id derived from the verified publisher `EntityId`.
        publisher: NodeId,
    },

    /// The decoded envelope's `kind` field doesn't match the
    /// fold it was dispatched into. The dispatch layer catches
    /// this BEFORE handing the envelope to `Fold::apply` so a
    /// crossed-channel publish doesn't pollute the wrong fold.
    #[error("envelope kind {got:#06x} does not match expected {expected:#06x}")]
    KindMismatch {
        /// Kind field decoded from the envelope.
        got: u16,
        /// Kind the dispatch path was expecting.
        expected: u16,
    },

    /// An [`FoldError`] surfaced during the post-verify apply.
    /// Wraps the underlying error so callers can pattern-match
    /// on the apply-side failure modes.
    #[error("apply rejected: {0}")]
    Apply(#[from] FoldError),
}

/// Canonical bytes the Ed25519 signature commits to.
///
/// Postcard-encodes every field EXCEPT `signature` in the order
/// they appear on [`SignedAnnouncement`]. Field order is wire-
/// load-bearing: any future field addition appends to the end
/// and bumps the `kind` reservation (a new `KIND_ID` means a
/// new fold, which gets a fresh canonical ordering). Existing
/// folds never reorder.
///
/// The borrow on `payload` avoids cloning the (potentially large)
/// per-fold payload — the canonical bytes are computed inside
/// `sign` and `verify`, both of which can hold the reference for
/// the duration of the postcard call.
pub(super) fn signing_bytes<P: Serialize>(
    kind: u16,
    class: u64,
    node_id: NodeId,
    generation: u64,
    meta: &EnvelopeMeta,
    payload: &P,
) -> Result<Vec<u8>, postcard::Error> {
    // A separate struct rather than a tuple so postcard's serde
    // derive emits length-tagged fields in the right order. Field
    // ORDER here is load-bearing — it MUST match the
    // `SignedAnnouncement` field declaration order.
    #[derive(Serialize)]
    struct ToSign<'a, P: Serialize> {
        kind: u16,
        class: u64,
        node_id: NodeId,
        generation: u64,
        announced_at: u64,
        ttl_secs: Option<u32>,
        flags: u8,
        payload: &'a P,
    }
    postcard::to_allocvec(&ToSign {
        kind,
        class,
        node_id,
        generation,
        announced_at: meta.announced_at,
        ttl_secs: meta.ttl_secs,
        flags: meta.flags,
        payload,
    })
}

impl<P: Serialize + DeserializeOwned> SignedAnnouncement<P> {
    /// Construct + sign an announcement with the supplied
    /// keypair. The signature commits to every other field via
    /// the canonical `signing_bytes` byte layout (private to
    /// this module).
    pub fn sign(
        keypair: &crate::adapter::net::identity::EntityKeypair,
        kind: u16,
        class: u64,
        node_id: NodeId,
        generation: u64,
        meta: EnvelopeMeta,
        payload: P,
    ) -> Result<Self, WireError> {
        let bytes = signing_bytes(kind, class, node_id, generation, &meta, &payload)?;
        let sig = keypair.sign(&bytes);
        Ok(Self {
            kind,
            class,
            node_id,
            generation,
            announced_at: meta.announced_at,
            ttl_secs: meta.ttl_secs,
            flags: meta.flags,
            payload,
            signature: sig.to_bytes().to_vec(),
        })
    }

    /// Verify the signature against a publisher's
    /// [`EntityId`](crate::adapter::net::identity::EntityId).
    ///
    /// Rejects:
    /// - Wrong-length signatures
    ///   ([`WireError::BadSignatureLength`])
    /// - The placeholder sentinel
    ///   ([`WireError::PlaceholderSignature`])
    /// - Invalid publisher public keys
    ///   ([`WireError::InvalidPublicKey`])
    /// - Tampered envelopes
    ///   ([`WireError::InvalidSignature`])
    pub fn verify(
        &self,
        publisher: &crate::adapter::net::identity::EntityId,
    ) -> Result<(), WireError> {
        if self.signature.len() != SIGNATURE_LEN {
            return Err(WireError::BadSignatureLength(self.signature.len()));
        }
        // Equivalent to `self.signature == placeholder_signature()`,
        // without allocating a throwaway 64-byte `Vec` on every
        // verify (PERF_AUDIT_2026_07_31_GANG_SCHEDULER §6). The
        // length check above already ran, so this can only see a
        // SIGNATURE_LEN-wide slice — an empty signature (which
        // `all` would vacuously accept) is unreachable here.
        if self.signature.iter().all(|&b| b == 0) {
            return Err(WireError::PlaceholderSignature);
        }

        let mut sig_bytes = [0u8; SIGNATURE_LEN];
        sig_bytes.copy_from_slice(&self.signature);
        let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);

        let meta = EnvelopeMeta {
            announced_at: self.announced_at,
            ttl_secs: self.ttl_secs,
            flags: self.flags,
        };
        let bytes = signing_bytes(
            self.kind,
            self.class,
            self.node_id,
            self.generation,
            &meta,
            &self.payload,
        )?;

        publisher.verify(&bytes, &sig).map_err(|e| match e {
            crate::adapter::net::identity::EntityError::InvalidPublicKey => {
                WireError::InvalidPublicKey
            }
            _ => WireError::InvalidSignature,
        })?;

        // Bind the envelope's `node_id` claim to the publisher that
        // signed it. A valid signature only proves the publisher
        // signed *these exact bytes* — it says nothing about whether
        // the embedded `node_id` is the publisher's own. `Fold::apply`
        // keys all state on `node_id`, so accepting a mismatched claim
        // lets any peer plant entries under another node's key
        // (capability injection / reservation hijack). The publisher
        // IS the node; require the two to agree. `node_id()` is a
        // BLAKE2s-MAC over the pubkey, not a field read, so derive it
        // once and reuse it for the error too.
        let publisher_node = publisher.node_id();
        if self.node_id != publisher_node {
            return Err(WireError::NodeIdMismatch {
                claimed: self.node_id,
                publisher: publisher_node,
            });
        }

        Ok(())
    }

    /// Encode the full envelope to wire bytes via postcard.
    pub fn encode(&self) -> Result<Vec<u8>, WireError> {
        postcard::to_allocvec(self).map_err(WireError::Decode)
    }

    /// Decode an envelope from wire bytes. Does NOT verify the
    /// signature — callers route through
    /// [`Self::decode_and_verify`] when they have the publisher's
    /// public key, or call [`Self::verify`] separately. Pure-
    /// decode is exposed for diagnostic tooling that wants to
    /// inspect malformed envelopes.
    pub fn decode(bytes: &[u8]) -> Result<Self, WireError> {
        postcard::from_bytes(bytes).map_err(WireError::Decode)
    }

    /// One-shot decode + verify. The dispatch layer's hot path
    /// goes through here.
    pub fn decode_and_verify(
        bytes: &[u8],
        publisher: &crate::adapter::net::identity::EntityId,
    ) -> Result<Self, WireError> {
        let ann = Self::decode(bytes)?;
        ann.verify(publisher)?;
        Ok(ann)
    }
}