Chain-of-trust for Nox - Part 1: TEE foundation and boot-time attestation

About the Author

Aghiles Ait Messaoud is a PhD, Research Software Engineer working at iExec. He specializes in distributed systems and confidential computing. He currently works at improving the trustworthiness of iExec platform by leveraging TEE capabilities. You can contact him at aghiles.ait-messaoud@iex.ec for any question regarding the article.

Introduction

The Nox protocol performs confidential computation over encrypted user data in off-chain services. For such a system to be trustworthy, a user must be convinced of a non-trivial properties: that the code processing their data is exactly the code iExec claims to deploy, that it executes on genuine confidential-computing hardware, and that neither the cloud provider nor the protocol operator can observe or tamper with the data while it is being processed. Establishing this property requires a chain of trust, a sequence of cryptographically verifiable links connecting a hardware root of trust to the running workload.

This document is the first part of a series describing that chain of trust. It establishes its foundation: the Trusted Execution Environment (TEE) layer. We describe (i) the hardware substrate, Intel Trust Domain Extensions (TDX), which provides memory-encrypted, hardware-isolated Confidential Virtual Machines (CVMs) together with a signed measurement of their boot state; (ii) dstack, the open-source orchestration layer (developed by Phala Network) that boots, supervises and gates these CVMs; and (iii) how the two are combined to deploy the Nox off-chain components so that every CVM is provably running the expected code before it is granted access to any secret. (iv) We close by discussing Proof-of-Cloud as a complementary seal that binds an attested platform to a known physical hosting environment.

Subsequent parts build on this foundation to cover runtime attestation surfaced to end users, source-code binding, and on-chain governance of the authorized measurements. The present part deliberately limits its scope to the TEE foundation and to boot-time attestation.

The remainder of this document is organised as follows. In Background, we introduce the Nox protocol, define what we mean by a chain of trust, and present the two building blocks of its foundation, Intel TDX and dstack. In Dstack deployment for Nox, we describe how these are instantiated on a TDX host and how the resulting boot-time verification gates every CVM before it receives any secret. In Proof-of-Cloud as a Hardware Provenance Seal, we discuss the complementary seal that ties an attested platform to its physical hosting environment. Finally, the Appendix details the boot sequences of the dstack-KMS, gateway and business CVMs.

Background

In this section, we establish the context and vocabulary used throughout the document. We first introduce the Nox protocol and the components that must be protected, then define the chain of trust, and finally present the two technologies its foundation builds on: Intel TDX at the hardware level and dstack at the orchestration level.

Nox protocol

Nox (Welcome | Nox documentation ) is iExec's confidential-computation protocol for decentralized finance (DeFi). It acts as a privacy layer that lets smart contracts operate on encrypted data without ever exposing plaintext on-chain, by combining on-chain smart contracts with off-chain Trusted Execution Environments based on Intel TDX. Confidential values are referenced on-chain through opaque handles while the corresponding ciphertext is processed off-chain inside enclaves, so that applications retain full composability with the existing DeFi ecosystem: confidential ERC-7984 tokens remain ERC-20-compatible, contracts are written in standard Solidity, and end users transact from ordinary Ethereum wallets.

The protocol rests on three concepts. (i) A handle is a 32-byte identifier that acts as a secure pointer to an encrypted value stored off-chain, rather than the ciphertext itself. (ii) An on-chain Access Control List (ACL) governs which addresses may use a handle as a computation input or decrypt its underlying data, enabling selective disclosure to authorized parties such as auditors while privacy is preserved by default. (iii) The computation on confidential data itself takes place inside Intel TDX enclaves, so that even the infrastructure operator cannot read the plaintext during execution.

Operationally, Nox coordinates six components: the on-chain smart contracts (notably the NoxCompute library and the ACL), an event listener (Ingestor), a message queue (NATS), a computation engine (Runner), an encrypted data store (Handle Gateway), and a key-management service (KMS). A confidential operation follows three phases: input, compute and output. In the input phase, a user encrypts a value through the Handle Gateway (which stores the ciphertext and returns a handle with an EIP-712 proof) and calls a contract using the NoxCompute library, which validates the handles, derives a deterministic result handle and emits an event. In the compute phase, the Ingestor relays the event through NATS to a Runner, which fetches the operands, decrypts them inside the enclave via a decryption-delegation protocol with the KMS, executes the operation, and stores the encrypted result. In the output phase, an authorised user retrieves the ciphertext and decrypts it locally. Encryption relies on ECIES over secp256k1 with the KMS public key, and the KMS itself never handles plaintext: it only delegates decryption to the legitimate recipient by encrypting the ECDH shared secret under the requester's ephemeral key.

Several of these off-chain services handle sensitive material in clear within their address space, plaintext operands in the Runner, input plaintext and ciphertext in the Handle Gateway, and the protocol private key in the KMS. Protecting their confidentiality and integrity is the central security concern that the chain of trust addresses, which is why each is deployed as a TDX confidential workload whose boot-time verification is described in this document.

Chain of Trust

We define the chain of trust as the set of mechanisms that establishes a verifiable bridge of confidence between an end user and iExec, a sequence of links, each independently checkable, such that an external party can conclude, end to end, that a given Nox component is genuinely running the expected code on genuine confidential hardware. The foundational link, and the subject of this part, is boot-time verification: the guarantee that a CVM cannot obtain the secrets it needs to operate unless it has booted a known operating-system image and a known application stack on attested TDX hardware. The higher links of the chain, surfacing this evidence to users, binding the running images to public source code, and anchoring the set of authorized measurements on-chain, are treated in later parts.

Intel TDX

Intel Trust Domain Extensions (TDX) is a capability of recent Intel Xeon processors that lets a virtual machine run as a Trust Domain (TD), isolated from the host operating system, the hypervisor and the cloud operator. Two properties make TDX a suitable root of trust for Nox.

First, confidentiality and integrity of runtime state: the CPU transparently encrypts the memory of each TD, so that neither the host nor a privileged operator can read or undetectably modify the guest's memory. Each Nox service therefore runs inside a hardware-isolated CVM whose runtime state is opaque to the hosting provider.

Second, measured boot and remote attestation: as a TD is launched, the BIOS and the TDX module measure its initial state into a set of registers, the static measurement MRTD and the runtime-extendable registers RTMR[0..3], recorded in a signed TD-Report. From this report the platform produces a quote, signed through Intel's Data Center Attestation Primitives (DCAP) and chaining up to the Intel SGX Root CA. A remote verifier validates this quote to obtain a hardware-backed statement of which code the TD runs, and evaluates the platform's Trusted Computing Base (TCB) status against Intel collateral, accepting only an up-to-date TCB.

Running TDX in production imposes a hardware baseline. Nox runs only on bare-metal servers satisfying Intel's TDX enabling guide, summarised below.

Dstack

dstack (GitHub - Dstack-TEE/dstack: Open framework for confidential AI ) is the open-source orchestration layer, developed by Phala Network, that operates CVMs on top of TDX. It removes the need to manage Trust Domains by hand and provides the gating mechanism on which boot-time verification rests. Its main components are a Virtual Machine Monitor (VMM) and supervisor that boot and supervise CVMs from a measured, reproducible dstack OS image; a KMS CVM that derives application keys inside the enclave and decides which workloads may boot by checking their measurements against a whitelist; and a Gateway CVM that terminates public TLS (with automatic ACME certificates) and reverse-proxies traffic to each registered CVM over an encrypted WireGuard overlay, load-balancing natively between CVMs that share the same application identifier (appId). dstack also offers an optional on-chain authorization contract (DstackKms) able to whitelist OS-image and per-application compose hashes, a capability that Nox has deliberately not yet enabled, the corresponding whitelist being held off-chain at this stage.

Dstack deployment for Nox

This section describes how the foundation above is instantiated for the Nox protocol, and how it yields the boot-time verification guarantee.

General architecture

In this section, we give an overview of how dstack is deployed on a TDX host to run the Nox components, before describing each element in turn. The diagram below shows the overall layout; the tables that follow detail the components running on the host and those composing a single Nox CVM.

dstack architecture for Nox protocol 

‍

TDX Host components
Nox CVM component

‍

Proof-of-Cloud as a Hardware Provenance Seal

Measured boot and remote attestation establish what code a CVM runs and that it runs on genuine Intel TDX silicon. They do not, by themselves, establish where that silicon is, the physical and operational provenance of the host. Proof-of-Cloud is the complementary seal that addresses this gap: it binds an attested TDX platform to a known, reputable hosting environment, extending the hardware root of trust with evidence about the provenance of the machine itself.

In the current Nox fleet this property is tracked per node. The testnets fleet runs two TDX nodes, node1, hosted at OVH, and node2, hosted at phoenixNAP, of which node1 carries a Proof-of-Cloud seal and node2 does not. The distinction is meaningful for an external verifier: where present, Proof-of-Cloud raises the cost of a relocation or substitution attack by tying the attested platform to a provider's verified infrastructure, rather than relying on the quote alone to vouch for an otherwise anonymous host.

Concretely, Proof-of-Cloud is coordinated by the Proof of Cloud Alliance (https://proofofcloud.org/), a vendor-neutral group, of which iExec is a member, that maintains a signed, append-only registry binding TEE hardware identities (an Intel DCAP PPID , in case of TDX, extracted from the attestation quote) to independently verified physical facilities. Member organisations cross-verify each location during a dedicated whitelisting ceremony and when pushing a new entry to PPID registry so that a verifier can establish not only that a workload runs on genuine TDX silicon but also that this silicon sits in a facility multiple independent parties have vouched for.

Within the chain of trust, Proof-of-Cloud should be read as a provenance complement to, not a replacement for, TDX attestation: TDX attests the integrity and identity of the execution environment, while Proof-of-Cloud attests the integrity of its hosting context. Together they reinforce the foundational claim on which the remaining links of the chain, runtime attestation, source-code binding and on-chain governance, are built.

Appendix

dstack-kms Boot Sequence

The full sequence diagram for dstack-kms boot is available at dstack/docs/kms-boot-sequence.md at master · iExec-Nox/dstack

The diagram traces the boot of the dstack-KMS CVM with the local SGX key provider. In summary:

  1. CVM boot and measurement. The VMM starts the KMS CVM under TDX; the CVM boots the dstack OS image and runs dstack-prepare.sh, which measures the application info (the compose_hash, the derived app_id and the instance_id) and emits a runtime event that extends these values into RTMR[3].
  2. Sealing-key request. The script generates an ephemeral keypair and a TDX quote carrying its public key in report_data, then calls the host API over VSOCK, which forwards the request to the SGX key provider (gramine-sealing-key-provider) on localhost:3443.
  3. Mutual attestation and key release. The SGX key provider verifies the KMS TDX quote, derives a sealing key bound to the KMS measurements (MRTD + RTMR[0..3]) and to the hardware, encrypts it under the CVM's public key, and returns it together with its own SGX quote. The CVM verifies that SGX quote, collateral through the PCCS, TCB status, and that report_data commits to the encrypted key, then decrypts the sealing key with its private key.
  4. Key derivation. From the sealing key the CVM derives its application keys (the app_key used to self-sign an RA-TLS certificate, the disk_crypt_key, and a secp256k1 key) and, if a key_provider_id is pinned in app-compose.json, checks that the provider's MR enclave matches the expected value.
  5. Disk mount and container start. The CVM mounts its encrypted disk with the disk_crypt_key, persists the derived app keys, and starts the KMS container through docker-compose.
  6. KMS initialization. If root keys already exist on the encrypted disk they are simply reloaded. On a first boot, one of two paths is taken:
    • Bootstrap (first KMS): it generates fresh root keys (root CA, secp256k1 root, RPC key and a temporary CA), self-signs its certificates and persists them, no authorization check is performed, since this instance is the origin of the root keys.
    • Onboarding (any subsequent KMS): it obtains a temporary-CA-signed RA-TLS certificate from an existing KMS and requests the root keys over mTLS. The source KMS verifies the requester's TDX attestation and calls the Boot Authorization server (POST /bootAuth/kms), which authorizes only if the TCB is up to date and the OS-image hash, the aggregated measurement and the device id match the kms entry of auth-config.json. On success the root keys are returned and stored; otherwise onboarding aborts and the KMS does not start.
  7. Service ready. The KMS regenerates its certificates from the stored keys (with a fresh TDX attestation) and starts its HTTPS service (container port 8000, exposed as 9201 on the host).

In short, the KMS obtains its sealing key only after proving its measured identity to the SGX key provider, and a non-bootstrap KMS additionally requires Boot Authorization server approval before receiving the protocol root keys.

It is worth mentioning that the KMS onboarding feature is not used in iExec deployment of dstack as each TDX machine operates as a self-contained environment for an independent dstack deployment.

dstack-gateway Boot Sequence

The full sequence diagram for dstack-gateway boot is available at dstack/docs/gateway-boot-sequence.md at master · iExec-Nox/dstack

The diagram traces the boot of the dstack-gateway CVM and how it obtains its keys. In summary:

  1. CVM boot and measurement. The VMM starts the gateway CVM under TDX; it boots the dstack OS image and runs dstack-prepare.sh, which measures the application info (compose_hash, the derived app_id, instance_id) and extends these values into RTMR[3].
  2. App-key request with mutual attestation. The CVM fetches a temporary CA certificate from the KMS, builds an RA-TLS client certificate embedding its TDX quote, and calls GetAppKey over mTLS. It verifies the KMS server certificate (special_usage == "kms:rpc", emitting mr-kms to RTMR[3]), while the KMS verifies the CVM's attestation carried in the client certificate.
  3. Authorization. The KMS extracts the boot info (app_id, compose_hash, device_id, tcb_status, os_image_hash) and calls the Boot Authorization server (POST /bootAuth/app), which authorizes only if the TCB is up to date and the OS-image hash, app_id, compose_hash and device id match auth-config.json. On refusal the CVM reboots or shuts down.
  4. Key derivation and disk mount. On success the KMS derives the app keys from its root keys (disk_crypt_key, env_crypt_key, k256_key) and returns them; the CVM optionally checks key_provider_id against the KMS CA public key, persists the keys, and mounts the encrypted disk with disk_crypt_key.
  5. Post-mount setup. Environment variables are unsealed with env_crypt_key and the guest agent and Docker registry are configured. Gateway self-registration is skipped here (gateway_enabled=false for the gateway CVM itself).
  6. Gateway container initialization. docker-compose starts the gateway container, which generates its WireGuard keys and gateway.toml, configures the WireGuard interface, and obtains its RPC TLS certificate through the guest agent: a fresh P256 key and CSR are signed by the KMS (SignCert, after attestation and authorization checks), which returns the certificate chain (cert + app CA + root CA).
  7. Services up. Proxy certificates are loaded from the encrypted store, or a self-signed wildcard certificate (*.apps.{base_domain}) is generated, and the gateway starts its Proxy (port 443, mapped to 9204), RPC (8000 → 9202) and Admin (8001 → 9203) services.

In short, the gateway receives its keys only after mutual attestation with the KMS and Boot Authorization server approval; unlike an application CVM it does not register with another gateway, handling WireGuard and TLS termination itself.

Business CVM boot Sequence

The full sequence diagram for business CVM (app CVM) boot is available at dstack/docs/app-boot-sequence.md at master · iExec-Nox/dstack

The diagram traces the boot of a business (application) CVM, which provisions its keys like the gateway and then additionally joins the WireGuard overlay. In summary:

  1. CVM boot and measurement. The VMM starts the app CVM under TDX; it boots the dstack OS image and runs dstack-prepare.sh, which measures the application info (compose_hash, the derived app_id, instance_id) and extends these values into RTMR[3].
  2. App-key request with mutual attestation. The CVM obtains a temporary CA certificate (and the KMS ca_cert) from the KMS, builds an RA-TLS client certificate embedding its TDX quote, and calls GetAppKey over mTLS. It verifies the KMS server certificate (special_usage == "kms:rpc", emitting mr-kms to RTMR[3]), while the KMS verifies the CVM's attestation from the client certificate.
  3. Authorization. The KMS extracts the boot info and calls the Boot Authorization server (POST /bootAuth/app), which applies the same five checks, tcb_status up to date, and os_image_hash, app_id, compose_hash and device id matching auth-config.json. On refusal the CVM reboots or shuts down.
  4. Key derivation and disk mount. On success the KMS derives the per-app keys from its root keys, disk_crypt_key = HKDF(ca_key, app_id + instance_id + …), env_crypt_key = x25519(HKDF(ca_key, app_id + …)) and k256_key, and returns them with the gateway_app_id. The CVM optionally checks key_provider_id, saves the keys, mounts the encrypted disk with disk_crypt_key, and emits a system-ready event.
  5. Post-mount setup and env decryption. Encrypted environment variables are decrypted with env_crypt_key (x25519 Diffie-Hellman + AES-256-GCM) and written to /dstack/.decrypted_env; the guest agent is configured.
  6. Gateway registration (WireGuard). Unlike the gateway CVM, the app CVM joins the WireGuard overlay through which it is reached: it requests a KMS-signed RA-TLS client certificate (SignCert), connects to the gateway over mTLS (verifying the gateway's app_id matches the gateway_app_id returned by the KMS), and calls RegisterCvm with its WireGuard public key. The gateway verifies the app's identity, allocates a WireGuard IP and returns the peer configuration, after which the CVM brings up its dstack-wg0 interface.
  7. Containers up. docker-compose starts the application container(s); at runtime the app can optionally use the guest agent over /var/run/dstack.sock to derive purpose-specific keys (get_key), obtain a KMS-signed TLS certificate (get_tls_key), or fetch a TDX quote for remote attestation (get_quote).

In short, an application CVM follows the same attested, authorized key-provisioning path as the gateway, and then registers with the gateway to enter the encrypted WireGuard network that fronts it.

Environment variables cryptographic state

The full sequence diagram for encrypting and decrypting secret environment variables is available at:  dstack/docs/env-encryption-sequence.md at master · iExec-Nox/dstack

Secret environment variables are encrypted by the operator before deployment and decrypted only inside the attested CVM, so that neither the VMM nor the KMS ever sees their plaintext. In summary:

  1. Get the encryption public key. From the compose file the operator computes the app_id and calls the KMS (GetAppEnvEncryptPubKey). The KMS derives a per-app X25519 key pair deterministically, secret = HKDF(ca_key, app_id + "env-encrypt-key"), and returns the public half (env_public_key) together with a k256_root signature over it, which the operator verifies against the whitelisted signer.
  2. Encrypt the variables (operator side). The operator serialises the .env file to JSON, generates an ephemeral X25519 key pair, computes a shared secret by Diffie-Hellman with env_public_key, and encrypts the JSON with AES-256-GCM under a random 12-byte IV. The packed blob is eph_public_key (32 B) || IV (12 B) || ciphertext.
  3. Deploy. The operator passes the hex-encoded blob to the VMM (CreateVm), which stores it in host-shared/ and never decrypts it.
  4. CVM boot. The VMM starts the CVM under TDX; the CVM loads the encrypted blob from host-shared/.
  5. Get the decryption key. During key provisioning the CVM calls GetAppKey over the attested mTLS channel; after verifying attestation and authorization, the KMS derives the same key pair and returns the private half as env_crypt_key.
  6. Decrypt (CVM side). The CVM splits the blob back into eph_public_key, IV and ciphertext, recomputes the identical shared secret by Diffie-Hellman with env_crypt_key (the X25519 property guarantees both sides agree), decrypts with AES-256-GCM, filters the result against allowed_envs from app-compose.json, and writes it to /dstack/.decrypted_env for the containers.

The construction provides confidentiality (only the attested CVM holding env_crypt_key can decrypt), integrity (AES-GCM detects tampering), forward secrecy (each deployment uses a fresh ephemeral key that is then discarded), and blindness of both the VMM (which only stores the ciphertext) and the KMS (which only serves keys). The env_crypt_key is app-specific, derived from app_id alone, so every instance of the same app shares it, while different apps cannot read one another's variables.

‍