Skip to content

Conversation

AmeanAsad
Copy link

@AmeanAsad AmeanAsad commented Jun 12, 2025

Overview

This PR adds Trusted Execution Environment (TEE) attestation report verification from Lunal's TEEs to proving service clients. This implementation came from @phklive's request to demonstrate an end-to-end flow for confidential delegated proving. With this PR, clients can verify that their delegated prover was run inside a TEE and thus preserved the input and processing privacy of their request.

Note: We tested this end-to-end on versions 0.9.0 of the miden client and the miden prover proxy. The reason behind using these versions is for compatibility with the Miden Testnet.

Implementation Details

Changes

Attestation Verification Integration

  • Add TEE attestation report verification to the proving service client.
  • Verify attestation reports attached in the Attestation-Report HTTP response header from TEE-enabled delegated provers.

New Dependencies

Architecture

  1. Client submits a proving request to a delegated prover running inside a TEE.
  2. The Lunal proxy service forwards the request to the Miden proving service running inside the TEE.
  3. The Miden proving service processes the client request and generates a proof.
  4. The proxy returns the generated proof along with a cryptographic attestation report in the Attestation-Report HTTP response header.
  5. The client verifies the attestation report and confirms the proof was generated in a private, trusted environment.
  6. Now verified, the standard proving workflow continues from there.

Backwards Compatibility

  • If an attestation report is present in the Attestation-Report HTTP response header? → Verify the attestation.
  • If there's no Attestation-Report response header or no attestation report in the Attestation-Report response headers? → Proceed without verification (legacy behavior).
  • No breaking changes to existing client configurations.

Lunal Proxy Service

Every Lunal machine comes with a proxy service pre-installed in the ~/lunal folder. This proxy acts as the bridge between external clients and the Miden proving service running inside the TEE, handling attestation report generation and response headers.

Running the Proxy

Basic Usage

cd ~/lunal
./proxy --host <hostname> --upstream <upstream-url>

Parameters

  • --host: The external hostname that the proxy will serve (e.g., miden-prover.lunal.dev)
  • --upstream: The local Miden service URL to proxy requests to (e.g., http://127.0.0.1:8082)

Example

./proxy --host miden-prover.lunal.dev --upstream http://127.0.0.1:8082

Running as a Background Process

For production use, you'll want to run the proxy as a background process. There are several options like systemd, screen, tmux, or process managers. Here's an example using the daemon tool:

cd ~/lunal
sudo daemon \
  --name=proxy \
  --pidfile=/var/run/proxy.pid \
  --respawn \
  --env="PATH=$PATH" \
  --chdir="$(pwd)" \
  -- ./proxy --host miden-prover.lunal.dev --upstream http://127.0.0.1:8082

You can also use other process managers like systemd, supervisor, or simply run it in a screen/tmux session depending on your preference.

Testing Changes

To test:

  • Spin up a delegated prover inside a TEE (this can be provided by Lunal folks). This will have an endpoint (e.g https://miden-prover.lunal.dev/)

  • Start the Lunal proxy service on the TEE machine using the instructions above

  • Initialize a local miden client (https://github.com/0xMiden/miden-client)

  • In the miden-client.toml, set the remote_rpc_endpoint to the delegated prover url:

    remote_prover_endpoint = "https://miden-prover.lunal.dev"

Migration Strategy

Phase 1: Parallel Deployment

  • Deploy TEE-secured delegated prover(s) alongside existing, non-TEE-secured delegated provers.
  • Separate, independent endpoints will exist in parallel for testing and gradual adoption.
  • Full backward compatibility maintained.

Phase 2: Testing & Validation

  • End-to-end, production testing with TEE delegated provers.
  • Validate client-side attestation verification.
  • Benchmark performance and reliability.

Phase 3: Transition (Optional)

  • Gradual migration of all traffic to TEE-secured delegated provers.
  • Optional requirement and enforcement of attestations by clients.
  • Graceful deprecation of non-TEE, non-private delegated provers.

@phklive
Copy link
Contributor

phklive commented Jun 13, 2025

Curious why the diff is so large yet so few things have been added.

Most apparently in the cargo.lock.

@AmeanAsad
Copy link
Author

@phklive the cargo.lock is indeed the reason for the large diff, this is due to adding attestation-rs (lunal's attestation crate) as a dependency. the actual code diffs are 13 lines.

@Mirko-von-Leipzig
Copy link
Collaborator

@phklive the cargo.lock is indeed the reason for the large diff, this is due to adding attestation-rs (lunal's attestation crate) as a dependency. the actual code diffs are 13 lines.

Part of the problem is that there are a lot of different dep versions e.g. this PR adds a 2nd hyper/http etc with different version to the one we have. I haven't looked into it much deeper beyond skimming the lockfile diff.

@bobbinth
Copy link
Contributor

Thank you very much for the awesome work! Being able to do delegated proving inside a TEE is a really cool feature, and I think it would be a great value add.

I haven't looked through everything in detail yet, but I do have a question: how does the user know that their request is going to a TEE? My understanding is that the attestation in the response header proves that the proof was generated in a TEE, but how the user can be sure that the encrypted request went directly to a TEE w/o someone in the middle having seen it?

That is, what currently prevents the following scenario:

  1. User sends a request via HTTPS to a remote prover.
  2. The remote prover receives the request unencrypted (just as our current remote prover does).
  3. The remote prover then submits this requests to a TEE and sends back the proof with the attestation generated by the TEE.

Would the user be able to tell in this scenario that the remote prover (in step 2) was able to see the request in plaintext?

@AmeanAsad
Copy link
Author

Great question @bobbinth!

To provide some background, there is a lunal proxy that runs inside every TEE. That proxy is used to establish a TLS session with any client communicating with the TEE to prevent a man in the middle attack. TLS ensures that user data is encrypted while it's being transmitted to the TEE. Since TLS terminates inside the TEE, the data is only decrypted inside the TEE where it is protected by the TEE boundary from any external party. The attestation report also includes a nonce generated by the verification client in order to prevent replay attacks for attestation verification.

With that in mind, based on our discussions with the Miden team, Miden currently runs the delegate/remote prover as one big instance. Therefore, our current setup assumes that the client communicates directly with a remote prover running inside the TEE directly without intermediate parties. In that setup the flow is:

  • Client establishes TLS session with the remote prover running inside the TEE directly. The client can then start sending encrypted requests directly to the TEE.
  • The client can optionally send an empty request to receive an attestation report from the remote prover. This proves to the client that they are talking to a legitimate TEE before they send any data.
  • The client then can start sending proof delegation requests with their data which would be encrypted via TLS and decrypted only inside the TEE.
  • The TEE would then process the request and send back the attestation proof
  • The client verifies the attestation proof for the request.

In the scenario you described where there is a remote prover proxy running independently that directs requests to multiple TEEs, there are options there to ensure that the user is always talking to a TEE:

  • Option 1: Our recommended solution is to also run the remote prover inside a TEE as a proxy. This is the cleanest solution because it allows TLS to terminate inside the proxy without revealing anything at all about the request to an external party. The user would receive a composite attestation that proves that the remote prover proxy and the end server that processed the proof request both ran in TEEs.
  • Option 2: if you want to use an untrusted proxy (not running inside a TEE) with end-to-end encryption where:
    • The proxy connects to an available TEE and obtains it’s attestation report.
    • The client receives and verifies the attestation report to ensure that it’s a legitimate TEE that the prover is connecting it to.
    • The client extracts the TEE’s public key from the verified attestation report
    • The client encrypts their data using the extracted public key rom the target TEE
    • The untrusted proxy can route the encrypted requests to the appropriate TEE but cannot decrypt or inspect the actual user data since it lacks access to the TEE's private keys
    • This ensures that the encrypted data can only be decrypted inside the specific TEE that holds the corresponding private key, maintaining the security boundary even when routing through an untrusted proxy
    • Note that with this option, the untrusted proxy would terminate TLS still. So even though the client payload (proof data) would be encrypted, other information from the HTTP headers would be visible to that proxy so one can argue that this option provides less privacy for the end client.

We support both options if required. For now, if the delegated prover is running as a single instance, the current setup suffices for that and we can switch to another setup if required later on.

Hope that answers your question!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants