From 3b2dd318477eb268e6821fb000aeadfe60d85987 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 9 Aug 2026 05:31:05 +0200 Subject: feat: Phase 6 complete — chat, multi-group, federation, replication, webcrypto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 6.1 Double Ratchet (meshbay_common/ratchet.py): Forward secrecy, break-in recovery, out-of-order delivery. Signal-spec KDF_RK/KDF_CK via HKDF-SHA256. 11/11 tests. 6.2 Multi-group node (config.py): [[groups]] TOML array, per-group ports, back-compat [group]. 6.3 MHP federation persistence (db/models.py FederatedGroup + SwarmSource): receive_directory() now persists to federated_groups table. list_public_groups() includes federated results with source attribution. 6.4 Content replication (node/replication.py + hub SwarmSource): ContentReplicator: fetch-index, download, hash-verify, register-swarm. Hub: POST /v1/swarm/register, GET /v1/swarm/{hash} for multi-source. 6.5 Browser private group (webcrypto.py + static/crypto.js): AES-256-GCM variant of GEK for WebCrypto-compatible groups. crypto.js: SubtleCrypto importGEK + deriveChunkKey + decryptChunk. Keys distinct from ChaCha20 via :aes HKDF info suffix. 4/4 tests. 74/74 tests total. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- packages/meshbay-common/tests/test_webcrypto.py | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 packages/meshbay-common/tests/test_webcrypto.py (limited to 'packages/meshbay-common/tests') diff --git a/packages/meshbay-common/tests/test_webcrypto.py b/packages/meshbay-common/tests/test_webcrypto.py new file mode 100644 index 0000000..25bcd5c --- /dev/null +++ b/packages/meshbay-common/tests/test_webcrypto.py @@ -0,0 +1,46 @@ +"""Tests for AES-256-GCM webcrypto variant.""" + +import os +import pytest +import blake3 +from meshbay_common.crypto import generate_gek +from meshbay_common.webcrypto import chunk_key_aes, encrypt_chunk_aes, decrypt_chunk_aes + + +def test_aes_roundtrip(): + gek = generate_gek() + data = os.urandom(1024 * 1024) # 1 MB + fh = blake3.blake3(data).digest() + key = chunk_key_aes(gek, fh, 0) + nonce, ct = encrypt_chunk_aes(key, data) + assert decrypt_chunk_aes(key, nonce, ct) == data + + +def test_aes_key_distinct_from_chacha_key(): + """AES and ChaCha20 keys for the same chunk must differ.""" + from meshbay_common.crypto import chunk_key as chacha_key + gek = generate_gek() + data = os.urandom(100) + fh = blake3.blake3(data).digest() + aes_k = chunk_key_aes(gek, fh, 0) + chacha_k = chacha_key(gek, fh, 0) + assert aes_k != chacha_k + + +def test_aes_wrong_key_rejected(): + gek = generate_gek() + data = b"private content" + fh = blake3.blake3(data).digest() + key = chunk_key_aes(gek, fh, 0) + nonce, ct = encrypt_chunk_aes(key, data) + wrong_key = chunk_key_aes(generate_gek(), fh, 0) + with pytest.raises(Exception): + decrypt_chunk_aes(wrong_key, nonce, ct) + + +def test_aes_chunk_keys_unique_per_chunk(): + gek = generate_gek() + data = os.urandom(32) + fh = blake3.blake3(data).digest() + keys = {chunk_key_aes(gek, fh, i) for i in range(5)} + assert len(keys) == 5 # all distinct -- cgit v1.2.3