@hisoka-io/anon-rpc-worker 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +142 -30
- package/dist/anon-rpc-worker.js +2171 -235
- package/dist/anon-rpc-worker.js.keccak256 +1 -1
- package/dist/anon-rpc-worker.provenance.json +19 -8
- package/package.json +3 -3
- package/snapshot/capabilities.json +7 -4
- package/snapshot/nox-snapshot.json +6 -3
- package/snapshot/nox-snapshot.json.keccak256 +1 -1
- package/src/config.ts +73 -0
- package/src/core.ts +43 -4
- package/src/embed-modules.d.ts +16 -0
- package/src/errors.ts +7 -1
- package/src/fetch-map.ts +18 -1
- package/src/tls/channel.ts +183 -0
- package/src/tls/module.ts +98 -0
- package/src/tls/pool.ts +581 -0
- package/src/tls/transport.ts +279 -0
- package/src/tls/tunnel.ts +360 -0
- package/src/worker.ts +12 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@hisoka-io/anon-rpc-worker`.
|
|
4
4
|
|
|
5
|
+
## 0.4.1 (2026-10-25)
|
|
6
|
+
|
|
7
|
+
Built on `@hisoka-io/nox-client` 0.9.0 and `@hisoka-io/nox-wasm` 0.3.0. Every 0.4.0 config stays valid.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- TLS spares: defaults `tlsSpares` 2 (was 1) and `tlsSpareTtlMs` 12,000 (was 20,000). The upstream RPC hosts close
|
|
12
|
+
an idle TLS connection 15.0 s after the handshake, so a 20 s spare could be dead when used: one wasted exchange
|
|
13
|
+
and a `tls.retry` on a new session. Measured from India on 2026-10-15 at a 15 s call gap: 13 of 30 calls retried
|
|
14
|
+
with a 20 s TTL, none of 70 with 12 s.
|
|
15
|
+
- A spare is refreshed before its TTL while its host was called within the last minute: the replacement opens at
|
|
16
|
+
once 1.5-2 s before the TTL, and a host holds at most `tlsSpares` spares plus the one being replaced. Past that
|
|
17
|
+
window an expired spare is not replaced, as before. The one-minute window bounds how far apart the refresh chain
|
|
18
|
+
can link a wallet's calls to one host at low traffic (about 20 s in 0.4.0).
|
|
19
|
+
- A call that finds no spare while a spare's handshake for its host has run for half the handshake median waits for
|
|
20
|
+
that handshake instead of opening another session; if that handshake fails, the call opens its own.
|
|
21
|
+
|
|
22
|
+
- Embedded nox-wasm 0.3.0: nox-crypto at nox `dff8ebe`, which refuses a reply body shorter than 33 bytes with an
|
|
23
|
+
error instead of trapping the module.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Log event `tls.call` (info), one per tunnel hop: `path` (`spare`, `inline`, `waited`, `reused` or `retry`),
|
|
28
|
+
`openMs`, `spareAgeMs` (spares only), `exchangeMs`, `retries`, `ok`.
|
|
29
|
+
|
|
30
|
+
## Stable specifier (2026-10-13)
|
|
31
|
+
|
|
32
|
+
Deployment record and tooling; the published package stays 0.4.0.
|
|
33
|
+
|
|
34
|
+
- Stable specifier `0x35133267A8F7EC35046a83DCA0f4Ca8A6e1b7A9A` deployed on Ethereum Sepolia on 2026-10-13,
|
|
35
|
+
pointing at 0.4.0: the reference `WorkerSpecifier` of ethereum/anon-rpc, which follows each worker release
|
|
36
|
+
through `setWorker`. The per-release immutable specifiers stay as pins (`specifier/README.md`).
|
|
37
|
+
- `specifier`: `pnpm set-worker`, a dry-run-first tool for the `setWorker` update.
|
|
38
|
+
|
|
39
|
+
## 0.4.0 (2026-10-10)
|
|
40
|
+
|
|
41
|
+
Built on `@hisoka-io/nox-client` 0.8.0. Every 0.3.0 config stays valid; with the new defaults, https calls go
|
|
42
|
+
through TLS tunnels and need exits on nox rc.9 or later with `[tunnel]` enabled.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- End-to-end TLS (README "End-to-end TLS"): the worker runs TLS 1.3 and 1.2 itself (rustls with ring in a second
|
|
47
|
+
embedded WebAssembly module, `packages/nox-tls`, compiled at boot) and exits relay TLS records over one TCP
|
|
48
|
+
connection per tunnel. Requests and responses stay end-to-end encrypted between the worker and the RPC provider,
|
|
49
|
+
and the mixnet keeps the sender anonymous; the exit sees the provider's host name, timing and sizes.
|
|
50
|
+
- Config `tls` (`"required"` default, `"preferred"`, `"off"`), `tlsSession` (`"per-call"` default, `"keep-alive"`),
|
|
51
|
+
`tlsSpares`, `tlsSpareTtlMs`, `tlsKeepAliveMs`, `tlsMaxCallsPerSession`, `tlsOpenTimeoutMs`, `tlsCopyAfterMs`,
|
|
52
|
+
`tlsGapMs`, `tlsMaxCopies`.
|
|
53
|
+
- Per-call sessions with handshaken spares per recently used host, opened in the background after the first wallet
|
|
54
|
+
call settled; keep-alive sessions as an opt-in. Resumption and 0-RTT stay off; ALPN is `http/1.1`.
|
|
55
|
+
- HTTP/1.1 in the worker: caller headers keep their order and duplicates; framing, hop-by-hop and `User-Agent`
|
|
56
|
+
headers are set by the encoder alone; CR, LF and NUL are refused; a whole JSON request is padded to a size bucket;
|
|
57
|
+
responses are complete only by HTTP framing or TLS close_notify.
|
|
58
|
+
- Call code `permission-denied` (the tunnel exit refuses the destination). Log events `boot.tls`, `tls.open`,
|
|
59
|
+
`tls.fallback`, `tls.reject`, `tls.exit.skipped`, `tls.retry`.
|
|
60
|
+
- Build: `nox-tls` built with the pinned toolchain and a pinned clang (`DEBIAN_SNAPSHOT`, `CLANG_PACKAGE_VERSION`,
|
|
61
|
+
`CLANG_VERSION` in `scripts/toolchain.env`); provenance records its digest, clang, the webpki-roots version and
|
|
62
|
+
release date, and `extraRoots`. `build.mjs --extra-root` and `build-test-worker.mjs --extra-root` for test beds;
|
|
63
|
+
`build-worker.sh --release` refuses a bundle with extra roots.
|
|
64
|
+
- Release gate: `verify-snapshot.mjs --release` requires an exit whose capability hints carry `tunnel_v1`.
|
|
65
|
+
|
|
5
66
|
## 0.3.0 (2026-10-06)
|
|
6
67
|
|
|
7
68
|
Every 0.2.0 config stays valid. Works against entries on nox 0.4.0-rc.6 and on nodes with claim protocol v2.
|
package/README.md
CHANGED
|
@@ -3,25 +3,27 @@
|
|
|
3
3
|
The Nox mixnet worker for the [anon-rpc](https://github.com/ethereum/anon-rpc) standard: one classic-script
|
|
4
4
|
bundle that a wallet's anon-rpc harness loads and pins by its keccak-256 hash.
|
|
5
5
|
|
|
6
|
-
The worker dials Nox entry nodes over KPS (`anonRpcWorker.kps`)
|
|
7
|
-
through the mixnet, and an exit
|
|
8
|
-
|
|
6
|
+
The worker dials Nox entry nodes over KPS (`anonRpcWorker.kps`) and sends each `fetch` call as Sphinx packets
|
|
7
|
+
through the mixnet. It runs TLS with the RPC provider itself, and an exit relays the encrypted records (the exit
|
|
8
|
+
performs the HTTP request with `tls: "off"`, or as the `"preferred"` fallback); replies return over single-use
|
|
9
|
+
reply blocks (SURBs). Who the nodes are comes from NoxRegistry: a snapshot pinned inside the bundle is the floor, and after
|
|
9
10
|
ready the worker reads the registry through the mixnet itself. Where the nodes are (IP, port, KPS certhash) is
|
|
10
11
|
looked up at run time, so operators change IPs whenever they like and new nodes join without a new bundle. A cold
|
|
11
12
|
boot needs no seed server and no public RPC; the bundle reaches the network only through the harness's KPS dialer.
|
|
12
13
|
|
|
13
|
-
What each party sees: the entry node sees the wallet's IP address and encrypted packets
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
What each party sees: the entry node sees the wallet's IP address and encrypted packets. With end-to-end TLS (the
|
|
15
|
+
default, `tls: "required"`), requests and responses stay end-to-end encrypted between the worker and the RPC
|
|
16
|
+
provider, and the mixnet keeps the sender anonymous: the worker runs TLS itself and the exit relays TLS ciphertext
|
|
17
|
+
over one TCP connection, seeing the provider's host name, timing and sizes (see [End-to-end TLS](#end-to-end-tls)).
|
|
16
18
|
|
|
17
19
|
## Layout
|
|
18
20
|
|
|
19
21
|
| Path | Contents |
|
|
20
22
|
|---|---|
|
|
21
|
-
| `src/` | The worker: `worker.ts` (bundle entry), `core.ts` (boot, readiness, accept loop, deadlines), `config.ts`, `fetch-map.ts` (`fetch` to Nox `HttpRequest`), `jsonrpc.ts`, `errors.ts`, `log.ts`, `storage.ts`, `spec-types.ts` (anon-rpc SPEC 0.3.2 worker types) |
|
|
23
|
+
| `src/` | The worker: `worker.ts` (bundle entry), `core.ts` (boot, readiness, accept loop, deadlines), `config.ts`, `fetch-map.ts` (`fetch` to Nox `HttpRequest`, redirects, gzip), `tls/` (end-to-end TLS: `tunnel.ts` one exit tunnel, `channel.ts` TLS and HTTP/1.1 over it, `pool.ts` sessions, spares and exit choice, `transport.ts` the per-hop transport and error mapping, `module.ts` the TLS module bindings), `jsonrpc.ts`, `errors.ts`, `log.ts`, `storage.ts`, `spec-types.ts` (anon-rpc SPEC 0.3.2 worker types) |
|
|
22
24
|
| `snapshot/` | The pinned registry snapshot, its keccak-256, JSON Schema and reviewed capability hints, and the discovery bootstrap (`nox-bootstrap.json`) |
|
|
23
25
|
| `scripts/` | Snapshot generator and verifier, reproducible WASM and bundle build, hashing, provenance |
|
|
24
|
-
| `specifier/` | Worker specifier contracts (Foundry)
|
|
26
|
+
| `specifier/` | Worker specifier contracts (Foundry), a read-only deploy planner and inspector, and the `setWorker` update tool for the stable address |
|
|
25
27
|
| `e2e/` | End-to-end test bed: a local Nox mesh with `nox-kps` sidecars, the reference harness in headless Chromium |
|
|
26
28
|
|
|
27
29
|
## Configuration
|
|
@@ -51,6 +53,16 @@ optional, and every config valid for 0.1.0 stays valid:
|
|
|
51
53
|
| `bootRetryMaxMs` | integer | 60,000 | 5,000-300,000 |
|
|
52
54
|
| `topologySources` | integer | 2 | 1-4 |
|
|
53
55
|
| `warmup` | boolean | false | one echo through a full route before ready |
|
|
56
|
+
| `tls` | `"required"`, `"preferred"`, `"off"` | `"required"` | end-to-end TLS, see [End-to-end TLS](#end-to-end-tls) |
|
|
57
|
+
| `tlsSession` | `"per-call"`, `"keep-alive"` | `"per-call"` | one TLS session per call, or sessions shared by the calls to one host |
|
|
58
|
+
| `tlsSpares` | integer | 2 | 0-4 handshaken spare sessions per recently used host (per-call mode; 1 in 0.4.0) |
|
|
59
|
+
| `tlsSpareTtlMs` | integer | 12,000 | 1,000-50,000; an unused spare is closed this long after its handshake (20,000 in 0.4.0) |
|
|
60
|
+
| `tlsKeepAliveMs` | integer | 30,000 | 1,000-50,000; how long a keep-alive session serves calls after it opened |
|
|
61
|
+
| `tlsMaxCallsPerSession` | integer | 100 | 1-1,000 calls per keep-alive session |
|
|
62
|
+
| `tlsOpenTimeoutMs` | integer | 5,000 | 1,000-30,000; an exit that does not answer a tunnel open in this long is skipped for 10 minutes |
|
|
63
|
+
| `tlsCopyAfterMs` | integer | 2,500 | 500-30,000; another copy of the current tunnel exchange when nothing arrived for this long (raised to the observed p95) |
|
|
64
|
+
| `tlsGapMs` | integer | 800 | 100-10,000; another copy when a gap in the reply stream stays open this long |
|
|
65
|
+
| `tlsMaxCopies` | integer | 3 | 1-5 copies per exchange for silence or a gap |
|
|
54
66
|
|
|
55
67
|
An unknown key, a wrong type or an out-of-range value fails the boot with `bad-config`, naming the field.
|
|
56
68
|
|
|
@@ -64,6 +76,82 @@ Examples:
|
|
|
64
76
|
{ gateways: ["100.56.0.72:15005:uEiBVDwIs40bsslDkM-BYb2AOHw3PHe70_bj5U_09r7vdIQ"] }
|
|
65
77
|
```
|
|
66
78
|
|
|
79
|
+
## End-to-end TLS
|
|
80
|
+
|
|
81
|
+
The worker carries a TLS client (rustls with ring, compiled to WebAssembly, `packages/nox-tls`) and runs TLS with
|
|
82
|
+
the RPC provider itself. A Nox exit that advertises `tunnel_v1` holds one TCP connection to the provider per tunnel
|
|
83
|
+
and relays TLS records both ways (`ServiceRequest::TunnelV1`, nox `docs/tunnel.md`). Requests and responses stay
|
|
84
|
+
end-to-end encrypted between the worker and the provider, and the mixnet keeps the sender anonymous; the exit relays
|
|
85
|
+
ciphertext.
|
|
86
|
+
|
|
87
|
+
What the exit learns: the provider's host name (from SNI, its own DNS lookup and the IP) and port 443; when a tunnel
|
|
88
|
+
opens and closes; the number, size and timing of TLS records in both directions (a whole JSON request is padded to
|
|
89
|
+
512 B, 1 KiB, 4 KiB, 16 KiB, then 16 KiB steps, so calls of similar size share one record size; response sizes stay
|
|
90
|
+
visible, and larger buckets are a later milestone); the TLS client fingerprint, shared by every worker of one
|
|
91
|
+
version; in keep-alive mode, which calls share a session. The URL path and query (API keys included), headers,
|
|
92
|
+
JSON-RPC methods and parameters, signed transactions, response content and the sender stay hidden from it, and the
|
|
93
|
+
worker detects any change or truncation of a response.
|
|
94
|
+
|
|
95
|
+
`tls`:
|
|
96
|
+
|
|
97
|
+
| `tls` | https on port 443, host by name | http, another port, or an IP-literal host | no known exit advertises `tunnel_v1` |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| `"required"` (default) | tunnel | rejects `unsupported` | rejects `network-error` |
|
|
100
|
+
| `"preferred"` | tunnel | exit `HttpRequest` (logged `tls.fallback`) | exit `HttpRequest` (logged `tls.fallback`) |
|
|
101
|
+
| `"off"` | exit `HttpRequest` | exit `HttpRequest` | exit `HttpRequest` |
|
|
102
|
+
|
|
103
|
+
The transport is chosen before any byte of a call leaves the worker, from the URL and the capability data, and a
|
|
104
|
+
call that started on a tunnel never continues on `HttpRequest`, redirects included. The capabilities of pinned
|
|
105
|
+
members are the hints in the bundle's snapshot (`snapshot/capabilities.json`), so tunnel exits change, and an exit
|
|
106
|
+
stops counting as one, only with a new bundle; with discovery, other members' capabilities come from the seed's
|
|
107
|
+
liveness data. `"preferred"` uses plaintext whenever no known exit advertises `tunnel_v1`; `"required"` keeps every
|
|
108
|
+
call end-to-end encrypted. An exit that answers `Disabled`, or no tunnel open within `tlsOpenTimeoutMs`, is skipped
|
|
109
|
+
for 10 minutes and the open moves to another tunnel exit; skipped exits still count as tunnel exits, so with every one
|
|
110
|
+
skipped a call fails with `network-error` under both settings.
|
|
111
|
+
|
|
112
|
+
Session modes:
|
|
113
|
+
|
|
114
|
+
- `"per-call"` (default): every call has its own TLS session and `Connection: close`; no resumption, no 0-RTT, a
|
|
115
|
+
fresh key share each time. `tlsSpares` keeps handshaken, unused sessions for hosts the wallet called, opened in
|
|
116
|
+
the background (only after the first wallet call settled) on a random tunnel exit after an exponential delay
|
|
117
|
+
(mean 2 s), each serving one call. While a host was called in the last minute, a spare's replacement opens at
|
|
118
|
+
once 1.5-2 s before its TTL, so a spare is ready across idle gaps; after that an expired spare is not replaced. A
|
|
119
|
+
call that finds no spare while a spare's handshake is halfway done waits for it. Each call travels in its own
|
|
120
|
+
session; at low traffic the timing of a spare's open can still chain sequential calls of one wallet to one host,
|
|
121
|
+
which weakens as traffic grows. The refresh opens each replacement about 10-10.5 s after the spare it replaces,
|
|
122
|
+
so that chain can span calls up to 1 minute apart (about 20 s in 0.4.0); `tlsSpares: 0` turns spares and
|
|
123
|
+
refreshes off.
|
|
124
|
+
- `"keep-alive"`: calls to one host share a session on one exit for `tlsKeepAliveMs`, up to 4 sessions per host.
|
|
125
|
+
The exit links the calls of a session, and the provider sees them on one TLS connection.
|
|
126
|
+
|
|
127
|
+
Latency: a call on a ready session (a spare, or a warm keep-alive session) costs what the exit `HttpRequest` path
|
|
128
|
+
costs. The first call to a host, and calls beyond the ready spares, add one mixnet round trip for the handshake.
|
|
129
|
+
|
|
130
|
+
Measured on 2026-10-10 with the anon-rpc reference bench (ethereum/anon-rpc `impl/site/bench`; worker 0.4.0 with
|
|
131
|
+
its defaults, `eth_getBalance` against a public Ethereum RPC, 39 warm calls per vantage, 40 of 40 calls answered):
|
|
132
|
+
warm median 381 ms from the US (p90 795 ms) and 636 ms from India (p90 1,294 ms); cold boot, median of four,
|
|
133
|
+
1.5 s from the US and 2.8 s from India. The testnet fleet runs a mix delay of 40 ms per hop, and the worker claims
|
|
134
|
+
replies with claim protocol v2.
|
|
135
|
+
|
|
136
|
+
Certificates are checked against the Mozilla root store compiled into the bundle (webpki-roots; the version and its
|
|
137
|
+
release date are in the provenance, refreshed with every worker release), with the host name and the device clock;
|
|
138
|
+
a wrong clock fails with a message that names it. Each worker release ships its root store, so wallets keep their
|
|
139
|
+
roots current by moving to new worker releases. Resends of a tunnel exchange go to the same exit through another
|
|
140
|
+
entry and mix and carry identical bytes, which the exit writes once; after a lost tunnel only reads that may be resent
|
|
141
|
+
run again on a new tunnel.
|
|
142
|
+
|
|
143
|
+
`tls` governs the wallet's calls. The chain check's registry reads (discovery) use the exit `HttpRequest` path: public
|
|
144
|
+
chain data, accepted only when a quorum of exit and provider pairs agrees byte for byte.
|
|
145
|
+
|
|
146
|
+
Next milestones: registry reads through tunnels as well, Encrypted Client Hello for providers that publish it, and
|
|
147
|
+
a hybrid post-quantum key exchange (X25519MLKEM768).
|
|
148
|
+
|
|
149
|
+
Errors: `permission-denied` (the exit refuses the destination), `network-error` (no tunnel exit, a certificate
|
|
150
|
+
failure, the exit cannot reach the provider), `protocol-error` (TLS or HTTP checks failed, a truncated response),
|
|
151
|
+
`too-large`, `timeout`. Log events: `boot.tls`, `tls.open` (debug: handshake time and CPU), `tls.fallback{reason}`,
|
|
152
|
+
`tls.reject{code}`, `tls.exit.skipped`, `tls.retry`, `tls.call{path, openMs, spareAgeMs, exchangeMs, retries, ok}`; none
|
|
153
|
+
carries bytes or a host name.
|
|
154
|
+
|
|
67
155
|
## Discovery (identity from chain, location at run time)
|
|
68
156
|
|
|
69
157
|
The bundle pins `snapshot/nox-bootstrap.json` (`nox-anon-rpc-bootstrap/1`) next to the snapshot, both covered by
|
|
@@ -90,9 +178,9 @@ worker first saw it, and a route holds at most one such member. Checks repeat ev
|
|
|
90
178
|
served fingerprint differs or every entry failed. Log events: `discovery.verified`, `discovery.disagreement`,
|
|
91
179
|
`discovery.incomplete`, `discovery.rejected`, `discovery.probation`, `discovery.floor`, `discovery.failed`.
|
|
92
180
|
|
|
93
|
-
What each party learns: RPC providers see exit IPs and public registry reads, never the wallet's address.
|
|
94
|
-
|
|
95
|
-
and the snapshot floor bound what a forged answer can change. If every chain path fails, the worker keeps working
|
|
181
|
+
What each party learns: RPC providers see exit IPs and public registry reads, never the wallet's address. Registry
|
|
182
|
+
reads use the exit `HttpRequest` path, so the two-exit quorum is attested by the exits; registry reads through
|
|
183
|
+
tunnels are the next milestone. Probation and the snapshot floor bound what a forged answer can change. If every chain path fails, the worker keeps working
|
|
96
184
|
on the snapshot floor.
|
|
97
185
|
|
|
98
186
|
Operators move by sending `updateUrl` / `updateMetadataUrl` from the node key (self-service, about 0.00002 ETH);
|
|
@@ -125,19 +213,36 @@ were read at, and when each member outside the snapshot was first seen. At most
|
|
|
125
213
|
days. A cache that fails any check (another registry, too many records, one malformed record) is ignored whole, and
|
|
126
214
|
a learned address is used only after the node's `/metadata.json` names the same member.
|
|
127
215
|
|
|
128
|
-
## Release 0.
|
|
216
|
+
## Release 0.4.0
|
|
129
217
|
|
|
130
218
|
| | |
|
|
131
219
|
|---|---|
|
|
132
|
-
| `workerHash` (keccak-256 of `dist/anon-rpc-worker.js`) | `
|
|
133
|
-
| Size |
|
|
134
|
-
|
|
|
135
|
-
|
|
|
220
|
+
| `workerHash` (keccak-256 of `dist/anon-rpc-worker.js`) | `0xd8bef626ec8511d7a468bca5137880680ca2da20d2ea4d9c981a2a3d0c86ba1e` |
|
|
221
|
+
| Size | 1,820,668 bytes; 649,310 bytes with `gzip -9 -n` |
|
|
222
|
+
| sha256 | `4b7b69cd6f45c364cd87ff75f5f8cacdebea2cd852d5384c47215bb88d56f35a` |
|
|
223
|
+
| Specifier, stable address | Ethereum Sepolia `0x35133267A8F7EC35046a83DCA0f4Ca8A6e1b7A9A` (`WorkerSpecifier`, owner-updatable: it follows the current release; trust model in [specifier/README.md](./specifier/README.md#deployments)) |
|
|
224
|
+
| Specifier, 0.4.0 pin | Ethereum Sepolia `0xDf5Db854BA75B52a4bF1a250a93D8d25cB982b2d` (`ImmutableWorkerSpecifier`, resolvers in [specifier/README.md](./specifier/README.md#deployments)) |
|
|
225
|
+
| Snapshot | NoxRegistry at Arbitrum Sepolia block 316,207,920, keccak-256 `0x1090f071…014681` (the 0.3.0 registry state with the exits' `tunnel_v1` hints) |
|
|
136
226
|
| nox-wasm module | 188,708 bytes, sha256 `5171416972ea4499fd4b4ea8bcadf002f4c10f10ed42cafb0d5a426121da9c4f` |
|
|
137
|
-
|
|
|
227
|
+
| nox-tls module | 593,738 bytes, sha256 `f74e8cfbed7be33ef1a9158b7b66e454b4ca0849bcb8956487fd13da47e4f4d2` |
|
|
228
|
+
| Root store | webpki-roots 1.0.9 (released 2026-07-18), 121 Mozilla root certificates |
|
|
229
|
+
| Built from | `nox-sdk` commit `fee8d69861a112c65f0cbb0b848a7726ec12f01a`, rustc 1.95.0, clang 19.1.7, wasm-pack 0.13.1, wasm-bindgen 0.2.114, binaryen 117, esbuild 0.27.3 |
|
|
230
|
+
|
|
231
|
+
The npm package carries the bundle and provenance file of the first pinned container build.
|
|
232
|
+
|
|
233
|
+
Each release publishes its own immutable specifier and then repoints the stable address with one `setWorker`
|
|
234
|
+
transaction from its owner ([specifier/README.md](./specifier/README.md#updating-the-stable-address-setworker)), so
|
|
235
|
+
wallets and listings that pin the stable address move to the new worker with nothing to change on their side.
|
|
236
|
+
|
|
237
|
+
Release 0.3.0: `workerHash` `0x24604525d220bcc7e39f2dbc22966a814600a63ada51baafad5dd0bcc1d28549` (961,799 bytes, commit
|
|
238
|
+
`6bddd85`), specifier `0x29B51ca9Ad80E9c0B0D111C8748E6a7908b82eDB`. Release 0.2.0: `workerHash`
|
|
239
|
+
`0x0a58f9915f686950072a4786249d396ecbf2194a39ac835effe8ea1a7c76f324` (924,847 bytes, commit `ef019a7`), specifier
|
|
240
|
+
`0x29b4a6A8Cc11769531854d87f9F33EC63Efe8fe6`.
|
|
138
241
|
|
|
139
|
-
Two builds in fresh containers of the pinned images (at two different paths)
|
|
140
|
-
|
|
242
|
+
Two builds in fresh containers of the pinned images (at two different paths) give these exact bytes
|
|
243
|
+
(`scripts/verify-reproducible.sh --ref fee8d69`), on an x86_64 workstation and again on the x86_64 CI runner
|
|
244
|
+
(job "Worker bundle (two container builds)"). The aarch64 reproduction is the next check: the toolchain pins carry
|
|
245
|
+
aarch64 digests for it. Run the script from the repository root as
|
|
141
246
|
`packages/anon-rpc-worker/scripts/verify-reproducible.sh` (or `pnpm verify:reproducible` inside the package); host builds (`--native`,
|
|
142
247
|
`--local`) use rustc 1.95.0, so set `RUSTUP_TOOLCHAIN=1.95.0` when the host default differs.
|
|
143
248
|
|
|
@@ -145,15 +250,16 @@ bytes (`scripts/verify-reproducible.sh --native`). Run it from the repository ro
|
|
|
145
250
|
|
|
146
251
|
- `dist/anon-rpc-worker.js`: a single IIFE built by esbuild (target es2022, not minified, so it can be read
|
|
147
252
|
and audited). Every module is inlined: the worker source, the `@hisoka-io/nox-client` sources, the
|
|
148
|
-
`nox-wasm` WebAssembly
|
|
149
|
-
byte for byte as one string.
|
|
253
|
+
`nox-wasm` and `nox-tls` WebAssembly modules (base64), the pinned registry snapshot and the discovery bootstrap,
|
|
254
|
+
each embedded byte for byte as one string. The TLS module is compiled at boot, off the first call's path.
|
|
150
255
|
The bundle has no imports and never loads code or WebAssembly by URL. The SDK's ambient `fetch` and
|
|
151
256
|
`WebSocket` defaults are replaced at build time by a stand-in that fails closed, and the build checks the
|
|
152
257
|
output for any other network API.
|
|
153
258
|
- `dist/anon-rpc-worker.js.keccak256`: the Ethereum keccak-256 of those exact bytes, the value a worker
|
|
154
259
|
specifier pins as `workerHash()`.
|
|
155
260
|
- `dist/anon-rpc-worker.provenance.json`: source commit, toolchain versions, input digests (snapshot, bootstrap,
|
|
156
|
-
WASM, lockfile) and
|
|
261
|
+
both WASM modules, lockfile), the compiled-in root store (webpki-roots version and release date, and
|
|
262
|
+
`extraRoots`, 0 in every release) and output digests.
|
|
157
263
|
|
|
158
264
|
## Pinned registry snapshot
|
|
159
265
|
|
|
@@ -172,13 +278,16 @@ The committed release snapshot is of block 316207920 (finalized, read and re-ver
|
|
|
172
278
|
providers of different organisations). It carries the KPS addresses nox-1, nox-2 and nox-8 publish, so the worker
|
|
173
279
|
finds its entries from the snapshot alone, and the default anchors and the chain check add any later moves.
|
|
174
280
|
The release gate names the seven mix and exit nodes, which serve the mixnet without a KPS listener, with
|
|
175
|
-
`--allow-missing-kps
|
|
281
|
+
`--allow-missing-kps`, and requires an exit whose capability hints carry `tunnel_v1`: the hints in
|
|
282
|
+
`snapshot/capabilities.json` come from the seed topology of 2026-10-10 (fleet on nox 0.4.0-rc.9), where the three
|
|
283
|
+
exits advertise `tunnel_v1`. The registry state at that block is the same as in 0.3.0 (fingerprint
|
|
284
|
+
`0x14ca3b69…7c75`); with the new hints the snapshot's keccak-256 is `0x1090f071…014681`.
|
|
176
285
|
|
|
177
286
|
```bash
|
|
178
287
|
pnpm --filter @hisoka-io/nox-client build
|
|
179
288
|
node scripts/make-snapshot.mjs --rpc <arbitrum-sepolia-rpc> --rpc <second-rpc> [--block <n>]
|
|
180
289
|
node scripts/verify-snapshot.mjs --offline # schema, hashes, SDK-derived fields
|
|
181
|
-
node scripts/verify-snapshot.mjs --rpc <rpc> --rpc <rpc> --release # chain re-read
|
|
290
|
+
node scripts/verify-snapshot.mjs --rpc <rpc> --rpc <rpc> --release # chain re-read, KPS addresses, a tunnel exit
|
|
182
291
|
```
|
|
183
292
|
|
|
184
293
|
## Building and checking the hash
|
|
@@ -190,10 +299,11 @@ node packages/anon-rpc-worker/scripts/hash.mjs packages/anon-rpc-worker/dist/ano
|
|
|
190
299
|
```
|
|
191
300
|
|
|
192
301
|
`scripts/toolchain.env` pins every tool that shapes the bytes: the Node and Rust container images by digest,
|
|
193
|
-
rustc, wasm-pack, wasm-bindgen and binaryen (with sha256 digests of their release archives)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
302
|
+
rustc, wasm-pack, wasm-bindgen and binaryen (with sha256 digests of their release archives), and clang with llvm-ar
|
|
303
|
+
for ring's C sources in `nox-tls` (Debian bookworm packages at a snapshot.debian.org timestamp, the same on x86_64
|
|
304
|
+
and aarch64); pnpm comes from the repository's `packageManager` field and esbuild from the lockfile.
|
|
305
|
+
`scripts/build-wasm.sh` follows the tor-js recipe: path remapping, a `RUSTC_WRAPPER` that makes cargo's metadata
|
|
306
|
+
host-independent, `--locked`, and a pinned `wasm-opt` on `PATH`.
|
|
197
307
|
|
|
198
308
|
`scripts/verify-reproducible.sh` builds a commit twice from clean `git archive` exports in fresh containers
|
|
199
309
|
of the pinned images, at two different paths, and compares the WASM, bundle and provenance digests
|
|
@@ -202,12 +312,14 @@ container builds and requires all three to match; `--local --bwrap` runs the two
|
|
|
202
312
|
namespaces at the container paths, for hosts without a container runtime.
|
|
203
313
|
|
|
204
314
|
For a test bed, `node scripts/build.mjs --snapshot <file>` embeds another canonical snapshot (for example a
|
|
205
|
-
local mesh); the provenance records which snapshot a
|
|
315
|
+
local mesh) and `--extra-root <ca.der>` a test CA next to the Mozilla roots; the provenance records which snapshot a
|
|
316
|
+
bundle carries and how many extra roots. The release build (`build-worker.sh --release`) refuses a bundle with an
|
|
317
|
+
extra root (`provenance.mjs --release`).
|
|
206
318
|
|
|
207
319
|
## Tests
|
|
208
320
|
|
|
209
321
|
```bash
|
|
210
|
-
pnpm --filter @hisoka-io/anon-rpc-worker test # worker, SDK integration over an in-memory KPS network, build
|
|
322
|
+
pnpm --filter @hisoka-io/anon-rpc-worker test # worker, SDK integration over an in-memory KPS network, build, e2e bed unit tests
|
|
211
323
|
pnpm --filter @hisoka-io/anon-rpc-specifier test # Foundry and harness-path tests (forge, anvil)
|
|
212
324
|
```
|
|
213
325
|
|