Start here
What runs today
A three-way split of the Rust workspace: what the node binary actually calls, what exists only as a library, and what is an interface with no implementor.
What this page is
The implementation is a Rust workspace whose members are thirteen library crates under crates/, two binaries — uninet-node and uninet-cli — and three bench and test crates, all listed in the workspace Cargo.toml. This page sorts that workspace into three categories, because "implemented" is not one state:
- Called by the node binary. A route, a handler or an executor path reaches the code when the node is running.
- A library with no caller outside its own crate and its tests. The code is written and tested; no binary invokes it.
- An interface with no implementor. A trait exists, the call site exists, and nothing outside that crate's own test module implements it.
The distinction matters because the second and third categories look identical to anyone reading a module list, and they behave very differently when you try to deploy.
One thing to know before reading further: this codebase contains no todo!(), no unimplemented!(), and no // TODO or // FIXME markers. Gaps are recorded in module-level doc comments instead — crates/net/src/onion/mod.rs carries a "# Not built" section, and crates/net/src/unp/resolver.rs opens with "This module is an interface with no backend." An absent marker is not evidence of completeness here, and the prose is the place to look.
What a running node serves
Start from the surface, because it bounds everything else. bin/uninet-node/src/api/unp_gateway.rs documents three entrypoints on the external HTTP listener once UNP enforcement is live:
GET /unp/resolve node identity + a signed bootstrap nonce
POST /unp/handshake X25519 ECDH -> AES-256-GCM session
POST /unp the tunneled request dispatcher
Every other /api/* route is reachable only through the third one. AppState holds the inner axum::Router, and the dispatcher decodes a TunneledRequest and re-dispatches it into that router. /unp/resolve returns the node's Ed25519 public key, its wallet address, its X25519 public key, a freshly generated 32-byte nonce and an Ed25519 signature over nonce ‖ timestamp_le ‖ node_id, so a client can check that the response came from the holder of the key it was handed. /unp/handshake derives the session key from that exchange, stores it server-side, and returns a session id and an expiry. Alongside these, /unp/:domain and /unp/:domain/*rest serve names registered on the node's own DNS chain, and the router registers three relay-overlay endpoints — /unp/relay, /unp/deliver and /unp/onion — whose behaviour the next section covers.
The CLI speaks this protocol. It also carries a --dev-insecure flag documented as dev-and-test only, which bypasses the tunnel and talks plain HTTP to a dev node's loopback listener.
This is the shape of what you can exercise today: one node, its hosted child chains, and a client that tunnels into it. Everything in the table below sits behind that surface.
The inventory
| Subsystem | What exists | What calls it |
| --- | --- | --- |
| Chain execution | crates/chain/src/executor.rs — seventeen transaction variants, including transfer, element deploy with in-transaction supply minting, contract execution, chain spawn, token bind and dissolve, governance vote, module swap and the storage set. Receipts are hashed into a receipts_root. | The node's /api/tx/* routes, and block application on every node that receives a block. |
| WASM execution | crates/vm/src/runtime/wasmtime_backend.rs — Wasmtime JIT, gas metering, StoreLimits bounding memory and table size, and fourteen env host functions. State reads and writes, balance reads, transfers, hashing, block info and events run against the chain's own state backend. Four of the fourteen are documented no-ops that return 0: element deploy, element dissolve, signature verification and cross-chain send. | executor.rs:1081 calls execute_with_state. This is the only engine the chain executor uses. |
| Interpreter runtime | crates/vm/src/runtime/mod.rs — InterpreterRuntime, whose simulate_execution echoes the call arguments back as return data. Its compile step requires the magic bytes UNVM and rejects anything else, so it cannot parse a WASM module. | Nothing outside crates/vm. Every reference to it is that crate's own tests. |
| PBFT consensus | crates/consensus — three-phase PBFT, Ed25519-signed votes, quorum arithmetic, view change, and a commit certificate stapled into the block so a node that was not present for the round can check it afterwards. | The node constructs PbftConsensusEngine::with_keys — the engine that verifies message signatures — only when real validator keys are configured. The default single-node path constructs PbftConsensusEngine::new, which does not verify them (state.rs:801-818). |
| Blocks and state roots | crates/chain/src/block.rs — the header commits to the state root the block executes against, and Chain::apply_block checks it before executing, so a diverged node refuses rather than executing onto a different base. A zero root is accepted as unverifiable. | Primary-chain block production (AppState::build_block) and application on every node. Two limits to read with it: the header commits the pre-execution root, and nothing commits a post-execution root into a block; and AppState::build_child_block, the child-chain proposer, writes a zero root, so the check is inert on child chains. |
| Storage and Merkle proofs | crates/storage — sparse trie. On the primary chain DiskBackend is wrapped in EncryptedBackend for both the state trie and the block store (state.rs:751, state.rs:761); child chains open a bare DiskBackend with no encryption wrapper (state.rs:2690). | POST /api/storage/proof serves a real trie proof from chain.state().prove(key) and verifies it against the node's current root. The handler withholds the proof path when the walk would end on another caller's value. Read the root carefully: it is this node's trie root, so the proof shows what this node stores, not what a validator set agreed. |
| Identity, RBAC, delegation | bin/uninet-node/src/rbac.rs — owner, root, admin, per-field grants, a parent cascade bounded at MAX_PARENT_DEPTH = 16, token gates evaluated against real ledger balances, and a delegation chain walk bounded at MAX_DELEGATION_DEPTH = 8. Revocation marks rather than deletes, so the record of who once held a right survives. | The thirteen /api/identity/{admin,permission,delegation}/* routes. A regression test asserts that the eleven mutating ones answer an unauthenticated caller 401 and specifically not 501, that a refused call writes nothing, and that the two verdict routes — permission/check and delegation/check — answer false rather than 501. |
| Governance | crates/ao — organization types, roles, a voting protocol with quorum, payment and inactivity state machines, and a transition validator. | The node's /api/aos/* routes — create, campaign, vote, tally, payment, governance action, status — and the /api/governance/* set, over gov_state.rs. |
| Onion routing and SURBs | crates/net/src/onion — one AES-256-GCM layer per hop, keyed by a fresh ephemeral X25519 exchange against that relay's published onion key; fixed cell size per privacy tier; every layer authenticating its own ciphertext, the cleartext frame header and the filler region; cover traffic; key rotation with a grace window for frames already in flight; and single-use reply blocks with a byte-exact wire format. Seventy-five unit tests across its packet, reply, key-schedule, parameter, cover-traffic and rotation modules, plus the integration suite in crates/net/tests/privacy_tiers.rs. | Partly. See below. |
| UNP confidential resolution | crates/net/src/unp/resolver.rs — the ThresholdResolver trait, and a four-point specification of what an implementation has to guarantee. | Nothing. See below. |
| .unp domains | crates/net/src/registry/domain.rs, bin/uninet-node/src/dns_chain.rs, and a 2,311-line HTTP API. Registration charges payment server-side and unconditionally, and records expiry and ownership; the NFT element is minted only when the request sets mint_nft. | /unp/:domain and /unp/:domain/*rest serve registered domains, and lookup_active treats a lapsed registration as absent. One caveat: lookup returns vec![leaf_hash], a single element the source itself comments as "Simplified". That is not a verifiable inclusion proof and should not be read as one. |
| Chain spawning | Two separate things share the name. crates/multichain/src/spawner holds a nine-step protocol with node allocation and hosting tokens. AppState::instantiate_child_chain creates a disk-backed, block-producing child chain with a deterministic genesis, this node as sole validator, and a free-tier fee config. | /api/tx/spawn-chain goes through the executor, which mints the hosting NFT and records the chain under an owner-scoped id, and then through instantiate_child_chain. ChainSpawner is referenced only inside crates/multichain and in tests/integration/src/multi_chain.rs; neither binary constructs one. |
| Ethereum bridge | crates/interop — bridge registry, a validator set with epochs and thresholds, and escrow with a seven-day lock TTL and a refund path for a mint that never lands. | Partly. /api/bridges/* drives the registry, the validator set and a relay log held in the node's own process; a "relayed" message is a hash appended to that log and nothing leaves the machine. The escrow and verifier modules have no reference in either binary. The crate's Cargo.toml declares no HTTP client, no RPC client and no light client — there is no transport in it at all. |
| Command-line client | bin/uninet-cli — seven subcommands: six read-only ones (status, health, block, receipt, chains, nodes) and a raw escape hatch that issues an arbitrary method and path against the inner router. | Reads, plus whatever raw is pointed at. There is no key generation, no identity creation and no transaction signing subcommand, so a write needs a session token and a signed payload produced somewhere other than the terminal. |
The UNP split
UNP is where the difference between category two and category three decides whether anything works end to end, so it is worth stating precisely.
The routing construction is implemented and tested. build_onion refuses any path whose hop count is not exactly the tier's. OnionDirectory::resolve_path rejects a path with any missing onion key rather than shortening it. RelayPool::select_circuit returns an error rather than a short chain. The node exposes POST /unp/onion, and that handler peels exactly one layer with the node's persisted onion secret, rejects a repeated ephemeral public key as a replay, caps total forwards at the tier's own hop count so a cycle is finite by construction, and refuses to forward to any address absent from relay_directory — a map initialized empty at state.rs:1235 that no code path writes to. POST /unp/relay, which peels the outer token layer and forwards to a peer's /unp/deliver, looks its next hop up in the same map, so it ends the same way: a node that has not opted into relaying forwards nothing, and nothing in this workspace opts it in.
Resolution has no backend. ThresholdResolver has no non-test implementor anywhere in the workspace. AppState.resolver is declared Option<Arc<dyn ThresholdResolver>> and constructed as resolver: None at state.rs:1226; relay_pool is None on the next line.
The relay branch of /unp/resolve is further out of reach than that. resolvability sorts an address three ways, and the relay attempt sits inside only one of them:
GET /unp/resolve?addr=...
│
├── Here self.node, this node's own pubkey hex, or a domain
│ registered on its DNS chain ──▶ node identity;
│ the client tunnels in
│
├── Unknown an unregistered domain, an unparseable address
│ ──▶ 404 UNP_ADDR_NOT_FOUND
│
└── Remote a hex address that is not this node, or a @username
│
├─ guarded by target_chain_id(addr), which returns Some
│ only for a DOMAIN form — and a domain is sorted Here
│ or Unknown, never Remote
│
└──────────────────────────────────▶ 404 UNP_ADDR_REMOTE
(try_relay_resolve not called)
So try_relay_resolve has one non-test call site, unp_gateway.rs:173, and its guard cannot be satisfied from that arm. In a running node the function is never entered.
Its refusal shape is still worth reading, because it is what would happen if a backend were injected, and the crate's own tests exercise it. Every failure inside try_relay_resolve is constructed by one function, relay_unavailable, which runs the requested tier through enforce_no_downgrade against what a direct connection is actually worth — PrivacyProfile::Standard. A chain configured Private or HighSecurity therefore gets DowngradeRefused, which the handler turns into a 503 PRIVACY_DOWNGRADE_REFUSED, rather than a direct connection underneath a client that still reports a private session. A Standard chain gets DirectPermitted and the handler falls through — to the same 404 above, because this node has no way to serve an address it does not host. The resolver refuses in the same shape: UnpResolver::resolve returns EnclaveUnavailable, resolve_to_token returns NoThresholdResolver, and a resolver that hands back the default all-zero session key is rejected so a client is never given a zero key to seal a payload with.
The consequence is one sentence: cross-node resolution does not work in any build of this workspace, private or otherwise. The cells, the layering and the reply blocks are code you can read and exercise locally; they are not a deployable private-hosting path.
What was removed, and why that is the outcome
Five hand-rolled primitives were deleted from crates/crypto on 2026-08-27. The reasons are recorded in crates/crypto/src/lib.rs under "# Deliberately absent", which lists them so nobody re-adds an approximation:
- Zero-knowledge proofs.
SchnorrZkp::verifyrecomputed the Fiat-Shamir challenge, discarded it, and returned whether both fields contained a non-zero byte — so any 64 non-zero bytes verified against any statement. Its three dependants inherited that. - Threshold signatures, and the
DistributedSigningSessionthat consumed them, which collected secret shares in one process rather than partial signatures on a wire. - Feldman verifiable secret sharing, whose commitments were a hash of the share, chosen by the same dealer. Shamir shares in this tree are documented as unverifiable.
Pkcs11Hsm, six methods that all returned an error, behind a feature flag that made it read as an available backend.SoftwareHsmis now the only backend and says so.
These were unsound rather than incomplete, and removing an unsound primitive is the correct engineering outcome. The routes that used to be backed by them — /api/crypto/zkp/*, /api/crypto/threshold/*, /api/crypto/smpc/* — return 501 with an error field and deliberately no field shaped like a result: no proof, no signature, no valid. A regression test in api/crypto.rs asserts none of them can return a stub value again. UniNet claims no zero-knowledge proof system, no verifiable secret sharing and no threshold signature scheme.
What the design does not address
Design-level limits, taken from the modules that implement the mechanism rather than from a policy statement:
- A global passive adversary. Fixed cells and constant-rate cover raise the cost of correlating flows. An observer who watches every link at once defeats low-latency onion routing. This is not a mixnet and makes no mixnet claim.
- End-to-end confirmation. A party observing both the entry and the exit of a circuit can correlate timing and volume. Relay diversity is the defence, and diversity is only as good as the attribution behind it — nothing in this workspace populates relay attribution, so in practice every relay counts as its own operator.
- Directory agreement.
OnionDirectoryis a local map. Key rotation is solved; agreement on which keys are canonical is not. Whoever fills a client's directory is a trusted component, not a verified one. - The
Standardtier, in the relay-anonymity sense. One hop means the single relay is both entry and exit and does see both the client and the destination. It is a latency tier and is documented as one. - The exit relay's view of the payload. Onion layers protect routing metadata. Payload confidentiality end to end is the tunnel's job, not the onion module's.
- The host operator. Isolation at the endpoint is container-level, and only on a Linux host with
crunorruncinstalled —bin/uninet-node/src/sandbox.rsactivates nowhere else, and the caller falls back to a plain process. An operator with root on the machine is inside the trust boundary either way.
Where to check each claim
The load-bearing claims above are read out of these files. They are listed so that disagreeing with this page is a matter of opening a file rather than taking a position. The inventory table names the rest.
| Claim | File |
| --- | --- |
| The external surface and the tunnel dispatcher | bin/uninet-node/src/api/unp_gateway.rs |
| resolver: None, relay_pool: None, empty relay_directory | bin/uninet-node/src/state.rs |
| No implementor for ThresholdResolver, and what one must guarantee | crates/net/src/unp/resolver.rs |
| Onion threat model, and the "# Not built" list | crates/net/src/onion/mod.rs |
| The reply-block wire format and who sees what | crates/net/src/onion/reply.rs |
| The five deleted primitives, and why | crates/crypto/src/lib.rs |
| 501 answers with no result-shaped field, plus the regression test | bin/uninet-node/src/api/crypto.rs |
| Wasmtime host functions, limits, and the documented no-ops | crates/vm/src/runtime/wasmtime_backend.rs |
| The interpreter's UNVM magic bytes and echo behaviour | crates/vm/src/runtime/mod.rs, crates/vm/src/compiler.rs |
| Pre-execution state-root commitment, checked before executing | crates/chain/src/block.rs, crates/chain/src/chain.rs |
| Depth bounds, token gates and the delegation walk | bin/uninet-node/src/rbac.rs |
| The single-leaf domain "proof" | crates/net/src/registry/domain.rs |
| Container isolation, and where it does not activate | bin/uninet-node/src/sandbox.rs |
| The non-verifying default consensus engine | bin/uninet-node/src/state.rs, crates/consensus/src/engine.rs |
Where any other document in the repository disagrees with these files, the files are correct. Nothing on this page is derived from a capability summary.
How it composes
Read this page against private networking, which describes the routing model the onion modules implement, and against execution for what runs at the far end of a route. Identity and authority cover the two subsystems whose node-side code carries explicit regression tests asserting their routes cannot return to stubs.
For the graded view of every subsystem, and the limitation attached to each one, go to build status. This page describes what the source does. That table is the single place the site records how far along each piece is, and every status chip on this page is rendered from it.
What is not finished
The repository ships no public bootstrap or seed address, no committed genesis/chainspec file and no hosted endpoint; peer discovery is a Kademlia behaviour that is constructed but never driven, and the only route onto a network is to be pre-listed as a validator in a config file every participant already holds.
The CLI is read-only — it has no key generation, no identity creation and no transaction signing — so any walkthrough involving a write path is written against the client application rather than the terminal.
The confidential resolver is an interface with no backend, and the node starts without one, so a request cannot be resolved to a service across nodes. Read the addressing and routing pages as design you can test locally, not as a deployable private-hosting path.
The allocation and dependency model are implemented, but consensus bootstrap for a new chain is not, and the node binary never invokes the spawner. Chains you can use are the ones created at genesis.