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.
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 globalconsolewith 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:
| Flag | Effect |
|---|---|
MOCKCVM=1 | Skips the SEV-SNP hardware check and attestation entirely |
ACCESS=1 | Skips 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
- Distribution and jurisdiction — the node quorum around this service, and an honest account of what is deployed today.