Repository navigation
Releases: dmarzzz/shade-tree-node
Release list
v0.7.5
0.7.5 — register-member funds itself
shadenet register-member with no key creates its own funding wallet, quotes one amount, waits for it and stakes (zkAPI's deposit step). Also: the proxy as a scheduler with --preopen session books, CLI output that names the budget, record RPC changes, monitoring fixes and dependency security bumps.
register-member funds itself (feature request: mirror zkAPI's funding path)
shadenet register-member --identity …with no--key-fileand noSHADENET_REGISTER_KEYon
a remote RPC used to fail asking for a funded key. It now does whatzkapi-clientd config
does: creates an owner-only funding wallet (funding.keynext to the identity file;
--funding-key <path>to move it), prints the wallet's address and the one amount to send
(the tier's bond frombondForplus a gas reserve: the smallest of 0.002, 0.004, 0.008 … ETH
that covers 1 800 000 gas at the current EIP-1559 fee; the lowest rung covers up to 1.11 gwei,
since Sepolia idles at a few wei and then spends a week near 1 gwei), polls the balance every
15 s, and stakes once the balance covers it. The reserve moves in those coarse steps rather
than tracking the fee to the wei because the amount is public next to the stake: on the
bundled Sepolia set every quiet-fee registration sends the same 0.012 ETH, so the amount does
not single out its sender. Top-ups after a partial payment are rounded up to 0.0001 ETH.- With the first amount it prints the Layer-0 advice from
docs/PAYMENTS.md: the address, amount
and stake are public and link the sending wallet to the membership (not to its use), so send
from a fresh address funded through a large shared pool (Railgun, Privacy Pools, a CEX
withdrawal) and send exactly the quoted amount. After the stake it prints the leftover gas
money still in the funding wallet and says it is tied to the stake: keep it for a later
top-up or send it on to a fresh address, not back to the wallet that funded it. The same command resumes after Ctrl-C and reuses the key; an already-staked
identity is reported before anyone is asked for money.--wait-timeout <seconds>(default
3600;0quotes once;SHADENET_FUNDING_POLL_MSsets the poll) bounds the wait; an unfunded
wallet at the deadline exits 4 naming what is still missing. The quote signs nothing and never callseth_estimateGas(an
unfunded sender cannot be estimated against), which is why the reserve is a ceiling; what
the registration does not spend stays in the wallet.--key-file, the environment key and
the loopback Anvil default are unchanged. - Proven live on 2026-10-11 against the bundled Sepolia set
0x789967F0…680b: a fresh identity,
shadenet register-member --identity …with no key, funding wallet
0x33ec7E53E143e2065887Bd5D5d7EB47159D72982quoted 0.0116 ETH (0.01 bond + 1 600 000 gas at the
1 gwei floor, the ceiling at the time); the deployer sent exactly that
(0x80dd0656d783f4279fa06e4448e1d3f40672b0f9369401a88bb1456d2cbe67ef, block 11897250); the
waiting command saw it on its next poll and staked
(0xd0ebc6fbc3ca69fbf95a490b0205d4f70c89ccad88f500f139f3e9faf1ea8a17, block 11897251,
1 471 267 gas at 0.001 gwei,member-status→active, index 14), leaving 0.0016 ETH in the
wallet. Two earlier one-hour waits also ran to the deadline and exited 4, and retried two
transient RPC errors (eth_call: expected 0x-hex data, a 15 s timeout) without losing the
wait. The stake used more gas than the first live registration (1 317 674), and the signer
prices at estimate + 20 % (about 1.77M), so the ceiling moved from 1 600 000 to 1 800 000 after
this run, and the reserve ladder above replaced the per-fee figure: the quote is now 0.012 ETH
at quiet fees. shadenet initprints the keylessregister-membercommand and says where the funding
wallet will be created;--key-fileis mentioned as the alternative.- The JavaScript
shade-tree register-memberdoes the same with noSHADE_TREE_REGISTER_KEYon a
remote RPC:--funding-key <path>/SHADE_TREE_FUNDING_KEY(default~/.config/shade-tree/funding.key),
--wait-timeout,SHADE_TREE_FUNDING_POLL_MS, exit 4, same quote and the same printed
lines. The arithmetic, rounding, key file and wait loop live in
packages/node/lib/funding-wallet.mjswith a selftest (quote and reserve ladder, privacy text, rounding, owner-only
creation and reuse, refusal of a world-readable or malformed file, the wait loop against an
injected clock: one quote, progress on partial payments, a retried RPC error, exit 4 at the
deadline, no quote for a staked leaf). - Library:
Wallet::generate(),member::fee_terms(factored out of the signer, same
numbers),member::funding_quote/FundingQuote/REGISTER_GAS_CEILING/QUOTE_MIN_FEE_PER_GAS; the bond
checksregisterperforms moved intotier_bond, shared with the quote. Tests cover the
quote arithmetic and the staked / wrong-chain refusals against the mock RPC, the owner-only
creation and reuse offunding.key(and thechmod 600refusal), the default key location,
the parser, and the rounding.
Monitoring
-
The compose monitoring bundle scraped the heartbeat on 9102 and the Elder on 9103; the defaults
are heartbeat 9103 and Elder 9100, soHeartbeatDownandBootnodeDownfired on a healthy host.
RpcEndpointFailingandCanopyPinDriftusedseverity: warnand had nocomponent; they now
usewarningwithcomponent: rpcandcomponent: fleet.monitoring/alerts.selftest.mjs
checks both (labels on every rule, compose ports againstbootstrap.sh). -
CanopyPinDriftcompares release versions, not commits. The published node image is built at the
release tag's commit while systemd hosts run the record's commit; both report the record's
version, so the orbital-one container (0.7.4 at 6709328) no longer fires it next to the fleet
(0.7.4 at 114065d). A role left on another release still fires; an off-pin commit on one host is
CanopyOffRecordPin's job. Checked withpromtool test rules: same version and two commits stays
quiet, while two versions fire after 30 minutes. -
The uptime probe accepts a comma-separated
SHADE_TREE_DIR_SIGNER, which is what the record sets
for a canopy with two Elder Trees. It passed the joined string toverifyDirectoryas one key, so
every check failed withdirectory:signer-not-pinnedand the lab vantage point reported the Elder
unreachable while it served a fresh, signed directory. -
The Sepolia record pins
services.*.committo 114065d, the commit the fleet runs (written with
scripts/record-canopy.mjs, as agent-devops' copy already was). It still named 954216e from 0.7.2,
so the probe reported every Elder off pin.
Dependencies
- quinn-proto 0.11.16 → 0.11.19 (
cargo update -p quinn-proto, lock only; it arrives through
arti/reqwest). RustSec published five advisories against 0.11.16 on 2026-10-12
(RUSTSEC-2026-0339 to -0343, stream-reassembly and connection-ID memory exhaustion; fix= 0.11.17), which turned the supply-chain audit red on every PR, including #353.
cargo audit
andcargo deny check advisoriesare clean on the new lock; the workspace builds--locked. - docker/setup-qemu-action v3 → v4.4.0 in
node-image(Dependabot #256).- Without a push,
node-imagenow also builds the arm64 image through QEMU (neither loaded nor
pushed), so a QEMU or base-image break shows up on a dispatch instead of during a release. - Checked with a dispatch on the branch (run 37545890531,
push=false): the amd64 build and
smoke test passed, and the arm64 build through QEMU v4 passed.
- Without a push,
- toml 0.8 → 1.1 for
config.toml(Dependabot #263). toml 1.x gatesfrom_strbehind itsserde
feature, so the dependency now enables["parse", "serde"]; Dependabot's version alone did not
compile.- A new corpus test pins every config people have, field by field: what
shadenet initwrites,
what agent-devops renders for Hermes, and every key at once with inline comments, a literal
string, a multi-line array with a trailing comma, and CRLF line endings. - It also checks that an unknown key (named in the error), a wrong type, a duplicate key and
broken syntax stay errors. - It passes unchanged on 0.8 and 1.1.
- A new corpus test pins every config people have, field by field: what
- snarkjs 0.7.5 → 0.7.6 in
packages/sdk, which proves exit/withdraw actions in the browser
(proveAction; Dependabot #257). The repo's hoisted snarkjs, used by the nodes and the JS client
for RLN proofs, stays 0.7.5.- Checked across versions:
- a fresh SDK exit proof (0.7.6) verifies under the hoisted 0.7.5; this is now a permanent
check insdk.selftest; - the on-chain
WithdrawGroth16Verifieraccepts it on Anvil and refuses it with a tampered
address; - the pre-bump
testdata/withdraw-proof.jsonfixture still verifies.
- a fresh SDK exit proof (0.7.6) verifies under the hoisted 0.7.5; this is now a permanent
- Checked across versions:
- @noble/curves 1.2.0 → 2.4.0, the browser backend's ed25519 (Dependabot #259). The import is
@noble/curves/ed25519.js; sign/verify (RFC 8032,zip215: false) are unchanged.- ethers, @semaphore-protocol/proof and @zk-kit/lean-imt keep their own nested 1.x.
test/crypto-backends.selftest.mjs(32 checks), directory forward-compat under both backends
and the SDK conformance vectors pass.- The stake page bundle is byte-identical.
- @noble/hashes 1.3.2 → 2.4.0, the browser crypto backend's sha256 and sha3-256
(packages/node/lib/crypto-browser.mjs; Dependabot #260, merged as #330). v2 exports explicit.js
subpaths, so the imports are@noble/hashes/sha3.jsand@noble/hashes/sha2.js.- New
test/crypto-backends.selftest.mjs(32 checks) pins the browser and Node (OpenSSL)
backends to each other: FIPS known answers, the RFC 8032 ed25519 vector, cross-verification,
and refusal of a corrupted or non-canonical-S signature. - The stake page bundle is byte-identical.
- New
- chacha20poly1305 0.10 → 0.11, the XChaCha20-Poly1305 seal on pa...
v0.7.4
0.7.4 — forward-compatible capability verification
Clients verify a signed directory over the bytes as signed and tolerate capability fields they do not recognize, instead of rejecting the directory. A future caps.* field no longer bricks an installed client the way caps.sets did to v0.7.0; interpretation still ignores unknown fields. Symmetric in the Rust client and the JS node, with a previous-release conformance vector. No new signed capability field ships until this release is the floor.
v0.7.3
0.7.3 — first-run polish
The one-tier launch set plus: init shortens the leaf it prints so it cannot be pasted where the identity commitment belongs, and doctor reports a pruned backup RPC as a note for clients instead of a failure.
Nothing yet.
v0.7.1
0.7.1 — the node container
ghcr.io/dmarzzz/shadenet-node:<version>: Tor + the JS node + its heartbeat in one image, run from a deployment record alone (SHADENET_RECORD).shadenet-node run | check [--probe] | identity | authorize | status | retire; ten knobs asSHADENET_*or/state/node.toml, includingsets(one node admitting several staked sets); any explicitSHADE_TREE_*still wins. Published multi-arch and attested by the release workflow.bootstrap.shand the v4 role takeSHADENET_SETS/shade_tree_extra_setsfor the same thing.- The operator page, OPERATOR.md, JOIN.md, CONFIG.md and docker/README.md lead with the container; the stale "blocked by #6" note is gone.
v0.7.0
0.7.0 — ShadeNet research preview on Sepolia
The launch release. The fleet, the site, both SDKs and the shadenet binary read one
production record, network/sepolia/deployment.json, deployed 2026-09-30 from the final
economics (H2) with the PSE trusted-setup verifier (H3). Signed and hashed v4 wire strings are
unchanged (test/wire-freeze.selftest.mjs). docs/LAUNCH-REPORT.md is the launch gate line by
line with evidence.
Launch
- Production contracts on Sepolia:
StakedReputationSet0xDEB294E6e9ad6A3FcBDeFfD1F67aC9678AC94bBC
with the ceremonyWithdrawVerifier, all four contracts Sourcifyexact_match; the earlier set
0xEB67…4275is retired (CHAIN-1). The record lists the RPC failover order (ADR 0012). - Economics final (H2, #212,
docs/ECONOMICS.md): tier 1 = 0.01 ETH, tier 8 = 0.08 ETH, 24 h
unbonding, slash bounty 1/10, 24 sponsor seats, session tickets on. - Trusted setup: PSE's finalized RLN ceremony adopted for both circuits after an independent
re-verification from public inputs (H3, #214,docs/ceremony/PSE-VERIFICATION.md, two archive
mirrors). The dual-VK window closed with the production record: only
rln-ae43614cd02ebe95is accepted,security.proofArtifactsistrusted-ceremony,
circuits/rln/previous/is gone, anddeploy/v4/preflight.mjsreads the lock'sCEREMONY
trust value. - The Get access page and its status API build from the production record; install lines pin
v0.7.0 (#216, #218, #222).
Protocol and SDKs
- Session tickets (#103, ADR 0011, #205): with the record's
sessionTicketsswitch on, one RLN
proof per epoch buys a gateway-bound ticket good for several tunnels inside the epoch's payload
budget; shared vectors, Rust client, JS node and@shadenet/sdkall agree. Off by default in
code; on in the launch record. Units render the switch (#221, #225). - Every Elder hears every node and the Lab reads the record's
elders[](ADR 0012, #213, #224). - Member-set replay verified against the contract's counters, fail closed on an RPC that returns
an empty log page (#207); v4 preflight retries a missing receipt (#209) and checks fallback RPCs
for current state (#217).
Ops and release
- The JS node lives in
packages/node/(#202) and the compatibility shims are gone (#208). - Staging rehearsal report (M7,
docs/STAGING-REHEARSAL.md, #209, #219, #223, #224): every
launch-gate line run on the staging canopy, including a browser stake, Hermes and SearXNG
through the canopy, Elder failover, an RPC outage, an alert round trip and a release from a tag. - Release: Intel macOS live binary (#198),
--versionnames the commit (#199), packageable crates
and a tag gate (#191), prerelease handling and a Homebrew tap step with a deploy key (#194, #211),
the publish dry-run passes a dist-tag for prereleases (#206);install.shglibc and aarch64
loader fixes (#220).
Dependencies
- brace-expansion 2.1.7 / 5.0.12 (GHSA-q2hr-2g5m-vwhr and related, #201, #203, #204); routine
npm, cargo and Actions bumps.
v0.7.0-rc.1
0.7.0-rc.1 — ShadeNet research preview, release candidate
First release cut from a tag under the ShadeNet name; a prerelease, so it never becomes "Latest"
and the fleet rolls to it on staging only. Work toward the ShadeNet research preview on Sepolia.
Signed and hashed v4 wire strings are unchanged (test/wire-freeze.selftest.mjs). The contracts
change and take effect with a fresh deployment: staging is live (network/sepolia-staging/);
production waits for the economics and the trusted setup.
Contracts
- Burn 90% of each slashed member bond instead of paying it all to the slasher (#115, audit 2.1.1).
- The set derives each leaf from the identity commitment and the tier it proves; zero and
non-canonical commitments are rejected; exit and withdraw proofs bind chain, contract and leaf
index (#136, audit 2.1.2 to 2.2.1). - The paid set burns a slashed identity at every tier and refuses to re-insert it; the registrar
refuses before charging (#181, audit 2.3.3). The audit's reproductions are regression tests. - Economics as config:
network/<net>/economics.jsonin, a read-back-verified deployment record
out; production refuses placeholder economics (#177). Staging deployed and source-verified on
Sourcify's v2 API (#187). - The internal audit report and a finding-by-finding index (#151).
SDKs and clients
- Session tickets behind the
sessionTicketsswitch (#103, ADR 0011): one RLN proof buys a
research-v1book of six single-use tunnel tickets at one node, spent with proof-less
envelopes on the v4 port; shared byte ceiling, shaping, lifetime and idle limits per book; the
onion-signedsessioncapability; the same vectors in the node, the JS client,@shadenet/sdk
and the Rust SDK. Off by default; H2 turns it on ineconomics.json. shadenet, the Rust SDK crate, with an async proxy and theshadenetCLI (#155): concurrent
CONNECTs, structured errors, a status endpoint,shadenet mcpfor agents.@shadenet/sdk, the JavaScript SDK for browsers and Node (#135).- Passphrase-protected identity files (#171); no panics on untrusted input (#164); Ethereum
primitives inshadenet::ethreplace ethers-core (#174). - Several Elder Trees: deployment record schemaVersion 2 with
elders[]; both SDKs use every
Elder (#182). - Agent docs, examples,
llms.txt, a local API spec and an installer (#161). - Rust CLI copy says ShadeNet and canopy (#184). The JS CLI binary is
shade-tree-node(#134).
Node and operations
- One-command join from the deployment record; secrets as systemd credentials; pinned Node;
journald caps (#165). Bootstrap fixes (#167). - RPC failover across up to five endpoints (#156); the spent-nullifier set persists across
restarts (#159); the running commit is inbuild_infoand/health(#158). - Onion DoS defenses: a per-circuit stream cap and Arti proof-of-work support (#162).
- Federation dials the configured Tor SOCKS port (#168); alert rules load in Prometheus (#150);
uptime probe hardening (#160). npm run dev:offlineruns a real node and Proxy on loopback without Tor (#175).- Every server environment variable is documented and checked (#172); SLOs, on-call and key
backup (#166); fleet hot key rotated and split by role (#148).
Site and docs
- ShadeNet and canopy across the site and current docs; Grove and Canopy merged into canopy.
/canopyserves the network page;/groveand the v1/v2 Data API paths keep working (#132, #152). - Get access page built from the deployment record: stake, status, exit and withdraw with proofs
made in the tab (#147, #169)./pricingredirects to/stake/. - Docs reorganized into use, run, reference, security, design and history, with a link check (#170).
- The launch runbook for the owner's gates (#185); ADR-0010, two SDKs and one spec (#130).
- Ceremony kit (#128); PSE's RLN setup passes the adoption check for both circuits (#163); the
launch circuit set is frozen (#186).
Build, CI and release
- Rust workspace moved to
crates/withshadenet-*crates (#131). - The JS node moved to
packages/node/with shims at the old paths for one minor release (#202). - Required checks: real-Tor e2e, bootstrap e2e,
cargo deny,cargo audit; one Node lane per PR
and the full matrix nightly (#153, #179); main CI is never cancelled (#183). - Release assets for
shadenet, Intel Mac live build, signing, Homebrew formula, GHCR image and
publish dry-runs (#157). - The Smithers harness is removed (#154).
Dependencies
Ceremony archive: PSE RLN trusted setup (adopted 2026-09-30)
Mirror 1 of the trusted-setup artifacts ShadeNet adopted under launch decision D3: PSE's RLN Trusted Setup Ceremony (rln-20: 60 contributions + beacon; rln-withdraw: 62 + beacon; Hermez phase 1, circom 2.1.5 --O2, circom-rln 17f0fed).
The tarball holds PSE's published zkeys/R1CS/WASM/vkeys/verifiers, the ptau, Firestore snapshots, both verifyFromInit transcripts and the check log; SHA256SUMS covers every file. Individual final zkeys, verification keys and the joined transcript are attached as well. Verification report: docs/ceremony/PSE-VERIFICATION.md on main after the adoption PR.
This is an archive release, not a software release.
v0.6.0
0.6.0 — Private staking and recovery
Official Sepolia research preview. This release still uses unaudited,
untrusted-testnet proof artifacts and testnet ETH. It is not a production anonymity
or security boundary and must not be used with real funds or sensitive traffic.
The public tier-1 Grove now has a friendly local-first staking page for humans and a
complete Node-free identity, staking, status, exit, and withdrawal path for agents.
The on-chain profile is unchanged: 0.1 Sepolia ETH admits one CONNECT tunnel per
fixed 60-second epoch with a 40 MiB combined payload ceiling.
Added
- Added a static privacy-first staking page that creates a Semaphore-v3-compatible
identity locally, validates imports, requires a recovery download, supports
injected-wallet registration, and offers a public-leaf-only sponsor mode. - Added
register-member --identityso the Rust client recomputes and validates the
secret, leaf, and exact tier before its first wallet or RPC interaction. - Added native Rust
member-status,exit-member, andwithdraw-membercommands.
Exit and recipient-bound withdrawal proofs are built and self-verified locally,
then the exact EIP-1559 call is simulated and signed locally with a separable gas
wallet. - Embedded the deployed withdrawal circuit artifacts in the live Rust binary and
verified Rust-generated proof calldata against the live Sepolia verifier.
Hardened
- Pin the browser flow to the current Sepolia chain, contract bytecode, tier, and
exact bond; reject active leaves, insufficient balances, changed parameters, and
reverted simulations before requesting a staking transaction. - Reject malformed, oversized, mismatched, extra-field, wrong-tier, and duplicate
identity/CLI inputs without printing bearer material. - Document the precise privacy ledger: the static host can see a page load, the
wallet/RPC sees the public registration, and the public commitment necessarily
links the pseudonymous register/exit/withdraw lifecycle.
v0.5.0
0.5.0 — Public Sepolia staking
Official research preview. This release uses unaudited, untrusted-testnet
proof artifacts and Sepolia ETH. It is not a production anonymity or security
boundary and must not be used with real funds or sensitive traffic.
The bundled Grove now admits any member who registers a tier-1 commitment with
exactly 0.1 Sepolia ETH. Tier 1 permits one CONNECT tunnel per fixed 60-second
epoch and caps that tunnel at 40 MiB of combined payload. Cross-gateway replay
suppression is authenticated but best-effort and fail-open, not an atomic global
reservation system.
Added
- Added a fresh immutable public staking profile with tier 1 at 0.1 Sepolia ETH,
compatibility tier 8 at 0.8 Sepolia ETH, a 24-hour unbonding window, and the
real in-repo testnet exit verifier. - Added native Rust member registration with owner-only key-file or environment
key input, local EIP-1559 signing, exact on-chain bond discovery, receipt
confirmation, and duplicate protection. - Added bundled zero-configuration JS and Rust defaults for the live Elder,
Canopy signer, staking contract, RPC, deployment block, tier, and rate policy. - Added onion-signed node rate capabilities and fail-closed client matching for
the 60-second epoch, 60-second root lifetime, and 40 MiB payload ceiling. - Added a pinned runtime-bytecode manifest for the staking set, commitment
hasher, exit wrapper, Groth16 verifier, and linked Poseidon libraries; both
local release tests and live deployment preflight reject executable drift.
Fixed
- Expire superseded membership roots and last-known-good RPC snapshots by wall
clock even when no later membership event occurs. - Reset node and light-client root history after a stale observation gap, and
retain only the current root during RPC fallback, so recovery cannot make an
old withdrawn-member root fresh again. - Keep identity enrollment, registration, and egress on the same explicit tier
in both implementations, including RPC-only overrides. - Sort contract tier tables globally so the new limit-1 tier precedes limit 8.
v0.4.1
0.4.1 — Accepted-tunnel close handling
Official research preview. This patch supersedes v0.4.0 for agents, but it
still uses the unaudited, untrusted-testnet proving setup and is not a production
anonymity or security boundary. Grove access remains invite-only.
Install the matching checksummed Rust -live binary with:
curl -q -fsSL --proto '=https' --proto-redir '=https' \
https://raw.githubusercontent.com/dmarzzz/shade-tree-node/v0.4.1/scripts/install.sh \
| SHADE_TREE_VERSION=v0.4.1 shAdded
- Added a hardened POSIX installer that detects the supported target, downloads
the pinned binary and checksum over HTTPS, verifies the digest and filename,
and installs withoutsudo.
Fixed
- Fixed Rust CONNECT Proxy completion after proof acceptance: once the Proxy has
sent200 Connection Established, a later peer-close relay error is logged as
the end of that accepted tunnel instead of being treated as a pre-accept setup
failure. Errors before the 200 response remain fail-closed and nonzero. - Hardened the real-Hermes/embedded-Arti E2E so each successful agent request is
corroborated by a new gateway acceptance while bounded retries remain within
the eight-slot research epoch budget.