Distribution and jurisdiction
Splitting a key 3-of-4 only means something if the four share-holders can genuinely fail, or be compelled, independently. Four shares on four VMs in one account, in one datacenter, under one company's control is a 3-of-4 split with a 1-of-1 failure mode. This page is about that gap: what the topology is designed to be, why it is shaped the way it is, and — plainly — what is actually deployed right now.
What the quorum is supposed to buy
The 3-of-4 threshold is doing two different jobs at once, and they pull in opposite directions.
Availability. Any one node can be lost — hardware failure, a provider outage, a region going dark — and the remaining three still form a quorum. Settlement continues. This is why the threshold is 3 and not 4.
Resistance to unilateral control. No single party can sign. A court order, a subpoena, a rogue administrator or a breach at any one of the four yields exactly one Shamir share, which is information-theoretically nothing. To actually move funds you would need three of the four to be compromised or compelled at the same time. This is why the threshold is 3 and not 1 or 2.
The design goal is that the three you would need are not reachable by one action, one company, or one legal system. In production that means no single operator, cloud provider or government can obtain a key: rebuilding one takes 3 of the 4 independent operators, in separate countries.
The designed topology
Four chain nodes, deliberately one per cloud provider:
| Node | Provider | Why this one |
|---|---|---|
| 1 | GCP | Also hosts the signing layer — the only provider in the set with SEV-SNP hardware matching what the code attests against |
| 2 | Exoscale | Swiss-operated. Puts one node outside the three large hyperscalers, commercially and legally |
| 3 | Hetzner | German, EU. Cost and simplicity; a different corporate entity and a different legal system again |
| 4 | AWS | Session Manager removes port 22 from the internet entirely, so two of the four nodes need no inbound SSH at all |
The runtime is Podman with rootless Quadlets and systemd supervision — no Docker Compose, no Kubernetes, no orchestration layer with cluster-wide credentials that would quietly re-centralise what the topology is trying to separate.
The jurisdictional argument, stated carefully
The Exoscale node is in the set specifically because Switzerland is not an EU member. An Exoscale node and a Hetzner node sit under two genuinely separate sovereign legal systems with separate courts: a compulsory order against one does not reach the other. That matters more than the geographic spread it is often confused with — two datacenters 1,000km apart under the same legal system are, for this purpose, one share.
And now the part that matters more for an honest assessment: GCP and AWS are both US companies, so the CLOUD Act reaches their data regardless of which datacenter it physically sits in. That is the largest cross-border legal-access exposure in this set, and it means the four providers do not represent four fully independent legal systems. They represent roughly three, with two of the four correlated.
Three of four is the signing threshold. If a single US legal instrument can reach two nodes, the margin is one share, not two. That is a real limitation of the current provider selection, not a hypothetical one; it is understood as such, and adding jurisdictional diversity is a known direction rather than a solved problem. Anyone evaluating this rail for funds that would attract that kind of attention should weigh it directly rather than reading "four providers" as "four jurisdictions".
The signing layer is the narrowest constraint
The confidential VM cannot be distributed the way the nodes can. It requires AMD SEV-SNP, Milan generation, and confidential compute must be enabled at instance creation. Of the four chain-node providers, only GCP offers hardware matching what the attestation code checks; Exoscale, Hetzner and AWS cannot host it at all. Azure's DCasv5 family is the only identified alternative home.
The two-party MPC-TSS method therefore has a specific, acknowledged weakness: the two parties whose independence the protocol depends on are, at present, both constrained to the same provider. The protocol is genuinely two-party — two separate processes with independent state, which is what makes the cryptography meaningful — but "two separate operators in two separate jurisdictions" is what the production design adds for the cggmp24 protocol that runs today: its second VM is designed to run on Azure, operated by a partner organisation rather than Varnir. Today the two-party method runs on testnet only, with both VMs on one host.
Pre-generated wallet pools
The reason a wallet is available the instant you ask for one — despite generation requiring a quorum of nodes and, for MPC-TSS, a multi-round interactive protocol — is that keys are generated ahead of demand.
Each gateway maintains pools per network and per key method, topping them up in
the background when a pool falls below its threshold, and tracking them in a
local SQLite table kept in sync from the ledger's own wallet-assigned events.
The standard and mpc-tss pools are separate: MPC-TSS keys cost considerably
more to produce, so that pool is smaller and refills in smaller batches.
Two consequences follow from that:
- Claiming a wallet id is an unauthenticated read. It hands out which id is
currently free. It does not decide who owns one — ownership is established by
a transaction signed with your own identity key, and
Varnir.AssignOwner's consensus vote is what actually rejects a double-assignment. The pool table is a capacity-planning cache, not the source of truth. - A pre-generated key was already split before anyone claimed it. It has no privileged period during which someone held it whole and waiting; the shares were distributed at generation, long before the wallet had an owner.
You can see the unassigned pool for a gateway at scanner.varnir.site/free-wallets.
What is actually running today
Everything above describes the production design in infra/chain-node/ and
infra/providers/. The public testnet runs from infra/single-dev/, and it is
built for a different job: speed and delivery. One host keeps it cheap to run
and quick to redeploy, so new features reach a live network the day they are
built. Production spreads the same code across separate geographies, cloud
providers and the organisations that maintain the nodes. The differences:
| Designed | Running today | |
|---|---|---|
| Chain nodes | 4 VMs, 4 providers | All 4 on a single cloud VM |
| Jurisdictions | CH / DE / US ×2 | One |
| Node hardening | Firewalled, rootless Quadlets, storage port loopback-only | Deliberately permissive; 80/443 restricted to Cloudflare ranges, storage port loopback-only, little else |
| Confidential VM | AMD SEV-SNP, attested at boot, SSH disabled | MOCKCVM — no SEV-SNP hardware, attestation bypassed, ACCESS=1 |
| MPC-TSS parties | Two separate hosts | Two separate containers, same VM |
| TLS between MPC hosts | Mutual TLS pinned to each VM's attestation evidence | Mutual TLS pinned to mock evidence (no hardware attestation) |
| Assets | — | Testnet only. Sepolia, Nile, BNB, Base, Arbitrum and Amoy testnets. No value |
infra/single-dev/README.md is clear about its role: it holds testnet coins
only, and keys on that host should be treated as already compromised. It exists
so that something real and reachable is there to point a wallet, a scanner and
an SDK integration at, today.
So, precisely:
- The cryptography is real and running. Shamir 3-of-4, the two-party threshold ECDSA, the quorum gating on generation and signing, the payload matching, the share hash verification — all of it executes on every wallet the network creates, and you can watch it happen.
- The distribution comes with production. On testnet the four node processes share a machine, an operator and a jurisdiction. The code that separates them is built; production deploys it across separate operators, providers and countries.
Testnet handles no real funds. Real funds wait for that production deployment.
What this costs you
That is how keys are held, at the level of detail someone signing off on a payment rail should demand of it. Here's what it costs you as an integrator: nothing. None of it is your problem to operate.
You do not run a node, hold a share, join a quorum, deploy a confidential VM, or implement any of the threshold cryptography above. You generate a keypair and call two methods, and you have a wallet that can receive money.
import {VarnirClient} from '@varnir/chain-client';
import {generateRecoveryPhrase, derivePrivateKeyFromPhrase} from '@varnir/chain-client/keys';
const phrase = generateRecoveryPhrase(); // 12 words, yours alone
const {privateKeyHex} = derivePrivateKeyFromPhrase(phrase);
const client = await VarnirClient.onboard({network: 'testnet', privateKeyHex});
const wallet = await client.getWallet('niles'); // an address that can receive
Underneath that is still three signed transactions — onboarding, the pool claim, the ownership assignment — every one of them signed locally, none of them sending a private key anywhere. The L1 key behind the address you get was generated inside the confidential VM, split before it finished existing, and distributed across the quorum before you asked for it. You never touch it, and neither does anyone else.
The TypeScript guide is the complete runnable flow.