From 50158f7fa8d2cc8d78fb769dbf75efb0adaa295c Mon Sep 17 00:00:00 2001 From: m17hr1l Date: Sat, 6 Jun 2026 16:08:31 +0200 Subject: [PATCH] stage-fed-b federation: dns record format --- src/psyc/lines/federation.py | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/psyc/lines/federation.py b/src/psyc/lines/federation.py index 28c6d9f..b031c1c 100644 --- a/src/psyc/lines/federation.py +++ b/src/psyc/lines/federation.py @@ -18,6 +18,7 @@ from typing import Tuple from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ed25519 +from pydantic import BaseModel from psyc import DATA_DIR, log @@ -28,6 +29,10 @@ FED_DIR = DATA_DIR / "federation" PRIVATE_KEY_PATH = FED_DIR / "node.key" PUBLIC_KEY_PATH = FED_DIR / "node.pub" +FEED_VERSION = "psyc1" +FEED_ALG = "ed25519" +FEED_PATH = "/federation/feed" + # ---------- keypair persistence ----------------------------------------- @@ -93,3 +98,56 @@ def node_fingerprint() -> str: _, pub = node_keypair() digest = hashlib.sha256(_raw_pubkey_bytes(pub)).digest() return digest[:16].hex() + + +def _fingerprint_for_pubkey_pem(pubkey_pem: str) -> str: + pub = serialization.load_pem_public_key(pubkey_pem.encode("ascii")) + if not isinstance(pub, ed25519.Ed25519PublicKey): + raise ValueError("not an Ed25519 public key") + return hashlib.sha256(_raw_pubkey_bytes(pub)).digest()[:16].hex() + + +# ---------- DNS record format ------------------------------------------- + +class DNSRecord(BaseModel): + """The SRV + TXT pair an admin pastes into their zone file.""" + srv_name: str + srv_target: str + srv_port: int + srv_priority: int = 10 + srv_weight: int = 10 + txt_name: str + txt_value: str + human_instructions: str + + +def dns_record(domain: str, port: int = 443) -> DNSRecord: + """Build the DNS-SD-style records that advertise this node at `domain`.""" + fp = node_fingerprint() + srv_name = f"_psyc._tcp.{domain}" + srv_target = f"{domain}." + txt_name = srv_name + txt_value = f"v={FEED_VERSION} fp={fp} alg={FEED_ALG} path={FEED_PATH}" + instructions = ( + f"; psyc federation records for {domain}\n" + f"; ----------------------------------------------------------\n" + f"; 1) SRV record — locates this psyc node (host + port).\n" + f'{srv_name}. 3600 IN SRV 10 10 {port} {srv_target}\n' + f";\n" + f"; 2) TXT record — declares protocol version, key fingerprint,\n" + f"; signature algorithm, and the feed endpoint path.\n" + f'{txt_name}. 3600 IN TXT "{txt_value}"\n' + f"; ----------------------------------------------------------\n" + f"; Once these are live, federation peers can fetch:\n" + f"; https://{domain}{FEED_PATH} (signed feed JSON)\n" + f"; https://{domain}/federation/key (public key PEM)\n" + f"; https://{domain}/federation/info (capabilities)\n" + ) + return DNSRecord( + srv_name=srv_name, + srv_target=srv_target, + srv_port=port, + txt_name=txt_name, + txt_value=txt_value, + human_instructions=instructions, + )