hivemind-rendezvous 0.1.1a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: hivemind-rendezvous
3
+ Version: 0.1.1a1
4
+ Summary: Async store-and-forward dead-drop rendezvous service for HiveMind nodes
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: hivemind-bus-client
9
+ Requires-Dist: json-database>=0.10.2a1
10
+ Requires-Dist: poorman-handshake
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest; extra == "dev"
13
+ Requires-Dist: pytest-cov; extra == "dev"
14
+
15
+ # hivemind-rendezvous
16
+
17
+ An async **store-and-forward dead-drop** for [HiveMind](https://github.com/JarbasHiveMind/HiveMind-core)
18
+ nodes that are never online at the same time. A sender deposits an encrypted
19
+ message addressed to a recipient's public key; the recipient later proves
20
+ ownership of that key and collects the message. No simultaneous connection, no
21
+ shared IP, no persistent HiveMind session.
22
+
23
+ ## Where it sits
24
+
25
+ Normal HiveMind links are live encrypted WebSocket connections between a satellite
26
+ and a [hivemind-core](https://github.com/JarbasHiveMind/HiveMind-core) hub. That
27
+ requires both ends to be reachable at once. hivemind-rendezvous fills the gap for
28
+ nodes that are only intermittently online: it is a small neutral HTTP relay that
29
+ holds [`INTERCOM`](https://github.com/JarbasHiveMind/hivemind-websocket-client)
30
+ messages until the recipient comes back to fetch them.
31
+
32
+ The relay never sees plaintext — messages are end-to-end encrypted to the
33
+ recipient's public key before deposit. The relay only stores opaque blobs keyed by
34
+ recipient pubkey and enforces ownership on retrieval.
35
+
36
+ ## How it works
37
+
38
+ ```
39
+ Node A (sender) Rendezvous node Node B (recipient)
40
+ │ │ │
41
+ │-- POST /deposit -->│ │
42
+ │ INTERCOM msg │ │
43
+ │ target=B.pubkey │ (stored, TTL ≤7d) │
44
+ │ │ │
45
+ │ (time passes) │
46
+ │ │<-- POST /retrieve --│
47
+ │ │ sign(B.privkey) │
48
+ │ │-- messages -------->│
49
+ │ │ (deleted) │
50
+ ```
51
+
52
+ Authentication is **proof of RSA pubkey ownership**: to retrieve, a node signs a
53
+ fresh timestamp with its private key. The relay verifies the signature against the
54
+ claimed pubkey and the timestamp freshness (replay window), then returns and
55
+ deletes the pending messages.
56
+
57
+ ## Prerequisites
58
+
59
+ - Python 3.10+
60
+ - An RSA identity for each node (handled by HiveMind / `poorman-handshake`).
61
+ - A reachable host to run the relay (a small VPS, a Pi, or any always-on box the
62
+ intermittent nodes can reach over HTTP).
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install hivemind-rendezvous
68
+ ```
69
+
70
+ From source:
71
+
72
+ ```bash
73
+ git clone https://github.com/JarbasHiveMind/hivemind-rendezvous
74
+ cd hivemind-rendezvous
75
+ pip install -e .
76
+ ```
77
+
78
+ ## Quickstart
79
+
80
+ Run the relay on an always-on host:
81
+
82
+ ```bash
83
+ hivemind-rendezvous
84
+ # Rendezvous server listening on 0.0.0.0:6789
85
+ ```
86
+
87
+ That is the whole relay. Senders `POST /deposit` an INTERCOM message addressed to
88
+ a recipient pubkey; recipients `POST /retrieve` with an ownership proof to collect
89
+ them. See [HTTP API](docs/http-api.md) for the request bodies and
90
+ [examples](docs/examples.md) for deposit/retrieve snippets.
91
+
92
+ ## Configuration
93
+
94
+ `run_server()` arguments (and their defaults):
95
+
96
+ | Argument | Default | Description |
97
+ | --- | --- | --- |
98
+ | `host` | `0.0.0.0` | Bind address. |
99
+ | `port` | `6789` | Listen port. |
100
+ | `node_pubkey` | `""` | This relay's own RSA pubkey (PEM), served at `/pubkey`. |
101
+ | `deposit_rate_limit` | `60` | Max deposits per client IP per window. |
102
+ | `deposit_rate_window` | `60` | Rate-limit window in seconds. |
103
+ | `require_depositor_proof` | `False` | Require a valid depositor ownership proof on every deposit. |
104
+
105
+ Message TTL is set per deposit (`ttl` field, default and hard cap **7 days**).
106
+
107
+ ## Documentation
108
+
109
+ See [`docs/`](docs/index.md):
110
+
111
+ - [How it works](docs/how-it-works.md) — deposit/retrieve flow and authentication.
112
+ - [HTTP API](docs/http-api.md) — endpoints, request/response bodies, error codes.
113
+ - [Deploy](docs/deploy.md) — running and configuring the relay.
114
+ - [Examples](docs/examples.md) — deposit and retrieve from a client.
115
+
116
+ ## License
117
+
118
+ Apache-2.0
@@ -0,0 +1,104 @@
1
+ # hivemind-rendezvous
2
+
3
+ An async **store-and-forward dead-drop** for [HiveMind](https://github.com/JarbasHiveMind/HiveMind-core)
4
+ nodes that are never online at the same time. A sender deposits an encrypted
5
+ message addressed to a recipient's public key; the recipient later proves
6
+ ownership of that key and collects the message. No simultaneous connection, no
7
+ shared IP, no persistent HiveMind session.
8
+
9
+ ## Where it sits
10
+
11
+ Normal HiveMind links are live encrypted WebSocket connections between a satellite
12
+ and a [hivemind-core](https://github.com/JarbasHiveMind/HiveMind-core) hub. That
13
+ requires both ends to be reachable at once. hivemind-rendezvous fills the gap for
14
+ nodes that are only intermittently online: it is a small neutral HTTP relay that
15
+ holds [`INTERCOM`](https://github.com/JarbasHiveMind/hivemind-websocket-client)
16
+ messages until the recipient comes back to fetch them.
17
+
18
+ The relay never sees plaintext — messages are end-to-end encrypted to the
19
+ recipient's public key before deposit. The relay only stores opaque blobs keyed by
20
+ recipient pubkey and enforces ownership on retrieval.
21
+
22
+ ## How it works
23
+
24
+ ```
25
+ Node A (sender) Rendezvous node Node B (recipient)
26
+ │ │ │
27
+ │-- POST /deposit -->│ │
28
+ │ INTERCOM msg │ │
29
+ │ target=B.pubkey │ (stored, TTL ≤7d) │
30
+ │ │ │
31
+ │ (time passes) │
32
+ │ │<-- POST /retrieve --│
33
+ │ │ sign(B.privkey) │
34
+ │ │-- messages -------->│
35
+ │ │ (deleted) │
36
+ ```
37
+
38
+ Authentication is **proof of RSA pubkey ownership**: to retrieve, a node signs a
39
+ fresh timestamp with its private key. The relay verifies the signature against the
40
+ claimed pubkey and the timestamp freshness (replay window), then returns and
41
+ deletes the pending messages.
42
+
43
+ ## Prerequisites
44
+
45
+ - Python 3.10+
46
+ - An RSA identity for each node (handled by HiveMind / `poorman-handshake`).
47
+ - A reachable host to run the relay (a small VPS, a Pi, or any always-on box the
48
+ intermittent nodes can reach over HTTP).
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install hivemind-rendezvous
54
+ ```
55
+
56
+ From source:
57
+
58
+ ```bash
59
+ git clone https://github.com/JarbasHiveMind/hivemind-rendezvous
60
+ cd hivemind-rendezvous
61
+ pip install -e .
62
+ ```
63
+
64
+ ## Quickstart
65
+
66
+ Run the relay on an always-on host:
67
+
68
+ ```bash
69
+ hivemind-rendezvous
70
+ # Rendezvous server listening on 0.0.0.0:6789
71
+ ```
72
+
73
+ That is the whole relay. Senders `POST /deposit` an INTERCOM message addressed to
74
+ a recipient pubkey; recipients `POST /retrieve` with an ownership proof to collect
75
+ them. See [HTTP API](docs/http-api.md) for the request bodies and
76
+ [examples](docs/examples.md) for deposit/retrieve snippets.
77
+
78
+ ## Configuration
79
+
80
+ `run_server()` arguments (and their defaults):
81
+
82
+ | Argument | Default | Description |
83
+ | --- | --- | --- |
84
+ | `host` | `0.0.0.0` | Bind address. |
85
+ | `port` | `6789` | Listen port. |
86
+ | `node_pubkey` | `""` | This relay's own RSA pubkey (PEM), served at `/pubkey`. |
87
+ | `deposit_rate_limit` | `60` | Max deposits per client IP per window. |
88
+ | `deposit_rate_window` | `60` | Rate-limit window in seconds. |
89
+ | `require_depositor_proof` | `False` | Require a valid depositor ownership proof on every deposit. |
90
+
91
+ Message TTL is set per deposit (`ttl` field, default and hard cap **7 days**).
92
+
93
+ ## Documentation
94
+
95
+ See [`docs/`](docs/index.md):
96
+
97
+ - [How it works](docs/how-it-works.md) — deposit/retrieve flow and authentication.
98
+ - [HTTP API](docs/http-api.md) — endpoints, request/response bodies, error codes.
99
+ - [Deploy](docs/deploy.md) — running and configuring the relay.
100
+ - [Examples](docs/examples.md) — deposit and retrieve from a client.
101
+
102
+ ## License
103
+
104
+ Apache-2.0
@@ -0,0 +1,18 @@
1
+ """hivemind-rendezvous — async store-and-forward dead drop for HiveMind nodes.
2
+
3
+ Nodes from different, non-simultaneously-connected hives can exchange INTERCOM
4
+ messages via a shared rendezvous point. The sender deposits a message keyed by
5
+ the recipient's RSA public key; the recipient retrieves it later by proving
6
+ pubkey ownership (signed timestamp, no server-side challenge state).
7
+
8
+ Submodules:
9
+ auth — :func:`~hivemind_rendezvous.auth.sign_ownership` /
10
+ :func:`~hivemind_rendezvous.auth.verify_ownership`
11
+ storage — :class:`~hivemind_rendezvous.storage.RendezvousStore`
12
+ server — :func:`~hivemind_rendezvous.server.run_server`
13
+ """
14
+
15
+ from hivemind_rendezvous.version import VERSION
16
+
17
+ __version__ = VERSION
18
+ __all__ = ["VERSION"]
@@ -0,0 +1,102 @@
1
+ """Proof-of-pubkey-ownership authentication for the rendezvous service.
2
+
3
+ Stateless, single-round-trip: the client signs a domain-separated message:
4
+
5
+ ``b"hivemind-rendezvous-v1\\x00" + pubkey_bytes + b"\\x00"
6
+ + server_pubkey_bytes + b"\\x00" + timestamp_bytes``
7
+
8
+ The server verifies the signature and rejects timestamps outside a ±60 second
9
+ window to prevent replay attacks. Binding the server's own pubkey into the
10
+ signed message prevents cross-server replay: a valid proof issued to one
11
+ rendezvous node cannot be replayed at another.
12
+ """
13
+
14
+ import base64
15
+ import time
16
+ from typing import Union
17
+
18
+ from poorman_handshake.asymmetric.utils import sign_RSA, verify_RSA
19
+
20
+
21
+ _TIMESTAMP_TOLERANCE_SECONDS: int = 60
22
+ _DOMAIN: bytes = b"hivemind-rendezvous-v1\x00"
23
+
24
+
25
+ def _ownership_message(pubkey: str, timestamp: int, server_pubkey: str = "") -> bytes:
26
+ """Return the canonical byte string that is signed/verified for ownership proof.
27
+
28
+ The message is domain-separated and binds the server's public key to prevent
29
+ cross-server replay attacks.
30
+
31
+ Format (bytes, null-delimited fields):
32
+ ``DOMAIN || claimer_pubkey || 0x00 || server_pubkey || 0x00 || timestamp_decimal``
33
+
34
+ Args:
35
+ pubkey: PEM-encoded RSA public key of the claiming node.
36
+ timestamp: Unix timestamp (integer seconds).
37
+ server_pubkey: PEM-encoded RSA public key of the rendezvous server.
38
+ Empty string when server identity binding is not used (legacy / testing).
39
+
40
+ Returns:
41
+ The message bytes to sign or verify.
42
+ """
43
+ return (
44
+ _DOMAIN
45
+ + pubkey.encode("utf-8") + b"\x00"
46
+ + server_pubkey.encode("utf-8") + b"\x00"
47
+ + str(timestamp).encode("utf-8")
48
+ )
49
+
50
+
51
+ def sign_ownership(private_key: Union[str, bytes], pubkey: str, timestamp: int,
52
+ server_pubkey: str = "") -> str:
53
+ """Produce a base64-encoded ownership proof signature.
54
+
55
+ Args:
56
+ private_key: RSA private key (PEM string, bytes, or RsaKey) of the claimer.
57
+ pubkey: PEM-encoded RSA public key of the claimer.
58
+ timestamp: Unix timestamp (integer seconds) to embed in the proof.
59
+ server_pubkey: PEM-encoded RSA public key of the rendezvous server.
60
+ Binds the proof to a specific server — must match the value used
61
+ by the server in :func:`verify_ownership`.
62
+
63
+ Returns:
64
+ Base64-encoded signature string suitable for JSON transport.
65
+ """
66
+ message = _ownership_message(pubkey, timestamp, server_pubkey)
67
+ signature_bytes = sign_RSA(private_key, message)
68
+ return base64.b64encode(signature_bytes).decode("utf-8")
69
+
70
+
71
+ def verify_ownership(pubkey: str, timestamp: int, signature: str,
72
+ server_pubkey: str = "") -> bool:
73
+ """Verify a proof-of-pubkey-ownership claim.
74
+
75
+ Checks both signature validity and timestamp freshness. A valid proof
76
+ requires:
77
+
78
+ 1. The signature over the domain-separated message verifies against
79
+ ``pubkey``.
80
+ 2. ``abs(now - timestamp) <= 60`` seconds (replay protection).
81
+ 3. ``server_pubkey`` in the signed message matches the value passed here
82
+ (cross-server replay protection).
83
+
84
+ Args:
85
+ pubkey: PEM-encoded RSA public key of the claiming node.
86
+ timestamp: Unix timestamp (integer seconds) embedded in the proof.
87
+ signature: Base64-encoded signature produced by :func:`sign_ownership`.
88
+ server_pubkey: PEM-encoded RSA public key of the rendezvous server
89
+ that was used when signing. Must be supplied by the server itself.
90
+
91
+ Returns:
92
+ ``True`` if the proof is valid and fresh; ``False`` otherwise.
93
+ """
94
+ now = int(time.time())
95
+ if abs(now - timestamp) > _TIMESTAMP_TOLERANCE_SECONDS:
96
+ return False
97
+ try:
98
+ signature_bytes = base64.b64decode(signature)
99
+ except Exception:
100
+ return False
101
+ message = _ownership_message(pubkey, timestamp, server_pubkey)
102
+ return verify_RSA(pubkey, message, signature_bytes)