@hisoka-io/anon-rpc-worker 0.0.0-stage → 0.2.0

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 ADDED
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@hisoka-io/anon-rpc-worker`.
4
+
5
+ ## 0.2.0 (2026-10-06)
6
+
7
+ Built on `@hisoka-io/nox-client` 0.6.0 (`kps.discovery`). Every 0.1.0 config stays valid.
8
+
9
+ ### Added
10
+
11
+ - Discovery (S1): identity from the snapshot and NoxRegistry, location looked up at run time. The bundle pins a
12
+ `nox-anon-rpc-bootstrap/1` file next to the snapshot: default anchors nox-1, nox-2 and nox-8 on their Elastic IPs,
13
+ the registry implementation, two keyless Arbitrum Sepolia RPC providers of different organisations and the
14
+ discovery policy. An empty config boots on those anchors.
15
+ - Chain checks through the mixnet after ready: two exits to two providers at one finalized block, used on
16
+ byte-identical agreement only; location updates, removals, new members on probation (1 per route, 14 days).
17
+ - Config: `bridges` (only addresses ever dialled), `registryRpcUrls`, `chainQuorum`, `discovery`
18
+ (`"chain"` / `"snapshot"`), reserved `trust` and `checkpoint`.
19
+ - Learned-anchor cache `nox/v1/anchors` (public chain data, at most 32 entries, 30 days), validated whole and
20
+ confirmed through `/metadata.json` before use.
21
+ - Build: `--bootstrap`, bootstrap digest in the build record and provenance; the test-bed build writes a bed
22
+ bootstrap (local providers, anchors, short check interval).
23
+
24
+ - Release snapshot of NoxRegistry at Arbitrum Sepolia block 316207920 (10 members; nox-1, nox-2 and nox-8 publish
25
+ their KPS addresses), read and re-verified byte for byte through two RPC providers.
26
+ - `verify-reproducible.sh --native` (a third build with host tools next to the two container builds) and `--bwrap`
27
+ (host builds at the container paths for hosts without a container runtime).
28
+
29
+ ### Changed
30
+
31
+ - `gateways` no longer has to be a pinned member's published address; gateways are tried first in place of the
32
+ default anchors instead of restricting entries. After the gateways, the worker may dial the default anchors and
33
+ other published entries. Wallets that used `gateways` to keep traffic on chosen addresses switch to `bridges`,
34
+ which the worker treats as the only addresses it ever dials.
35
+ - A gateway or bridge must answer `/metadata.json` with the node it serves (nox-kps `node_address`), and that node
36
+ must be a member in the bundle's snapshot.
37
+
38
+ ### Fixed
39
+
40
+ - Reproducible WASM across checkout paths: `reproducible-rustc.sh` strips the cargo home, sysroot and workspace
41
+ prefixes only at the start of a source path. A checkout at a path such as `/src` used to rewrite the
42
+ `registry/src/` part of every dependency path, which changed each crate's `-C metadata` and the module bytes.
43
+
44
+ ## 0.1.0
45
+
46
+ First version of the Nox mixnet worker for the anon-rpc standard (SPEC 0.3.2), built on
47
+ `@hisoka-io/nox-client` 0.5.0 in KPS mode.
48
+
49
+ ### Added
50
+
51
+ - The worker: boots from the NoxRegistry snapshot pinned in the bundle, dials Nox entry nodes through
52
+ `anonRpcWorker.kps`, and serves `fetch` calls as Nox HTTP requests through an exit, with JSON-RPC aware reply
53
+ sizing and retries for reads, per-call deadlines and abort, `fetch`-style redirects and stable `signalFailed` and
54
+ per-call error codes. The config is optional and validated before ready.
55
+ - A reproducible single-file bundle: the nox-wasm module and the snapshot are embedded, the snapshot byte for byte;
56
+ the SDK's ambient `fetch` and `WebSocket` defaults are replaced by a stand-in that fails closed, and the build
57
+ checks the output for other network APIs. Provenance records the toolchain and every input digest.
58
+ - Snapshot tools: generation from the chain, offline and on-chain verification, and a release gate that requires
59
+ every member's KPS address and two RPC providers.
60
+ - Worker specifier contracts (the reference `WorkerSpecifier` and an `ImmutableWorkerSpecifier`) with a read-only
61
+ deploy planner and inspector.
62
+ - An end-to-end test bed: a local Nox mesh with `nox-kps` sidecars and the reference harness in headless Chromium.
package/LICENSE ADDED
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by the Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding any notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2026 hisoka-io
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,197 @@
1
- # Temporary Holding Version
1
+ # @hisoka-io/anon-rpc-worker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Nox mixnet worker for the [anon-rpc](https://github.com/ethereum/anon-rpc) standard: one classic-script
4
+ bundle that a wallet's anon-rpc harness loads and pins by its keccak-256 hash.
5
+
6
+ The worker dials Nox entry nodes over KPS (`anonRpcWorker.kps`), sends each `fetch` call as a Sphinx packet
7
+ through the mixnet, and an exit node performs the HTTP request; the reply returns over single-use reply blocks
8
+ (SURBs). Who the nodes are comes from NoxRegistry: a snapshot pinned inside the bundle is the floor, and after
9
+ ready the worker reads the registry through the mixnet itself. Where the nodes are (IP, port, KPS certhash) is
10
+ looked up at run time, so operators change IPs whenever they like and new nodes join without a new bundle. A cold
11
+ boot needs no seed server and no public RPC; the bundle reaches the network only through the harness's KPS dialer.
12
+
13
+ What each party sees: the entry node sees the wallet's IP address and encrypted packets; the exit node sees the
14
+ HTTP request (RPC URL, headers it forwards, body) and never who sent it. End-to-end TLS inside the worker,
15
+ which also hides the request from the exit, is the next milestone.
16
+
17
+ ## Layout
18
+
19
+ | Path | Contents |
20
+ |---|---|
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) |
22
+ | `snapshot/` | The pinned registry snapshot, its keccak-256, JSON Schema and reviewed capability hints, and the discovery bootstrap (`nox-bootstrap.json`) |
23
+ | `scripts/` | Snapshot generator and verifier, reproducible WASM and bundle build, hashing, provenance |
24
+ | `specifier/` | Worker specifier contracts (Foundry) and a read-only deploy planner and inspector |
25
+ | `e2e/` | End-to-end test bed: a local Nox mesh with `nox-kps` sidecars, the reference harness in headless Chromium |
26
+
27
+ ## Configuration
28
+
29
+ `anonRpcWorker.config` is optional; `undefined` and `{}` boot on the anchors pinned in the bundle. Every field is
30
+ optional, and every config valid for 0.1.0 stays valid:
31
+
32
+ | Field | Type | Default | Range and meaning |
33
+ |---|---|---|---|
34
+ | `v` | number | 1 | must be 1 |
35
+ | `gateways` | string[] | the bundle's default anchors | 1-16 unique KPS addresses (`<ip>:<port>:<certhash>`, no DNS names), tried first in place of the default anchors. Any address is accepted: the node behind it must name an eligible member in its `/metadata.json` before the worker routes through it |
36
+ | `bridges` | string[] | none | 1-16 KPS addresses. When set, the worker dials **only** bridges and never a published Nox address (Tor bridge semantics). Each bridge's `/metadata.json` names the member it serves (nox-kps `node_address`), which must be in the bundle's snapshot. Excludes `gateways` |
37
+ | `registryRpcUrls` | string[] | the bundle's list | 2-8 `https:` URLs, replacing the bundle's registry RPC providers (for example a wallet's own Arbitrum node) |
38
+ | `chainQuorum` | integer | 2 (bundle policy) | 2-4 (exit, provider) pairs that must answer byte for byte the same; a check waits when fewer distinct exits or providers are available |
39
+ | `discovery` | `"chain"`, `"snapshot"` | `"chain"` | `"snapshot"`: no registry reads, the pinned snapshot is the only membership source (0.1.0 behaviour, no RPC provider involved) |
40
+ | `trust` | `"auto"` | `"auto"` | reserved for proof-backed discovery; any other value fails with `bad-config` |
41
+ | `checkpoint` | | | reserved; any value fails with `bad-config` |
42
+ | `logLevel` | `"debug"`, `"info"`, `"warn"`, `"error"` | `"info"` | |
43
+ | `attemptTimeoutMs` | integer | 12,000 | 3,000-60,000 |
44
+ | `callDeadlineMs` | integer | 25,000 | at least `attemptTimeoutMs`, at most 120,000 |
45
+ | `maxConcurrentCalls` | integer | 16 | 1-64 |
46
+ | `maxRequestBytes` | integer | 1,048,576 | 1,024-4,194,304 |
47
+ | `maxResponseBytes` | integer | 8,388,608 | 65,536-67,108,864 |
48
+ | `surbFormat` | `"auto"`, `"v1"`, `"v2"` | `"auto"` | |
49
+ | `claimIntervalMs` | integer | 200 | 50-2,000 |
50
+ | `bootRetryMaxMs` | integer | 60,000 | 5,000-300,000 |
51
+ | `topologySources` | integer | 2 | 1-4 |
52
+ | `warmup` | boolean | false | one echo through a full route before ready |
53
+
54
+ An unknown key, a wrong type or an out-of-range value fails the boot with `bad-config`, naming the field.
55
+
56
+ Examples:
57
+
58
+ - default wallet: no config, or `{}`;
59
+ - censored user: `{ "bridges": ["203.0.113.9:15005:uEiB..."] }`;
60
+ - adopters listing `exampleConfig`:
61
+
62
+ ```json5
63
+ { gateways: ["100.56.0.72:15005:uEiBVDwIs40bsslDkM-BYb2AOHw3PHe70_bj5U_09r7vdIQ"] }
64
+ ```
65
+
66
+ ## Discovery (identity from chain, location at run time)
67
+
68
+ The bundle pins `snapshot/nox-bootstrap.json` (`nox-anon-rpc-bootstrap/1`) next to the snapshot, both covered by
69
+ the worker hash:
70
+
71
+ | Field | Value |
72
+ |---|---|
73
+ | `anchors` | nox-1 `100.56.0.72:15005`, nox-2 `3.232.137.146:15005`, nox-8 `18.215.18.61:15005` (Elastic IPs) with their KPS certhashes |
74
+ | `registry`, `registryImpl` | NoxRegistry `0xf7bf...6cb6` on Arbitrum Sepolia (421614), implementation `0x7285...e2a2` behind the EIP-1967 proxy |
75
+ | `registryRpcUrls` | `https://sepolia-rollup.arbitrum.io/rpc` (Offchain Labs) and `https://arbitrum-sepolia-testnet.api.pocket.network` (Pocket Network): keyless, and checked through the live exits for `finalized`, EIP-1898 block-hash reads at the finalized block and 20-call batches. Tenderly's and Tatum's public gateways rate-limit or charge for those batches, PublicNode prunes the state of the finalized block, dRPC's free tier caps batches at 3 calls; a wallet can add its own providers with `registryRpcUrls` |
76
+ | `policy` | quorum 2, state at most 3,600 s old, check every 600 s, at most 1 member on probation per route, probation 14 days, removals need 2 anchors, 2 members per layer |
77
+
78
+ Boot: the worker dials, in priority classes and shuffled within each, bridges only (when set); otherwise the
79
+ wallet's gateways or the bundle's anchors, then learned anchors, then the snapshot's KPS addresses, three at a time.
80
+ As soon as one anchor serves an acceptable topology it signals ready (boot time unchanged). Served topologies are
81
+ judged on identity (address, Sphinx key, role): a changed IP is a move, never a removal.
82
+
83
+ After ready, the SDK reads NoxRegistry at one finalized block through the mixnet: two different exits to two
84
+ different providers, every call pinned to the block hash. It uses the answer only when both pairs agree byte for
85
+ byte, the registered members close the set (`relayerCount()` and the XOR `topologyFingerprint()`), and the proxy
86
+ still points at the implementation the bundle knows. The answer updates locations and removes members (each route
87
+ layer keeps at least two snapshot members); a member outside the snapshot is on probation for 14 days after the
88
+ worker first saw it, and a route holds at most one such member. Checks repeat every 10 minutes, and early when a
89
+ served fingerprint differs or every entry failed. Log events: `discovery.verified`, `discovery.disagreement`,
90
+ `discovery.incomplete`, `discovery.rejected`, `discovery.probation`, `discovery.floor`, `discovery.failed`.
91
+
92
+ What each party learns: RPC providers see exit IPs and public registry reads, never the wallet's address. Until
93
+ TLS runs inside the worker, the exits terminate HTTPS, so the two-exit quorum is attested by the exits; probation
94
+ and the snapshot floor bound what a forged answer can change. If every chain path fails, the worker keeps working
95
+ on the snapshot floor.
96
+
97
+ Operators move by sending `updateUrl` / `updateMetadataUrl` from the node key (self-service, about 0.00002 ETH);
98
+ running workers pick the new address up within about 10 minutes.
99
+
100
+ ## Readiness and failure codes
101
+
102
+ The worker signals ready once the config is valid, the embedded WebAssembly is initialised, the pinned snapshot
103
+ verifies, a KPS entry has answered and a served topology was accepted. Transient faults (no entry reachable,
104
+ a topology fetch failing) are retried with back-off while `ready` stays pending.
105
+
106
+ `signalFailed` codes: `bad-config`, `unsupported-platform` (no KPS dialer, `crypto.getRandomValues` or
107
+ WebAssembly), `wasm-blocked` (the embedder's CSP must allow `'wasm-unsafe-eval'`), `snapshot-invalid`,
108
+ `snapshot-invalid` also covers an invalid discovery bootstrap. `snapshot-stale` (two or more nodes agree the registry no
109
+ longer lists any known member of a route layer with its identity, and a chain check does not resolve it: a newer
110
+ bundle is due) and `internal-error`. Members that are listed but reported offline, as while
111
+ nodes reconnect after a restart, keep the worker running: calls through them fail one by one (`timeout` or
112
+ `network-error`) until a topology refresh sees them online.
113
+
114
+ A rejected call carries a string `code`: `cancelled` (`AbortError`), `timeout`, `network-error`, `too-large`,
115
+ `unsupported`, `protocol-error` or `internal-error`. Redirects follow `fetch` rules inside the call deadline (at
116
+ most 5 hops). `eth_sendRawTransaction` is sent once and never resent; after a `timeout`, check the receipt by
117
+ transaction hash.
118
+
119
+ ## Storage
120
+
121
+ The worker stores public network state only, under `nox/v1/`: members recent topologies removed (reorders anchors)
122
+ and the learned-anchor cache (`nox/v1/anchors`): KPS addresses verified chain checks confirmed, with the block they
123
+ were read at, and when each member outside the snapshot was first seen. At most 32 anchors, each dropped after 30
124
+ days. A cache that fails any check (another registry, too many records, one malformed record) is ignored whole, and
125
+ a learned address is used only after the node's `/metadata.json` names the same member.
126
+
127
+ ## What the bundle contains
128
+
129
+ - `dist/anon-rpc-worker.js`: a single IIFE built by esbuild (target es2022, not minified, so it can be read
130
+ and audited). Every module is inlined: the worker source, the `@hisoka-io/nox-client` sources, the
131
+ `nox-wasm` WebAssembly module (base64), the pinned registry snapshot and the discovery bootstrap, each embedded
132
+ byte for byte as one string.
133
+ The bundle has no imports and never loads code or WebAssembly by URL. The SDK's ambient `fetch` and
134
+ `WebSocket` defaults are replaced at build time by a stand-in that fails closed, and the build checks the
135
+ output for any other network API.
136
+ - `dist/anon-rpc-worker.js.keccak256`: the Ethereum keccak-256 of those exact bytes, the value a worker
137
+ specifier pins as `workerHash()`.
138
+ - `dist/anon-rpc-worker.provenance.json`: source commit, toolchain versions, input digests (snapshot, bootstrap,
139
+ WASM, lockfile) and output digests.
140
+
141
+ ## Pinned registry snapshot
142
+
143
+ `snapshot/nox-snapshot.json` (format `nox-anon-rpc-snapshot/1`, schema in `snapshot/nox-snapshot.schema.json`)
144
+ records every NoxRegistry member on Arbitrum Sepolia at one block: address, Sphinx key, routing and ingress
145
+ URLs, `metadataUrl` (where each node publishes its KPS address as `kps:<ip>:<port>:<certhash>/metadata.json`),
146
+ stake, role, layer, status, and reviewed capability hints from `snapshot/capabilities.json`. Its topology
147
+ fingerprint is computed with the SDK's own `computeTopologyFingerprint` and equals the registry's on-chain
148
+ `topologyFingerprint()` at that block.
149
+
150
+ Nodes may serve fresher topology over KPS. The worker uses it for liveness and removals (only when two different
151
+ nodes agree, keeping at least two members per route layer) and for moved routing addresses two nodes agree on;
152
+ additions and confirmed locations come from the chain check.
153
+
154
+ The committed release snapshot is of block 316207920 (finalized, read and re-verified byte for byte through two RPC
155
+ providers of different organisations). It carries the KPS addresses nox-1, nox-2 and nox-8 publish, so the worker
156
+ finds its entries from the snapshot alone, and the default anchors and the chain check add any later moves.
157
+ The release gate names the seven mix and exit nodes, which serve the mixnet without a KPS listener, with
158
+ `--allow-missing-kps`.
159
+
160
+ ```bash
161
+ pnpm --filter @hisoka-io/nox-client build
162
+ node scripts/make-snapshot.mjs --rpc <arbitrum-sepolia-rpc> --rpc <second-rpc> [--block <n>]
163
+ node scripts/verify-snapshot.mjs --offline # schema, hashes, SDK-derived fields
164
+ node scripts/verify-snapshot.mjs --rpc <rpc> --rpc <rpc> --release # chain re-read + every member publishes a KPS address
165
+ ```
166
+
167
+ ## Building and checking the hash
168
+
169
+ ```bash
170
+ corepack enable && pnpm install --frozen-lockfile
171
+ bash packages/anon-rpc-worker/scripts/build-worker.sh --release # pinned toolchain, clean tree
172
+ node packages/anon-rpc-worker/scripts/hash.mjs packages/anon-rpc-worker/dist/anon-rpc-worker.js
173
+ ```
174
+
175
+ `scripts/toolchain.env` pins every tool that shapes the bytes: the Node and Rust container images by digest,
176
+ rustc, wasm-pack, wasm-bindgen and binaryen (with sha256 digests of their release archives); pnpm comes from the
177
+ repository's `packageManager` field and esbuild from the lockfile. `scripts/build-wasm.sh` follows the
178
+ tor-js recipe: path remapping, a `RUSTC_WRAPPER` that makes cargo's metadata host-independent, `--locked`, and
179
+ a pinned `wasm-opt` on `PATH`.
180
+
181
+ `scripts/verify-reproducible.sh` builds a commit twice from clean `git archive` exports in fresh containers
182
+ of the pinned images, at two different paths, and compares the WASM, bundle and provenance digests
183
+ (`--local` does the same with the host's tools). `--native` adds a third build with the host's tools to the two
184
+ container builds and requires all three to match; `--local --bwrap` runs the two host builds in bubblewrap mount
185
+ namespaces at the container paths, for hosts without a container runtime.
186
+
187
+ For a test bed, `node scripts/build.mjs --snapshot <file>` embeds another canonical snapshot (for example a
188
+ local mesh); the provenance records which snapshot a bundle carries.
189
+
190
+ ## Tests
191
+
192
+ ```bash
193
+ pnpm --filter @hisoka-io/anon-rpc-worker test # worker, SDK integration over an in-memory KPS network, build
194
+ pnpm --filter @hisoka-io/anon-rpc-specifier test # Foundry and harness-path tests (forge, anvil)
195
+ ```
196
+
197
+ The end-to-end bed has its own lockfile: see `e2e/README.md`.