Skip to main content

The confidential VM

Every L1 wallet key on the network is born inside this service and never exists whole anywhere else. How keys are held covers what happens to the key once it exists; this page covers the machine it happens on, and — more usefully for due diligence — the mechanisms by which you can check that machine from the outside rather than taking its word for anything.

Read this before the rest of the page

The service described below runs today with MOCKCVM set, which bypasses the hardware attestation entirely. The attestation code is real, the hardware requirement is documented and enforced when the flag is absent, but the testnet you can reach right now is not running on attested confidential hardware. Full detail in what is actually running.

The threat it is built against

The assumption is that the operator of the machine is not automatically trusted. A conventional VM gives its cloud provider — and anyone with root on the hypervisor, and anyone who can compel either — the ability to read guest memory. For a process whose entire job is to hold a private key in RAM for a few milliseconds, that is the attack.

apps/confidentialVM is built so that the following are all separately false:

  • the host operator can read the key out of memory
  • an administrator can log into the box and change what it does
  • the service can be quietly swapped for a modified build without an outside party being able to detect it
  • key material can leak through logs or disk

AMD SEV-SNP

The service attests against AMD SEV-SNP, specifically the Milan generation (3rd-gen EPYC). SEV-SNP encrypts guest memory with a key held in the CPU's secure processor and unavailable to the hypervisor, and adds integrity protection against a malicious host remapping or replaying guest pages. The practical consequence is that the cloud provider hosting this VM cannot read what is in it, and a compelled provider cannot be made to.

The generation is pinned in two places (package.json's attest:verify and src/confidential.ts's snpguest fetch ca pem milan), so a Genoa-or-later SKU fails attestation as the code stands rather than silently degrading. That is deliberate: an attestation check that quietly accepts hardware it was not written for is worse than no check.

Because SEV-SNP is a creation-time property of an instance and cannot be enabled afterwards, and because only some providers offer it, this constrains where the signing layer can run at all — see distribution.

Three checks at boot, all of which must pass

From src/index.ts, the service will not bind a port unless all three return favourably:

if ((await Confidential.basics()) &&
(await Confidential.attest()) &&
!Confidential.accessible()) {

basics() confirms the kernel actually reports AMD SEV-SNP active, by reading the kernel log. (This is why the host needs kernel.dmesg_restrict = 0; without it the check fails with a misleading "Couldnt access DMESG" rather than a hardware error.)

attest() generates a fresh SEV-SNP attestation report and verifies it against AMD's key distribution service, accepting only when the verification output contains VEK signed the Attestation Report. This is a live check against AMD's certificate chain at boot, not a cached assertion — which is why the host's egress rules must reach AMD's service even while its ingress is closed to almost everything.

accessible() is the one that tends to surprise people. It runs ssh localhost and passes only when the connection is refused. The service considers a reachable SSH daemon to be a failure condition and refuses to start. The intended end state for the host is systemctl disable --now ssh, with all administration through the provider's out-of-band console. There is no remote shell on the machine that generates the keys, by design and by self-enforcement.

Verifying the running code from outside

This is the mechanism worth the most attention, because it addresses the question every other control leaves open: how do I know the binary running right now is the one you published?

The service exposes GET /verify/:salt. You supply an arbitrary salt. It returns a hash computed over, in order:

  • a boot-time hash of every file in its own directory tree
  • a re-computed, salted hash of that same tree at request time
  • the runtime string form of its own loaded functions — the key generators, the signers, the redistribution logic, every attestation routine, the hardening function, the HTTP listener
  • the source of every registered route handler, iterated over

all interleaved with your salt and the service version. Because the salt is yours and chosen per request, a previous response cannot be replayed at you, and a modified service cannot serve a pre-computed answer. Because the hash covers loaded function bodies and not merely files on disk, a service that was patched in memory after start-up produces a different answer than one that was not.

Pair that with GET / — which returns the version, the AMD certificate chain URL, the fetched ark/ask certificates, and the exact openssl verify command to check them yourself — and an outside party can independently establish both what hardware this is and what code is on it, without trusting any claim made by Varnir.

It also means the honest disclosure is unavoidable rather than voluntary: GET / reports isMocked and isAccessible directly. A deployment running with the attestation bypassed says so, to anyone who asks it.

Runtime hardening

  • All console output is disabled. Harden() (src/harden.ts) replaces the global console with a proxy of no-ops immediately before the server starts listening. Key material cannot leak through a stray log line, an unhandled error trace, or a debugging statement someone forgot to remove — the sink is gone at runtime.
  • Nothing is persisted. Generated keys exist only in the generating function's scope, are deleted (delete key.prv) the moment shares exist, and reconstructed keys are dropped (key = undefined) immediately after producing a signature. There is no key database on this machine to steal, and its in-memory session state expires on a short TTL.
  • It holds no shares. Between requests the VM stores no fragment of any key. It is a pure function of the shares the node quorum sends it, which is why compromising it in isolation yields nothing — there is nothing at rest to take.
  • Ports. 443 inbound, restricted to specific callers and deliberately not fronted by a CDN. Port 22 closed — not allowlisted, closed. Egress open only as far as AMD's attestation service needs.
  • Not containerised, not co-located. It runs as a bare systemd unit on its own VM. It is explicitly excluded from the chain-node topology and must not share a host with one, because a chain node is administered over SSH and this machine's entire security posture depends on not being.

The bypass flags, and why they are documented here

Two environment variables disable the checks above:

FlagEffect
MOCKCVM=1Skips the SEV-SNP hardware check and attestation entirely
ACCESS=1Skips the SSH-unreachable check

They exist so the stack can be developed and tested on ordinary hardware. Both are set on the current testnet deployment, because it runs on a VM with no SEV-SNP hardware, and both are reported by the service's own status endpoint.

Neither belongs on a deployment holding anything of value, and infra/confidential-vm/README.md says exactly that. A reader evaluating this should treat every hardware-backed claim on this page as architecture that is implemented and testable but not currently exercised, and weigh the sections above accordingly.

Next