Skip to content

feat(address-resolver): WebTransport bind/resolve with cert-hash (WT discovery foundation)#3302

Merged
0pcom merged 1 commit into
skycoin:developfrom
0pcom:feat/ar-wt-certhash
Jun 26, 2026
Merged

feat(address-resolver): WebTransport bind/resolve with cert-hash (WT discovery foundation)#3302
0pcom merged 1 commit into
skycoin:developfrom
0pcom:feat/ar-wt-certhash

Conversation

@0pcom

@0pcom 0pcom commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

First PR of the WebTransport carrier build (WT currently has 0 transports fleet-wide — never wired).

WebTransport has no CA — a dialing peer pins the server's self-signed cert by its SHA-256 hash. So to dial a peer's WT endpoint a visor must discover both the endpoint and that hash. This adds the address-resolver foundation:

  • LocalAddresses.CertHash (json:"cert_hash") — rides VisorData via the embedded LocalAddresses, so /resolve/wt returns the hash alongside the endpoint with no new VisorData plumbing.
  • BindWT(ctx, port, certHash) on the AR client (+ interface + mock), mirroring BindQUIC plus the hash; posts to /bind/wt.
  • /bind/wt route + bindWT handler → bindForType(WT).
  • types.WT added to the redis store's Bind/DelBind/Resolve/GetAll switches (memory store is already generic) — same shape as the QUIC store fix (fix(address-resolver): handle the QUIC type in the redis store (quic dead fleet-wide) #3300).
  • AddressSuite gains a WT bind→resolve round-trip asserting the cert hash survives (both backends).

Inert on its own — no visor calls BindWT yet (that's the WT accept-side PR). Deploys with the AR service.

Next PRs: visor WT accept (InitClient(WT) + wt_port + BindWT the advertised hash) → dial resolution (resolve endpoint+hash, pin) → multi-type autoconnect (WS+WT).

🤖 Generated with Claude Code

…discovery foundation)

WebTransport has no CA — a dialing peer must pin the server's self-signed cert
by its SHA-256 hash. For a browser (or any) visor to dial a peer's WT endpoint it
needs to discover both the endpoint AND that hash. This adds the AR side:

- LocalAddresses.CertHash (json cert_hash) — rides VisorData via the embedded
  LocalAddresses, so /resolve/wt returns the hash with the endpoint, no new
  VisorData plumbing.
- BindWT(ctx, port, certHash) on the AR client (+ interface + mock), mirroring
  BindQUIC plus the hash; posts to /bind/wt.
- /bind/wt route + bindWT handler → bindForType(WT).
- types.WT added to the redis store's Bind/DelBind/Resolve/GetAll switches
  (memory store is already generic), exactly like the QUIC fix.
- AddressSuite gains a WT bind→resolve round-trip asserting the cert hash
  survives (both backends).

Foundation only — no visor calls BindWT yet (that's the WT accept-side PR). Inert
until then; deploys with the AR service.
@0pcom
0pcom merged commit 4638b5e into skycoin:develop Jun 26, 2026
0pcom added a commit that referenced this pull request Jun 26, 2026
… with the AR (#3303)

Second PR of the WT carrier build. Every native visor now starts a WebTransport
server and advertises it so browser (wasm) visors — which can only reach a visor
over WS/WT/WebRTC — can dial it:

- initWTClient (module "wt", depends on the transport module like ws/quic) calls
  InitClient(types.WT). The WT server binds its own ephemeral UDP port, generates
  a self-signed cert, and serves HTTP/3 WebTransport.
- The WT client gets the AR (held as `any`, mirroring wsClient.ar). On serve it
  registers the endpoint + the cert's SHA-256 hash via BindWT (#3302) and keeps
  the entry alive with a 90s re-register (retry-with-backoff like stcpr/quic).

wtClient.Dial already guards a nil table (no crash on `tp add -t wt` before the
dial-resolution PR lands). Verified live: the WT server starts, binds UDP, mints
a cert, and BindWT retries (404 until the AR redeploys #3302); visor stays alive.

Next: dial-side resolution (resolve the peer's WT endpoint+hash from the AR and
pin) + WS/WT multi-type autoconnect.
0pcom added a commit that referenced this pull request Jun 26, 2026
…h via the AR (#3304)

Third PR of the WT carrier build. A native `tp add -t wt <pk>` (and the dial side
generally) had only the WTTable to find a peer's WebTransport endpoint, so a
native visor returned ErrWTEntryNotFound. Now, on a table miss, wtClient.Dial
resolves the peer's WT record from the address resolver — the endpoint
(https://host:port/skywire) AND the pinned self-signed cert hash that BindWT
(#3302/#3303) stored — and dials with the pin. Mirrors the native WS resolution.

Build-tagged like the WS resolver (real on !tinygo via addrresolver, no-op stub
on tinygo); browser build unaffected (table hit first, nil AR). End-to-end
native↔native WT needs #3302 (AR) + #3303 (accept) deployed so peers have WT
records; this is the dial half.
0pcom added a commit that referenced this pull request Jun 26, 2026
…lic visor (#3306)

A native visor makes BOTH stcpr and sudph to the same public visors (autoconnect.go)
for redundancy + path diversity. The browser analog is WS (TCP) + WT (QUIC); until
now the wasm autoconnect dialed only WS.

The autoconnect now dials both carriers to each public visor:
- WS rides the stcpr cmux port (resolve stcpr IP, ws://ip:port/) — unchanged.
- WT: new arDmsg.resolveWT fetches the peer's AR WT record (/resolve/wt) — the
  https://host:port/skywire endpoint + the pinned self-signed cert hash (stored by
  BindWT) — sets the wtTable entry and SaveTransport(WT). Needs the browser-native
  WT dial (#3305) and the AR WT records (#3302/#3303 deployed).

Counts DISTINCT visors now (a peer may hold both a WS and a WT transport), and a
peer with no WT record (404) just gets WS — graceful until the fleet registers WT.
Refreshes the committed wasm blob.
0pcom added a commit that referenced this pull request Jul 7, 2026
…discovery foundation) (#3302)

WebTransport has no CA — a dialing peer must pin the server's self-signed cert
by its SHA-256 hash. For a browser (or any) visor to dial a peer's WT endpoint it
needs to discover both the endpoint AND that hash. This adds the AR side:

- LocalAddresses.CertHash (json cert_hash) — rides VisorData via the embedded
  LocalAddresses, so /resolve/wt returns the hash with the endpoint, no new
  VisorData plumbing.
- BindWT(ctx, port, certHash) on the AR client (+ interface + mock), mirroring
  BindQUIC plus the hash; posts to /bind/wt.
- /bind/wt route + bindWT handler → bindForType(WT).
- types.WT added to the redis store's Bind/DelBind/Resolve/GetAll switches
  (memory store is already generic), exactly like the QUIC fix.
- AddressSuite gains a WT bind→resolve round-trip asserting the cert hash
  survives (both backends).

Foundation only — no visor calls BindWT yet (that's the WT accept-side PR). Inert
until then; deploys with the AR service.
0pcom added a commit that referenced this pull request Jul 7, 2026
… with the AR (#3303)

Second PR of the WT carrier build. Every native visor now starts a WebTransport
server and advertises it so browser (wasm) visors — which can only reach a visor
over WS/WT/WebRTC — can dial it:

- initWTClient (module "wt", depends on the transport module like ws/quic) calls
  InitClient(types.WT). The WT server binds its own ephemeral UDP port, generates
  a self-signed cert, and serves HTTP/3 WebTransport.
- The WT client gets the AR (held as `any`, mirroring wsClient.ar). On serve it
  registers the endpoint + the cert's SHA-256 hash via BindWT (#3302) and keeps
  the entry alive with a 90s re-register (retry-with-backoff like stcpr/quic).

wtClient.Dial already guards a nil table (no crash on `tp add -t wt` before the
dial-resolution PR lands). Verified live: the WT server starts, binds UDP, mints
a cert, and BindWT retries (404 until the AR redeploys #3302); visor stays alive.

Next: dial-side resolution (resolve the peer's WT endpoint+hash from the AR and
pin) + WS/WT multi-type autoconnect.
0pcom added a commit that referenced this pull request Jul 7, 2026
…h via the AR (#3304)

Third PR of the WT carrier build. A native `tp add -t wt <pk>` (and the dial side
generally) had only the WTTable to find a peer's WebTransport endpoint, so a
native visor returned ErrWTEntryNotFound. Now, on a table miss, wtClient.Dial
resolves the peer's WT record from the address resolver — the endpoint
(https://host:port/skywire) AND the pinned self-signed cert hash that BindWT
(#3302/#3303) stored — and dials with the pin. Mirrors the native WS resolution.

Build-tagged like the WS resolver (real on !tinygo via addrresolver, no-op stub
on tinygo); browser build unaffected (table hit first, nil AR). End-to-end
native↔native WT needs #3302 (AR) + #3303 (accept) deployed so peers have WT
records; this is the dial half.
0pcom added a commit that referenced this pull request Jul 7, 2026
…lic visor (#3306)

A native visor makes BOTH stcpr and sudph to the same public visors (autoconnect.go)
for redundancy + path diversity. The browser analog is WS (TCP) + WT (QUIC); until
now the wasm autoconnect dialed only WS.

The autoconnect now dials both carriers to each public visor:
- WS rides the stcpr cmux port (resolve stcpr IP, ws://ip:port/) — unchanged.
- WT: new arDmsg.resolveWT fetches the peer's AR WT record (/resolve/wt) — the
  https://host:port/skywire endpoint + the pinned self-signed cert hash (stored by
  BindWT) — sets the wtTable entry and SaveTransport(WT). Needs the browser-native
  WT dial (#3305) and the AR WT records (#3302/#3303 deployed).

Counts DISTINCT visors now (a peer may hold both a WS and a WT transport), and a
peer with no WT record (404) just gets WS — graceful until the fleet registers WT.
Refreshes the committed wasm blob.
@0pcom
0pcom deleted the feat/ar-wt-certhash branch July 25, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant