# MeshForge experimental public peer bootstrap and proof — v1 Implemented opt-in protocol and offline/private observation cache; not an installer, release authority, automatic peer activation, LAN discovery or eclipse- resistant source-selection system. The default server has no bootstrap endpoint. No configured peer list or private repository registry is advertised automatically. ## Explicit operator workflow ```sh mf peer-identity --data /trusted/node mf peer-store-init --store /trusted/existing-peer-observation-directory meshforge serve --data /trusted/node --addr 127.0.0.1:8787 --bootstrap-origin http://127.0.0.1:8787 mf peer-probe --store /trusted/existing-peer-observation-directory --url http://127.0.0.1:8787 --peer-id mf:peer:INDEPENDENTLY_CONFIRMED_64_HEX_DIGITS mf peer-candidates --store /trusted/existing-peer-observation-directory ``` The pin above is an intentionally invalid placeholder. Obtain the actual expected peer ID through a separately trusted channel; never silently accept the ID from the same response being checked. This proves control of that peer key, not a real person, a repository owner, code authorship or a SteveCo-AI release root. All network operations are explicit. Probe performs exactly a manifest GET and fresh proof POST; it changes only the specified local observation store, never discovery configuration or public events. The observation directory must exist; initialization refuses existing state and Open never resets missing/corrupt state. `--bootstrap-candidates FILE` optionally supplies an explicit bounded JSON array of `{peer_id,key,origin}` entries in that field order, sorted by peer ID. Indentation is allowed, duplicate/unknown fields are not. At most 32 distinct non-self peers; each origin must satisfy the public-only address policy. No loopback/private candidate is allowed, even in local fixtures. Without that flag the signed list is empty. Candidate approval is an explicit act of publication, but does not magically verify the suggested endpoint. No cache is automatically exported. Ordinary public origins require HTTPS. Explicit loopback HTTP is a development exception; a loopback bootstrap origin requires a loopback listener. That exception never applies to a DNS name resolving to a private address. Actual intranet discovery needs a separate future policy and cannot relax this one. ## Typed canonical wire format `internal/peertrust` declares exact field order. Encoding is Go typed `json.Marshal`, without whitespace, unknown/duplicate fields or aliases. Signing sets the signature field to the empty string, then signs UTF-8 context (including its trailing NUL) followed by the typed JSON bytes. Binary keys/signatures/nonces are canonical unpadded base64url. Version changes require new schemas/contexts. - Peer ID: `mf:peer:` + lowercase SHA256 of `meshforge/peer/id/v1` + NUL + raw 32-byte Ed25519 public key. - Manifest schema `mf.peer-bootstrap/1`; signature context `meshforge/peer/bootstrap/v1` + NUL. - Proof schema `mf.peer-proof/1`; signature context `meshforge/peer/proof/v1` + NUL. Fixed public-key vector `03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8` produces `mf:peer:bbb703dc0a85b96b1f41376e892c2b12082efce77af41f08e8c0deca9f8cf459` (independently computed with Python hashlib). This new peer namespace does not rename existing `dh:repo`, `dh:id`, DDP or native/DOP objects/signatures. `GET /.well-known/meshforge/bootstrap.json` returns schema, peer_id, key, exact origin, network (`public` only), issued_at, expires_at, candidates and signature. The server signs a ten-minute lifetime; verifier maximum is one hour. Client checks its separately supplied pin, origin, canonical signature, current validity and every candidate's ID/key/public-origin binding before requesting a proof. `POST /experimental/peer/v1/proof` accepts exactly canonical JSON `{"nonce":"CANONICAL_32_BYTE_BASE64URL_NONCE"}`. The client uses crypto/rand for each probe. The response binds schema, peer_id, key, exact origin, network, nonce, issue/expiry and signature. Lifetime is at most 60 seconds. Replaying a proof against a new client nonce fails. This is a key-control challenge, not a bearer credential or an authenticated write operation. Client verifies against the already pin-verified manifest; no private key is transmitted. Server writes raw canonical bytes without a JSON-encoder newline. Public document size is at most 64 KiB; challenge body at most 256 bytes. No query/encoded-path aliases. Admission: four concurrent requests, 256 per IP/minute and 2048 globally, at most 4096 tracked IP windows. No per-request goroutine fanout. Probe has a 30-second total context and an eight-second HTTP timeout, bounded responses, no redirects/environment proxy and checked numeric DNS dialing. ## Shared public network policy `internal/netpolicy` is shared by discovery, snapshot clients and bootstrap. `IsGlobalUnicast` alone is insufficient: it includes non-public special-purpose ranges. The policy rejects private/link-local/shared-CGN/benchmark/documentation/ reserved ranges, mapped private IPv4 and scoped IPv6. IPv6 is conservatively limited to ordinary 2000::/3 space excluding protocol-assignment, tunnel and reserved documentation blocks. NAT64/6to4-style translation paths are excluded. Some globally reachable special-purpose exceptions are deliberately also denied. This is a conservative peer-hosting policy, not a universal routability claim. Sources reviewed on 25 October 2026: [IANA IPv4 special-purpose registry](https://www.iana.org/assignments/iana-ipv4-special-registry/), [IANA IPv6 special-purpose registry](https://www.iana.org/assignments/iana-ipv6-special-registry/). Every DNS answer candidate passes the same policy; the transport dials the chosen numeric address directly, preventing a second resolution from changing it. Explicit localhost is pinned to loopback rather than mutable DNS. These tests use fake resolvers/connectors, not external targets. Future allocation updates need review; there is no downloaded policy update or LAN fallback. ## Observation cache and trust limits Private `peer-observations.json` schema `mf.peer-observations/1` uses bounded process-locked atomic `internal/state` storage. Limit: 2 MiB, 128 endpoint entries, four provenance sources per entry. Record re-verifies both protocol signatures and the caller pin before updating. Quota failure leaves existing state unchanged. Clock rollback against stored observation time is rejected. An entry records peer ID/key/origin, first/last observed times, optional historical `key_proof_verified_at` and source ID/origin/observation time. Suggestions have no verification timestamp until separately probed with an explicit pin. Alternate origins create separate unverified entries; a suggestion cannot repoint a verified endpoint. Reopening/two instances preserve observations and provenance. This cache records local observations, not a signed portable provenance chain. Its administrator/filesystem are trusted. VerifiedAt is historical key control, not permanent trust, live reachability, operator independence or release approval. Source diversity can be inspected with the explicit local preview below; no peer majority grants trust. Availability, actual operator independence, peer-key rotation, TTL/GC, signed portable provenance, private LAN policy and installer integration remain separate gates. Offline work needs none of these endpoints or services. ## Explicit source-diverse selection preview `peer-select --store PRIVATE_OBSERVATIONS --policy LOCAL_POLICY.json` performs no network request and changes neither observations nor discovery configuration. The policy has exact typed fields: ```json {"schema":"mf.peer-selection-policy/1","sources":[{"peer_id":"PINNED_SOURCE_ONE","key":"SOURCE_ONE_PUBLIC_KEY","origin":"https://one.example","group":"operator-one"},{"peer_id":"PINNED_SOURCE_TWO","key":"SOURCE_TWO_PUBLIC_KEY","origin":"https://two.example","group":"operator-two"}],"target":1,"min_groups":2,"max_per_group":1,"max_age_seconds":600} ``` Replace placeholders with independently retained pins. Groups are explicit local classifications, never claims accepted from a relay or inferred from DNS names. Duplicate source peers/origins are denied. Limits: 2–32 source pins, target1–16, minimum2–8 groups, quota1–16 candidates per supporting group, age1–86400seconds. Each eligible public endpoint needs a recent directly verified key proof plus recent recommendations recorded from fresh authenticated pinned sources in at least `min_groups` distinct local groups. A source cannot endorse itself. Changing an origin or spelling creates no new source authority. Loopback fixtures are not public selection candidates. Selection sorts by newest key proof, then peer ID and origin; each peer identity and exact origin can appear at most once. Every supporting group consumes quota, so a candidate cannot choose one convenient group to bypass a concentrated source budget. This is conservative greedy selection, not an optimal packing search. An insufficient target returns `status:"insufficient"`, diagnostic `eligible` entries and an empty `selected` array. There is no silent weaker fallback. Even `status:"ready"` means only that these historical local policy constraints were met; it does not establish current reachability, independent human operators, Sybil resistance or release authority. Activation and subsequent fresh probes remain explicit operator actions. Automatic rotation is not implemented here.