aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-28 16:24:12 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-28 16:24:12 +0200
commita4b36e5fe31cb671a4cbbdaad75746cd68b601fe (patch)
tree48f9f4fb76f91e063c436ace344d5220611de8b5 /packages/meshbay-node/src
parent5330896c0e8023053d4cd15961b2ae0482686ca6 (diff)
downloadmeshbay-a4b36e5fe31cb671a4cbbdaad75746cd68b601fe.tar.gz
refactor: remove the unused GroupIndex.serialize chain
serialize/deserialize had no production caller, and took with them the per-chunk signature, the ChaCha20 cipher variant and the zstandard dependency. Key derivations are unchanged. Docs corrected, including design §4.3's claim that chunks are compressed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src')
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/group_index.py151
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/wire.py9
2 files changed, 14 insertions, 146 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/group_index.py b/packages/meshbay-node/src/meshbay_node/indexer/group_index.py
index 9e4e780..85bc13e 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/group_index.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/group_index.py
@@ -1,67 +1,41 @@
"""
-Mesh Group Index — encrypted file listing for a group.
+Mesh Group Index — the file listing for one group, and the deltas between versions.
-Wire format (private group):
- msgpack({entries, version, group_id}) → zstd compress → GEK ChaCha20 encrypt → sign
-
-Wire format (public group):
- msgpack({entries, version, group_id}) → sign (no encryption)
+What travels on the wire is built from it by `transport/wire.py` and sealed under
+the group key (`meshbay_common.groupbox`); this module holds no encoding of its own.
Delta format:
{base_version, version, additions: [...], deletions: [id, ...]}
"""
-import base64
import logging
-from dataclasses import asdict, dataclass, field
+from dataclasses import dataclass, field
-import blake3
-import msgpack
-import zstandard as zstd
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-from meshbay_common.crypto import (
- pk_to_b64,
- sign_chunk,
- verify_chunk_signature,
-)
from meshbay_common.protocol import IndexDelta, IndexEntry
-from meshbay_common.webcrypto import (
- chunk_key_aes as derive_chunk_key,
-)
-from meshbay_common.webcrypto import (
- decrypt_chunk_aes as decrypt_chunk,
-)
-from meshbay_common.webcrypto import (
- encrypt_chunk_aes as encrypt_chunk,
-)
log = logging.getLogger(__name__)
-ZSTD_LEVEL = 3 # fast compression
-INDEX_CHUNK = 0 # the index itself is treated as chunk 0 of a virtual "index file"
-
@dataclass
class GroupIndex:
"""
- Encrypted, signed Mesh Group Index for one group.
+ Mesh Group Index for one group.
Usage:
idx = GroupIndex(group_id="...", sk_node=sk, gek=gek_bytes)
idx.add_entry(entry)
- wire_bytes = idx.serialize() # for sending to members
- recovered = GroupIndex.deserialize(wire_bytes, sk_node=sk, gek=gek_bytes)
"""
group_id: str
sk_node: Ed25519PrivateKey
gek: bytes | None = None # None → public group (no encryption)
version: int = 1
- # The group's roots and whether each is readable right now. Travels inside
- # the encrypted payload because it names the operator's directories, and a
- # member needs it to tell "temporarily unavailable" from "deleted" — a
- # distinction the entries alone cannot carry, since an unavailable root's
- # files are still listed. Absent in an index written before roots existed.
+ # The group's roots and whether each is readable right now, as the indexer
+ # last described them. A member needs this to tell "temporarily unavailable"
+ # from "deleted" — a distinction the entries alone cannot carry, since an
+ # unavailable root's files are still listed. What members receive is built
+ # from the RootSet by `transport/wire.py`, not from this copy.
roots: list = field(default_factory=list)
_entries: dict = field(default_factory=dict, repr=False) # id → IndexEntry
@@ -101,111 +75,6 @@ class GroupIndex:
idx._entries = dict(entries_by_id)
return idx
- # ── Serialisation ─────────────────────────────────────────────────────────
-
- def serialize(self) -> bytes:
- """
- Produce a signed index envelope:
- msgpack → zstd → [GEK encrypt if private] → sign → length-prefixed envelope
-
- **This is not an MNP message.** It was the payload of `index_sync` on the QUIC
- transport, while WebRTC sent plain entries under the same type — one message
- type, two encodings (2026-09-03). Both transports now build `index_sync` from
- `transport/wire.py`. This stays as a correct at-rest/interchange format, and
- as the only thing that signs and encrypts a whole index; read it as that, not
- as a wire contract.
- """
- payload = msgpack.packb({
- "group_id": self.group_id,
- "version": self.version,
- "roots": list(self.roots),
- "entries": [asdict(e) for e in self.entries],
- }, use_bin_type=True)
-
- compressed = zstd.compress(payload, level=ZSTD_LEVEL)
-
- if self.gek is not None:
- # Private group: encrypt with GEK-derived key
- idx_hash = blake3.blake3(compressed).digest()
- ckey = derive_chunk_key(self.gek, idx_hash, INDEX_CHUNK)
- nonce, ct = encrypt_chunk(ckey, compressed)
- sig = sign_chunk(self.sk_node, INDEX_CHUNK, nonce, blake3.blake3(ct).digest())
- envelope = msgpack.packb({
- "type": "index",
- "encrypted": True,
- "version": self.version,
- "group_id": self.group_id,
- "idx_hash_b64": base64.b64encode(idx_hash).decode(),
- "nonce_b64": base64.b64encode(nonce).decode(),
- "ct_b64": base64.b64encode(ct).decode(),
- "sig_b64": base64.b64encode(sig).decode(),
- "pk_node_b64": pk_to_b64(self.sk_node.public_key()),
- }, use_bin_type=True)
- else:
- # Public group: just sign the compressed payload
- payload_hash = blake3.blake3(compressed).digest()
- sig = sign_chunk(self.sk_node, INDEX_CHUNK,
- bytes(12), # zero nonce for plaintext
- payload_hash)
- envelope = msgpack.packb({
- "type": "index",
- "encrypted": False,
- "version": self.version,
- "group_id": self.group_id,
- "data_b64": base64.b64encode(compressed).decode(),
- "hash_b64": base64.b64encode(payload_hash).decode(),
- "sig_b64": base64.b64encode(sig).decode(),
- "pk_node_b64": pk_to_b64(self.sk_node.public_key()),
- }, use_bin_type=True)
-
- return envelope
-
- @classmethod
- def deserialize(
- cls,
- data: bytes,
- sk_node: Ed25519PrivateKey,
- gek: bytes | None = None,
- ) -> "GroupIndex":
- """Deserialize, verify signature, and decrypt (if private)."""
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
-
- envelope = msgpack.unpackb(data, raw=False)
-
- pk_node_raw = base64.b64decode(envelope["pk_node_b64"])
- pk_node = Ed25519PublicKey.from_public_bytes(pk_node_raw)
- sig = base64.b64decode(envelope["sig_b64"])
-
- if envelope["encrypted"]:
- if gek is None:
- raise ValueError("GEK required to decrypt private group index")
- ct = base64.b64decode(envelope["ct_b64"])
- nonce = base64.b64decode(envelope["nonce_b64"])
- ct_hash = blake3.blake3(ct).digest()
- verify_chunk_signature(pk_node, INDEX_CHUNK, nonce, ct_hash, sig)
-
- idx_hash = base64.b64decode(envelope["idx_hash_b64"])
- ckey = derive_chunk_key(gek, idx_hash, INDEX_CHUNK)
- compressed = decrypt_chunk(ckey, nonce, ct)
- else:
- compressed = base64.b64decode(envelope["data_b64"])
- payload_hash = base64.b64decode(envelope["hash_b64"])
- verify_chunk_signature(pk_node, INDEX_CHUNK, bytes(12), payload_hash, sig)
-
- payload = msgpack.unpackb(zstd.decompress(compressed), raw=False)
- idx = cls(
- group_id=payload["group_id"],
- sk_node=sk_node,
- gek=gek,
- version=payload["version"],
- # Absent from an index written before roots existed; an empty list
- # reads as "nothing known about availability", not "no roots".
- roots=payload.get("roots") or [],
- )
- for e in payload["entries"]:
- idx.add_entry(IndexEntry(**e))
- return idx
-
# ── Delta ─────────────────────────────────────────────────────────────────
def diff(self, previous: "GroupIndex") -> IndexDelta:
diff --git a/packages/meshbay-node/src/meshbay_node/transport/wire.py b/packages/meshbay-node/src/meshbay_node/transport/wire.py
index 6986b01..1f9c925 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/wire.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/wire.py
@@ -12,11 +12,10 @@ envelope produced by `GroupIndex.serialize()`. Same message type, two encodings,
consumer each and nothing asserting they matched. Same failure mode as the two
`file_chunk` encoders, and the same fix: one builder, used by both.
-`GroupIndex.serialize()`/`deserialize()` are unchanged and still tested — they remain
-a correct signed index envelope — but they no longer describe any MNP message. Read
-them as an at-rest/interchange format, not as a wire contract. It is also not a
-candidate for reuse below: it compresses with zstd, which no browser can decompress
-(`DecompressionStream` offers gzip and deflate only).
+That envelope, and `GroupIndex.serialize()`/`deserialize()` which produced it, are
+gone: once both transports built `index_sync` here, nothing stored or exchanged it.
+It was never a candidate for reuse below either — it compressed with zstd, which no
+browser can decompress (`DecompressionStream` offers gzip and deflate only).
Since MNP 1.0 both messages carry their payload **sealed under a GEK-derived subkey**
(`meshbay_common.groupbox`). Only the routing fields — `type`, `v`, `group_id` — stay