mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-07 12:57:57 +02:00
Fix Extra-participant E2E: live contact send and Tor prekey cache path.
Adds connect-contact HTTP endpoint with cached-bundle support, subprocess contact send via docker cp bundle file, and direct Tor prekey fetch to avoid wedging single-worker uvicorn. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -862,6 +862,7 @@ _ROUTE_TRANSPORT_POLICY: dict[tuple[str, str], RouteTransportPolicy] = {
|
||||
("POST", "/api/wormhole/gate/messages/decrypt"): _local_only_route_policy("private_control_only"),
|
||||
# ── Wormhole DM (strong) ──────────────────────────────────────────
|
||||
("POST", "/api/wormhole/dm/compose"): _local_only_route_policy("private_control_only"),
|
||||
("POST", "/api/wormhole/dm/connect-contact"): _local_only_route_policy("private_control_only"),
|
||||
("POST", "/api/wormhole/dm/decrypt"): _local_only_route_policy("private_control_only"),
|
||||
("POST", "/api/wormhole/dm/mls-key-package"): _local_only_route_policy("private_control_only"),
|
||||
("POST", "/api/wormhole/dm/register-key"): _local_only_route_policy("private_control_only"),
|
||||
|
||||
@@ -330,6 +330,14 @@ class WormholeDmBootstrapDecryptRequest(BaseModel):
|
||||
ciphertext: str
|
||||
|
||||
|
||||
class WormholeDmConnectContactRequest(BaseModel):
|
||||
lookup_token: str = ""
|
||||
peer_id: str = ""
|
||||
note: str = ""
|
||||
lookup_peer_url: str = ""
|
||||
cached_prekey_bundle: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class WormholeDmInviteImportRequest(BaseModel):
|
||||
invite: dict[str, Any]
|
||||
alias: str = ""
|
||||
@@ -1089,6 +1097,20 @@ async def api_wormhole_dm_bootstrap_decrypt(request: Request, body: WormholeDmBo
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/wormhole/dm/connect-contact", dependencies=[Depends(require_local_operator)])
|
||||
@limiter.limit("30/minute")
|
||||
async def api_wormhole_dm_connect_contact(request: Request, body: WormholeDmConnectContactRequest):
|
||||
from services.openclaw_infonet import send_contact_request
|
||||
|
||||
return send_contact_request(
|
||||
lookup_token=str(body.lookup_token or ""),
|
||||
peer_id=str(body.peer_id or ""),
|
||||
note=str(body.note or ""),
|
||||
lookup_peer_url=str(body.lookup_peer_url or ""),
|
||||
cached_prekey_bundle=dict(body.cached_prekey_bundle or {}) if body.cached_prekey_bundle else None,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/wormhole/dm/sender-token", dependencies=[Depends(require_local_operator)])
|
||||
@limiter.limit("60/minute")
|
||||
async def api_wormhole_dm_sender_token(request: Request, body: WormholeDmSenderTokenRequest):
|
||||
|
||||
@@ -1361,13 +1361,15 @@ def bootstrap_encrypt_for_peer(
|
||||
plaintext: str,
|
||||
*,
|
||||
lookup_token: str = "",
|
||||
fetched_bundle: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
token = str(lookup_token or "").strip()
|
||||
peer = str(peer_id or "").strip()
|
||||
fetched_bundle = fetch_dm_prekey_bundle(
|
||||
agent_id=peer if not token else "",
|
||||
lookup_token=token,
|
||||
)
|
||||
if fetched_bundle is None:
|
||||
fetched_bundle = fetch_dm_prekey_bundle(
|
||||
agent_id=peer if not token else "",
|
||||
lookup_token=token,
|
||||
)
|
||||
if not fetched_bundle.get("ok"):
|
||||
detail = str(fetched_bundle.get("detail", "") or "")
|
||||
if "root attestation" in detail.lower():
|
||||
|
||||
@@ -592,6 +592,7 @@ def send_contact_request(
|
||||
peer_id: str = "",
|
||||
note: str = "",
|
||||
lookup_peer_url: str = "",
|
||||
cached_prekey_bundle: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Send a first-contact request using a short address or peer id."""
|
||||
from services.mesh.mesh_wormhole_dead_drop import build_contact_offer
|
||||
@@ -604,11 +605,14 @@ def send_contact_request(
|
||||
return {"ok": False, "detail": "lookup_token or peer_id required"}
|
||||
|
||||
preferred_peer = str(lookup_peer_url or "").strip().rstrip("/")
|
||||
bundle = fetch_dm_prekey_bundle(
|
||||
agent_id=peer if not token else "",
|
||||
lookup_token=token,
|
||||
lookup_peer_urls=[preferred_peer] if preferred_peer else None,
|
||||
)
|
||||
if cached_prekey_bundle and cached_prekey_bundle.get("ok"):
|
||||
bundle = dict(cached_prekey_bundle)
|
||||
else:
|
||||
bundle = fetch_dm_prekey_bundle(
|
||||
agent_id=peer if not token else "",
|
||||
lookup_token=token,
|
||||
lookup_peer_urls=[preferred_peer] if preferred_peer else None,
|
||||
)
|
||||
if not bundle.get("ok"):
|
||||
return bundle
|
||||
recipient = str(bundle.get("agent_id") or peer).strip()
|
||||
@@ -621,7 +625,11 @@ def send_contact_request(
|
||||
dh_algo=str(identity.get("dh_algo") or "X25519"),
|
||||
geo_hint=str(note or ""),
|
||||
)
|
||||
encrypted = bootstrap_encrypt_for_peer(recipient, offer, lookup_token=token)
|
||||
encrypted = bootstrap_encrypt_for_peer(
|
||||
recipient,
|
||||
offer,
|
||||
fetched_bundle=bundle,
|
||||
)
|
||||
if not encrypted.get("ok"):
|
||||
return encrypted
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import hmac
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import time
|
||||
import urllib.error
|
||||
@@ -129,15 +130,26 @@ _EMBED_SIGNED_MAILBOX_HELPERS = textwrap.dedent(
|
||||
|
||||
|
||||
def _docker_json(method: str, path: str, body: dict | None = None, *, admin_key: str = "", timeout_s: int = 30) -> dict:
|
||||
payload = ["docker", "exec", "shadowbroker-backend", "curl", "-s", "--max-time", str(timeout_s)]
|
||||
use_stdin = body is not None
|
||||
payload = ["docker", "exec"]
|
||||
if use_stdin:
|
||||
payload.append("-i")
|
||||
payload.extend(["shadowbroker-backend", "curl", "-s", "--max-time", str(timeout_s)])
|
||||
if admin_key:
|
||||
payload.extend(["-H", f"X-Admin-Key: {admin_key}"])
|
||||
if body is not None:
|
||||
payload.extend(["-H", "Content-Type: application/json", "-X", method.upper(), "-d", json.dumps(body)])
|
||||
payload.extend(["-H", "Content-Type: application/json", "-X", method.upper(), "-d", "@-"])
|
||||
else:
|
||||
payload.extend(["-X", method.upper()])
|
||||
payload.append(f"http://127.0.0.1:8000{path}")
|
||||
proc = subprocess.run(payload, capture_output=True, text=True, timeout=timeout_s + 15, check=False)
|
||||
proc = subprocess.run(
|
||||
payload,
|
||||
input=json.dumps(body) if body is not None else None,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_s + 15,
|
||||
check=False,
|
||||
)
|
||||
raw = (proc.stdout or "").strip()
|
||||
if not raw:
|
||||
raise RuntimeError(proc.stderr.strip() or f"{method} {path} produced no response")
|
||||
@@ -330,6 +342,46 @@ def _docker_python(code: str, *, timeout_s: int = 600) -> dict:
|
||||
return json.loads(line)
|
||||
|
||||
|
||||
def _docker_python_contact_send(
|
||||
*,
|
||||
handle: str,
|
||||
peer_id: str,
|
||||
note: str,
|
||||
lookup_peer_url: str,
|
||||
cached_prekey_bundle: dict,
|
||||
timeout_s: int = 120,
|
||||
) -> dict:
|
||||
"""Send contact request in an isolated backend subprocess with a cached prekey bundle."""
|
||||
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json", encoding="utf-8") as tmp:
|
||||
json.dump(cached_prekey_bundle, tmp)
|
||||
local_path = tmp.name
|
||||
try:
|
||||
subprocess.run(
|
||||
["docker", "cp", local_path, "shadowbroker-backend:/tmp/e2e_prekey_bundle.json"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
finally:
|
||||
os.unlink(local_path)
|
||||
code = (
|
||||
"import json\n"
|
||||
"from services.openclaw_infonet import send_contact_request\n"
|
||||
'bundle = json.load(open("/tmp/e2e_prekey_bundle.json", encoding="utf-8"))\n'
|
||||
f"result = send_contact_request(lookup_token={json.dumps(handle)}, "
|
||||
f"peer_id={json.dumps(peer_id)}, note={json.dumps(note)}, "
|
||||
f"lookup_peer_url={json.dumps(lookup_peer_url)}, cached_prekey_bundle=bundle)\n"
|
||||
"print(json.dumps({"
|
||||
"'ok': bool(result.get('ok')), "
|
||||
"'send': result, "
|
||||
"'msg_id': result.get('msg_id',''), "
|
||||
"'sender_id': result.get('sender_id',''), "
|
||||
"'recipient_id': result.get('recipient_id','')"
|
||||
"}))\n"
|
||||
)
|
||||
return _docker_python(code, timeout_s=timeout_s)
|
||||
|
||||
|
||||
def _local_compose_cmd(*subcommand: str) -> list[str]:
|
||||
cmd = ["docker", "compose"]
|
||||
for compose_file in LOCAL_COMPOSE_FILES:
|
||||
@@ -2295,6 +2347,15 @@ def _fetch_peer_prekey_bundle(
|
||||
lookup_token: str = "",
|
||||
lookup_peer_url: str = "",
|
||||
) -> dict:
|
||||
hint = str(lookup_peer_url or (f"http://{PETE_ONION}" if PETE_ONION else "")).strip()
|
||||
if ".onion" in hint and lookup_token:
|
||||
try:
|
||||
bundle = _direct_tor_prekey_bundle(lookup_token, hint)
|
||||
if bundle.get("ok"):
|
||||
bundle["source"] = "direct_tor_prekey_bundle"
|
||||
return bundle
|
||||
except Exception as exc:
|
||||
print(json.dumps({"direct_tor_prekey_bundle_fallback": str(exc) or type(exc).__name__}, indent=2))
|
||||
path = "/api/mesh/dm/prekey-bundle?"
|
||||
params: list[str] = []
|
||||
if lookup_token:
|
||||
@@ -2529,8 +2590,8 @@ print(json.dumps({{
|
||||
return {"ok": False, "detail": str(exc) or type(exc).__name__}
|
||||
|
||||
|
||||
def _direct_tor_prekey_lookup(handle: str, lookup_peer_url: str) -> dict:
|
||||
"""Fetch invite-scoped prekey over Tor without blocking local uvicorn on /dm/pubkey."""
|
||||
def _direct_tor_prekey_bundle(handle: str, lookup_peer_url: str) -> dict:
|
||||
"""Fetch invite-scoped prekey bundle over Tor without blocking local uvicorn."""
|
||||
peer = str(lookup_peer_url or "").strip().rstrip("/")
|
||||
if not peer:
|
||||
peer = f"http://{PETE_ONION}".rstrip("/") if PETE_ONION else ""
|
||||
@@ -2560,8 +2621,16 @@ def _direct_tor_prekey_lookup(handle: str, lookup_peer_url: str) -> dict:
|
||||
)
|
||||
raw = (proc.stdout or "").strip()
|
||||
if not raw:
|
||||
raise RuntimeError(proc.stderr.strip() or "direct tor prekey lookup produced no response")
|
||||
raise RuntimeError(proc.stderr.strip() or "direct tor prekey bundle produced no response")
|
||||
payload = json.loads(raw)
|
||||
if not isinstance(payload, dict):
|
||||
return {"ok": False, "detail": "invalid prekey bundle response"}
|
||||
return payload
|
||||
|
||||
|
||||
def _direct_tor_prekey_lookup(handle: str, lookup_peer_url: str) -> dict:
|
||||
"""Fetch invite-scoped pubkey fields over Tor without blocking local uvicorn."""
|
||||
payload = _direct_tor_prekey_bundle(handle, lookup_peer_url)
|
||||
if not isinstance(payload, dict) or not payload.get("ok"):
|
||||
return payload if isinstance(payload, dict) else {"ok": False, "detail": "invalid prekey response"}
|
||||
bundle = payload.get("bundle") if isinstance(payload.get("bundle"), dict) else {}
|
||||
@@ -2705,19 +2774,13 @@ def main() -> int:
|
||||
raise RuntimeError(f"Pete prekey bundle unavailable before contact send: {pete_prekey_bundle}")
|
||||
|
||||
print("== step 2: send contact request from local ==")
|
||||
send_code = (
|
||||
"import json\n"
|
||||
"from services.openclaw_infonet import send_contact_request\n"
|
||||
f"result = send_contact_request(lookup_token={json.dumps(handle)}, note={json.dumps(MARKER)}, lookup_peer_url={json.dumps(lookup_peer_url)})\n"
|
||||
"print(json.dumps({"
|
||||
"'ok': bool(result.get('ok')), "
|
||||
"'send': result, "
|
||||
"'msg_id': result.get('msg_id',''), "
|
||||
"'sender_id': result.get('sender_id',''), "
|
||||
"'recipient_id': result.get('recipient_id','')"
|
||||
"}))\n"
|
||||
send_result = _docker_python_contact_send(
|
||||
handle=handle,
|
||||
peer_id=pete_id,
|
||||
note=MARKER,
|
||||
lookup_peer_url=lookup_peer_url,
|
||||
cached_prekey_bundle=pete_prekey_bundle,
|
||||
)
|
||||
send_result = _docker_python(send_code)
|
||||
print(json.dumps(send_result, indent=2))
|
||||
if not send_result.get("ok"):
|
||||
raise RuntimeError(f"local send failed: {send_result}")
|
||||
|
||||
Reference in New Issue
Block a user