Skip to content
Tenzro
Documentation menu
Build and operate

Confidential VM images

Run tenzro-node in an Intel TDX or AMD SEV-SNP confidential VM, or an AWS Nitro enclave, from a measured release image, and check that a node and the proofs it makes come from the published release.

Testnet v1 publishes measured images of the node, so a node running in a confidential VM can prove which release it runs:

ImagePlatformWhat it measures
tenzro-node-cvm-x86_64Intel TDX and AMD SEV-SNP guests (one UEFI disk)the node and its operating system
tenzro-node-cvm-x86_64-proverthe samethe node plus the finality prover and its Groth16 prover container
tenzro-node-nitro-x86_64.eifAWS Nitro Enclavesthe node and the libraries it links

The images carry the released tenzro-node-linux-x86_64-cpu binaries unchanged. They are built by scripts/release/build-cvm-image.sh and scripts/release/build-nitro-eif.sh from that archive; two builds of the same inputs give the same bytes, so anyone can rebuild an image and compare its digests with the published ones.

What is measured

The confidential VM disk has three partitions: an EFI system partition holding one file, a read-only root file system, and its dm-verity hash tree. The EFI file is a unified kernel image: the kernel, the initrd and a kernel command line that carries the verity root hash. Changing any byte of the root file system changes the root hash, which changes the unified kernel image, which changes what the firmware measures when it starts it.

  • Intel TDX. The firmware records the unified kernel image's Authenticode digest in RTMR1, with the disk's partition table, and the image's own stub records its sections in RTMR2. MRTD and RTMR0 describe the platform's firmware and virtual hardware. scripts/release/cvm-measure.py verify replays the firmware event log against the quote, checks that the image and the kernel it carries are the only programs the firmware started, and compares RTMR1 and RTMR2 with the published values.
  • AMD SEV-SNP. The launch measurement covers what the hypervisor loads before the guest runs. When the firmware boots the disk, as on cloud platforms that supply their own firmware, it measures that firmware only: compare it with the firmware measurement the platform publishes, and bind the image through the guest's TPM event log. On your own host, boot the unified kernel image directly (QEMU -kernel tenzro-node-cvm-x86_64.efi with an SEV-SNP OVMF build and kernel hashes on); the launch measurement then covers the image, and sev-snp-measure --mode snp --ovmf <OVMF.fd> --vcpus <n> --vcpu-type <type> --kernel tenzro-node-cvm-x86_64.efi gives the expected value for that host.
  • AWS Nitro. PCR0 measures the whole enclave image, PCR1 the kernel, command line and bootstrap ramdisk, PCR2 the node's ramdisk.

The published values for this release are in the table at the end of this page and in each image's .measurements.json / .pcrs.json.

Booting the image

The disk is a plain UEFI disk (.raw; .gce.tar.gz holds it as disk.raw for cloud image import). It needs:

  • a confidential VM with Intel TDX or AMD SEV-SNP and a virtual TPM 2.0 (the node's keys live in the TPM; a guest without one refuses to start the node);
  • a disk of at least 8 GB: on first boot the free space becomes the state partition (/var), which holds the chain data, the TPM-sealed authorization of the node's keys and nothing else;
  • outbound network access (DHCP). The node's RPC listens on loopback only.

On first boot the image seals a fresh authorization for the node's TPM keys to the guest's TPM, provisions the node's keys (tenzro-node tpm provision) and starts the node. Node flags go in /var/lib/tenzro-config/node.env as TENZRO_NODE_ARGS=...; on Google Cloud the instance metadata attribute tenzro-node-args sets them. Flags are configuration, not code, and are not part of the measurement.

When the node answers, the image prints to the serial console the detected TEE, the measurement registers of a fresh report, and the report and the firmware event logs (gzip, base64), in lines tagged TENZRO-DETECT, TENZRO-MEASUREMENTS, TENZRO-REPORT and TENZRO-EVENTLOG-*.

Checking a node

Ask the node for a report and its registers:

tenzro tee attest --out report.json

tenzro_getTeeAttestation returns the registers (measurements: MRTD and RTMR0-3 on TDX, MEASUREMENT on SEV-SNP) and the report itself (report_hex). tenzro_verifyTeeAttestation verifies a report against a nonce from tenzro_teeAttestationChallenge and returns the verified registers.

For a TDX guest, check the image too:

python3 scripts/release/cvm-measure.py verify \
  --measurements tenzro-node-cvm-x86_64.measurements.json \
  --report report.json --eventlog CCEL.bin --platform gcp-tdx

CCEL.bin is the guest's /sys/firmware/acpi/tables/data/CCEL (also printed on the console).

Proofs made in the VM

The prover image runs both stages of the finality prover on the machine: the STARK stage (tenzro-finality-prover stark, on the CPU here; on an NVIDIA GPU in a GPU confidential VM) and the Groth16 wrap (tenzro-finality-prover wrap, on the CPU, in the Groth16 prover container the image carries by digest). A proof made there is bound to the machine's attestation:

tenzro-finality-prover wrap --receipt receipt.bin --out proof.json
tenzro zk attest-finality-proof --proof proof.json --out bound.json
tenzro zk verify-tee-finality-proof --bundle bound.json

tenzro_attestFinalityProof verifies the seal, then asks the TEE for a report whose report data commits to SHA-256("tenzro/zk/tee-bound-finality-proof" || image_id || SHA-256(journal) || SHA-256(seal) || prover_digest), where prover_digest is the digest of the Groth16 prover container. Any node verifies the bundle with tenzro_verifyTeeFinalityProof: the seal, the prover, the binding and the vendor evidence, returning the verified registers. Comparing those registers with the published measurements of the prover image shows the proof came from the published prover in a measured VM.

Receipts placed in /var/lib/tenzro-prover/inbox/*.receipt are wrapped, bound and verified automatically, with the results in /var/lib/tenzro-prover/outbox/. On Google Cloud the instance metadata attribute tenzro-prover-receipt (a gs:// object the instance's service account can read) places one receipt in the inbox.

The GPU STARK stage needs an NVIDIA GPU in confidential-computing mode inside a TDX or SEV-SNP guest, and a prover built with the cuda feature; the evidence then also carries the GPU's attestation. The prover image as published runs both stages on the CPU.

Published measurements (testnet v1)

These values are for the release candidate (node built from commit dc36c9fe, images built with SOURCE_DATE_EPOCH=1791298041). The release build of testnet v1 rebuilds the node, so the images and these values are refreshed then; the release's .measurements.json and .pcrs.json files are authoritative.

tenzro-node-cvm-x86_64

ValueSHA-384 / SHA-256
disk (.raw, SHA-256)cbf8a848fcbf30c3bef02b5738d860e6e6b479aa0f70d52312832696de1248d9
unified kernel image (.efi, SHA-256)91d77b458aced8c92864db69f3142a7b817792bef1e9e9da83e9c2b8d279dea3
dm-verity root hashe80e17ec3a3fd67d95bbaaecfb71df43f832fe595af238dc2f3ee81362c47e77
unified kernel image, Authenticode7df00391382ee1732abdd99aaccf81209de8a2b523613ad6580da5818914f8abd5a62537f551308aef4d6a0838774ae5
kernel it starts, Authenticode8fbeb1462072cc699e357771ed8276a46a2bc2cc8db5df2bdc43e0099921b6e6289cab24efba9f8612067443ac641d2e
TDX RTMR1c00baf2814f20c4fbc9e9c6d5d16bf31ed8283e3a799b6a91f0faccc8e9f5c31fd3ce711f270b439b2c1f1f144d052b4
TDX RTMR26f6d2f3d3fc3b0e0becfa89238397f4be702f6ef7a35c235c45a5a87ebe927880c71dcde9e24a5149d6615ce99e8ce42

tenzro-node-cvm-x86_64-prover

ValueSHA-384 / SHA-256
disk (.raw, SHA-256)1708949c7091006a6f3a63fe51c7a08e7b0a17e50672132d1701dad7b22432d3
unified kernel image (.efi, SHA-256)0c65f28013507b2297a89ccd0fb270c1296c5e2b59f009738584ef6fd20f2371
dm-verity root hashbc935795b9fa73c11b5e40a2e5340161ef6afa174380a1dd8906ac7d131117b7
unified kernel image, Authenticode22a66bc1115783b335b99c082de1b2e138cf341061ff555f8d0b97facafa98b20afcca22fec63602feda5084445cb481
TDX RTMR1bd17e08b18818b0b52bf0d26d951351b3e8bb94aafda8ff15725acd24c8b28429495f5f65e9cad22c711fafbbc279d8c
TDX RTMR2b208e93aca7db283218667617787f46fc243787106ffbd6c81f17cfb3e30f87441834a76cb1e0cd07d49555fa7b31b7f
Groth16 prover containerrisczero/risc0-groth16-prover@sha256:7f173963196570b7a71816ed70565a4579264c5d2e3e0ecb028102538ad0e331

tenzro-node-nitro-x86_64.eif

RegisterSHA-384
PCR0955ceb0ec943e9d337a2bc75da6d87b03ca16f70c5ea8bc1545a3fd84913688bef41bdab6f3812f1fef80cbe246a9ab5
PCR18e229f58d29ff87baf647b3771a37b966d29a7cdb499e95b4504ba5a9087d095a34c53d5028d7ebf69a1df2f789b7a80
PCR27f46aa3c49f728a26c07ed3995107b2d74fb94d6ec010452170ca2fdac766b88e0293a39e79e2bff03bf73a7ec21f2f0

Platform registers on Google Cloud. MRTD, RTMR0 and the SEV-SNP launch measurement belong to the platform's firmware, not the image; each is signed for by the platform's firmware endorsement (gs://gce_tcb_integrity/ovmf_x64_csm/<tdx|sevsnp>/<value>.binarypb). Observed values:

MachineRegisterSHA-384
c3-standard-4, Intel TDXMRTDc1ee9c16e3afc506cfe042c5b846a368528f3b37618eafb27469bc114cf914e9222c91618470e7f2b28ac360968270a5
c3-standard-4, Intel TDXRTMR0c49d22aff6edb37cb6178defb05e0e2b512c26960e6ee73b1ea303365a31def807ab2ad71e5874236feca2ca552c6307
n2d-standard-2, AMD SEV-SNPMEASUREMENT0e017d2fba7c4964575fdd8c770061abf07f0753148d030f94e969a2d1fa5a5747ba0ced9174f2d44b5f6663fa2de00f