From 91297944791a36f30302ef8c86dd69ebeb177671 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 30 Sep 2026 15:06:14 +0200 Subject: feat: bundles sealed per node under the passphrase and the hub's pepper The session key is M = HKDF(Argon2(passphrase) || pepper, account id); each node's bundle key and the playlist key derive from it. Bundles are MBK3, bound to account and node; MBK1/MBK2 are refused by name, never replaced silently. Playlists move to key v2 and are re-sealed over unreadable node copies. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-hub/tests/test_spa_ordering.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'packages/meshbay-hub/tests/test_spa_ordering.py') diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py index fdedaf8..6f28aaa 100644 --- a/packages/meshbay-hub/tests/test_spa_ordering.py +++ b/packages/meshbay-hub/tests/test_spa_ordering.py @@ -91,6 +91,23 @@ def test_keys_are_recovered_before_the_join_is_attempted(): "browser that did not register has no key to sign the join with") +def test_a_bundle_is_opened_with_the_key_the_node_has_already_proved(): + """ + A bundle is sealed for one account on one node: its key derives from the + node's public key and its associated data names both. That key has to be + the one the challenge signature proved — recorded before the bundle is + fetched — or a bundle would be opened, or a new one sealed, for nothing. + """ + proved, user, sealed_for, fetch = _positions( + "this.nodePk = reply.node_pk", + "this._userId = userId", + "const sealedFor = { userId: this._userId, nodePk: this.nodePk }", + "type: 'keypair_bundle_fetch'", + ) + assert proved < sealed_for < fetch + assert user < sealed_for + + def test_the_ack_still_verifies_the_announced_node_key(): """ Taking node_pk from the challenge is only safe because the ack proves it and -- cgit v1.2.3