A bootstrap node helps other nodes find peers and exchange signed connection
setup signals. The configuration name is seed: seeds holds bootstrap-node
cards and each --seed option supplies one card on the CLI. It does not carry memory or messages
or grant private access. See the glossary for this
and related terms.
Updated defense policy: see DEFENSE.md for shared blocks, audit, temporary bans, search protection and tighter registration quotas. Public-address mode now defaults to 18-bit work (minimum 16); private-demo default stays zero. Searches are sent directly to data peers, never through the bootstrap protocol. The newer defense policy supersedes the original rate/default discussion below. See PRIVACY.md for metadata disclosure considerations.
There are two separate listeners. The data listener uses pinned mutual TLS and local read/publish/message permissions. The bootstrap listener accepts unknown registrants over server-authenticated TLS, stores self-signed endpoint announcements, and returns candidate peers. Registering is open; data permissions remain explicit. A successful puzzle grants no data permission or endorsement.
Demonstration
.venv/bin/python -m examples.bootstrap_mesh
This starts two independent bootstrap processes and two data-node processes.
Alice advertises through both seeds. The first seed stops. New node Bob uses the
actual join CLI to fall through the unavailable seed, register with the second,
and discover Alice. Both trust stores remain empty. After explicit local
approval, Bob sends Alice a signed message over mutual TLS. Temporary directories
and processes are cleaned up. The demo uses 12-bit work to exercise admission;
ordinary messages carry no work requirement.
Run a seed
.venv/bin/intertexum --data /tmp/seed1 init
.venv/bin/intertexum --data /tmp/seed1 card --host 127.0.0.1 --port 7444 > /tmp/seed1-card.json
.venv/bin/intertexum --data /tmp/seed1 bootstrap-serve --port 7444 --network agentmesh-demo-v1
Create another seed with another directory and port. Distribute seed cards through a trusted channel; the exact TLS certificate is pinned. Any node can host this separate service; seeds do not have memory-access privileges.
.venv/bin/intertexum --data /tmp/alice init
.venv/bin/intertexum --data /tmp/alice join \
--seed /tmp/seed1-card.json --seed /tmp/seed2-card.json \
--host 127.0.0.1 --port 7443 --network agentmesh-demo-v1
.venv/bin/intertexum --data /tmp/alice discover \
--seed /tmp/seed1-card.json --seed /tmp/seed2-card.json
join advertises and discovers; it does not start the data listener. Run serve
separately. Review returned signed candidates, save a candidate's body.card,
then use trust CARD --allow ... on the appropriate nodes. Output lists failed
seeds and never claims complete global discovery. Existing data connections do
not require an available seed.
For a reachable hosted seed, bind bootstrap-serve --host 0.0.0.0, advertise its
real numeric public IP in the seed card, choose an explicit network name, and
use --public-addresses to reject non-global advertised peer addresses. This
implementation's listener is IPv4; it does not yet offer IPv6 listening or DNS
endpoint resolution. A public seed is hostable, but no host has been deployed.
Bounds and proof of work
Private demo default: --pow-bits 0 (no search effort). Optional registration
friction: --pow-bits 16. The server issues a 120-second HMAC-authenticated
challenge bound to server identity, network, register action, peer identity,
and signed announcement hash. The client finds a SHA-256 nonce. Verification
checks the MAC, binding, expiry, difficulty, and hash; registration consumes the
challenge atomically with the directory update. Reuse fails. A restart rotates
the challenge key and invalidates outstanding challenges. New announcements and
renewals each require a new ticket; normal search, messaging, and cache fetches
do not. A client refuses more than 20 bits or more than two seconds of work.
This is not a coin, consensus protocol, trust score, or proof of personhood. GPU/ASIC users and botnets can buy disproportionately cheap hashes. A fixed hash difficulty does not promise a fixed delay, especially on weaker CPUs. Renewal work is opt-in with the seed's difficulty; use zero for the private demo. Before enabling it publicly, measure p95 solve time on the slowest supported device and keep work out of emergency revocation and established-peer traffic.
Local calibration on the benchmark machine, single Python solver:
| Difficulty | Trials | Solve p50 | Solve p95 | Verify p50 |
|---|---|---|---|---|
| 12 bits | 32 | 3.7 ms | 11.5 ms | .018 ms |
| 16 bits | 64 | 23.3 ms | 114.2 ms | .061 ms |
| 18 bits | 32 | 108.2 ms | 592.7 ms | .068 ms |
No trial hit the two-second client budget. These are random-work samples, not
guarantees; the 16-bit maximum was 298.6 ms. Verification timing covers the work
ticket, not TLS, signature checks, or SQLite writes. See
benchmarks/results/work.json (generated locally by python -m benchmarks.work). Start the private demo with
work disabled; 16 bits is a candidate for a measured public admission experiment.
The seed also caps concurrent handlers at 16, requests at 16 KiB, announcements at 8 KiB, directory entries at 1,000, replay tickets at 10,000, and discovery pages at 50 entries. Connections are limited before TLS to a global bucket (20/sec, burst 40) and per-source-IP bucket (2/sec, burst 10); the IP map is capped at 4,096 entries and expires idle buckets after 60 seconds. These limits share capacity across clients behind a NAT. Handshake and frame deadlines are five seconds each. This is a bounded prototype, not volumetric DDoS protection.
What public networking still needs
An unknown node can register with a reachable seed and discover the network today. It cannot automatically read, publish, or message arbitrary nodes. The next policy decision is whether public peers get narrowly scoped public-read access, introductions/invitations, or some other admission mechanism. Private content must remain protected in all cases. Solved work must never be that authorization mechanism.
Announcements expire after 15 minutes by default, with a maximum one-hour lifetime. Registration checks signatures, certificate validity, network name, and timestamps; a live directory rejects backwards timestamp updates and conflicting announcements in the same second. It does not persist a per-peer sequence watermark after expiry, so old still-valid announcements can be replayed to a fresh directory. Entries are unverified reachability claims. Optional reachability probes dial only the caller socket IP and its registered unprivileged port. Configured clients refresh peers and renew registrations. Open profiles admit new identities for public access only; private invitation profiles require their membership proof. Ordinary peer exchange is implemented. Seed-to-seed gossip and fully seedless initial discovery are not implemented. A malicious seed can omit peers; several operator-independent seeds reduce dependence but do not prove completeness or eclipse resistance.
Consumer machines behind NAT can enable signed rendezvous, ICE hole punching and authenticated relay fallback. Bootstrap alone does not make an inbound port reachable. We also still need hosted-process supervision, key renewal/recovery, monitoring, and abuse/load testing before calling this a production public mesh.
Sources: libp2p discovery/DHT, connection and resource limits, and NAT hole punching.