This repository contains Solidity contracts for verifying attestations from Trusted Execution Environments (TEEs), including AWS Nitro Enclaves and Intel SGX. It is used in our integration projects including optimism-espresso-integration and nitro-espresso-integration.
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Foundry consists of:
- Forge: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- Cast: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- Anvil: Local Ethereum node, akin to Ganache, Hardhat Network.
- Chisel: Fast, utilitarian, and verbose solidity REPL.
Download foundry at https://book.getfoundry.sh/
forge buildforge testforge fmtforge snapshotanvilcast <subcommand>forge --help
anvil --help
cast --helpFor ease of development in go projects, we have included a small utility in this repo to generate go bindings for the TEEVerifier contracts present here.
This utility uses the bind v2 implementation from github.com/ethereum/go-ethereum/abi/abigen/bind.go
To generate these bindings for use in a go project, simply run go run bindings/gen.go from the root of this repo.
Afterwards the bindings should appear in bindings/go/espressogen/espressogen.go and can be linked into your project easily if you are using this repo as a git submodule.
All TEE contracts are deployed using the OpenZeppelin v5.x Transparent Proxy pattern. In this pattern, each TransparentUpgradeableProxy automatically deploys its own ProxyAdmin contract internally. The ProxyAdmin owner controls upgrade capabilities.
Start with a fresh build to ensure contracts are built with proper profiles for gas optimizations:
forge cleanCreate a .env file in the project root with the following variables.
The ETHERSCAN_API_KEY should be generated from your account on etherscan.io and works across all supported chains via the V2 API.
# Variables for script command
RPC_URL=<your-rpc-url>
PRIVATE_KEY=<your-private-key>
CHAIN_ID=<your-chain-id>
# Etherscan V2 API Key from etherscan.io (works for all chains)
ETHERSCAN_API_KEY=<your-etherscan-v2-api-key>
# Variables for deployment
SGX_QUOTE_VERIFIER_ADDRESS=<quote_verifier_address_from_automata> # From: https://github.com/automata-network/automata-dcap-attestation/tree/main/rust-crates/libraries/network-registry/deployment/current
NITRO_ENCLAVE_VERIFIER=<nitro_enclave_verifier_address> # From: https://github.com/automata-network/aws-nitro-enclave-attestation
# To be updated after individual deployment (not needed for DeployAllTEEVerifiers)
TEE_VERIFIER_ADDRESS=""
NITRO_VERIFIER_ADDRESS=""
SGX_VERIFIER_ADDRESS=""
Save the file then source it:
source .envYou can deploy the TEE contracts in two ways:
This deploys all three TEE verifier contracts in a single script, handling the circular dependency automatically:
forge script scripts/DeployAllTEEVerifiers.s.sol:DeployAllTEEVerifiers \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY" \
--broadcast \
--verify --verifier etherscan --chain "$CHAIN_ID"This script will:
- Deploy
EspressoTEEVerifierproxy (with placeholder SGX/Nitro addresses) - Deploy
EspressoSGXTEEVerifierproxy (linked to TEEVerifier) - Deploy
EspressoNitroTEEVerifierproxy (linked to TEEVerifier) - Update
EspressoTEEVerifierwith the actual SGX and Nitro addresses
If you prefer to deploy contracts separately:
-
Deploy Espresso TEE Verifier First
First, deploy the main TEEVerifier with placeholder addresses:
# Set placeholder addresses for individual deployment export SGX_VERIFIER_ADDRESS=0x0000000000000000000000000000000000000000 export NITRO_VERIFIER_ADDRESS=0x0000000000000000000000000000000000000000 forge script scripts/DeployTEEVerifier.s.sol:DeployTEEVerifier \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --broadcast \ --verify --verifier etherscan --chain "$CHAIN_ID"
Update your
.envwith the deployed TEEVerifier proxy address:TEE_VERIFIER_ADDRESS=<deployed_tee_verifier_proxy> -
Deploy SGX Verifier
source .env FOUNDRY_PROFILE=sgx forge script scripts/DeploySGXTEEVerifier.s.sol:DeploySGXTEEVerifier \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --broadcast \ --verify --verifier etherscan --chain "$CHAIN_ID"
-
Deploy Nitro Verifier
FOUNDRY_PROFILE=nitro forge script scripts/DeployNitroTEEVerifier.s.sol:DeployNitroTEEVerifier \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --broadcast \ --verify --verifier etherscan --chain "$CHAIN_ID" -
Update TEEVerifier with actual addresses
After deploying SGX and Nitro verifiers, call
setEspressoSGXTEEVerifierandsetEspressoNitroTEEVerifieron the TEEVerifier proxy using cast:cast send $TEE_VERIFIER_ADDRESS "setEspressoSGXTEEVerifier(address)" $SGX_VERIFIER_ADDRESS \ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" cast send $TEE_VERIFIER_ADDRESS "setEspressoNitroTEEVerifier(address)" $NITRO_VERIFIER_ADDRESS \ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
- Verify all contracts on Block Explorer
- Deployment artifacts are saved in
deployments/<chain_id>/ - Each deployment JSON contains:
proxy: The proxy address (this is what users interact with)implementation: The implementation contract address
The script located at scripts/MultiSigTransfer.s.sol is meant to assist with this.
When run the script will first initiate transfer of the TEEVerifier contracts to the multi-sig wallet with the original owner wallet.
Then, the script will batch the acceptOwnership() transactions that need to be executed on the multi-sig wallet. It will require the user to sign
the proposal to the multi-sig wallet with a ledger containing an account that is a designated signer or delegate.
If the ledger signature fails, or is invalid, the entire script will revert, meaning that the initial ownership transfer will not be reflected on chain.
Currently this script is NOT compatible with ARB Sepolia, only ETH, ETH Sepolia, and ARB One. We hope to extend compatibility to ARB Sepolia in the future.
- Prepare your .env file.
Your .env file will need to contain the following items.
LEDGER_DERIVATION_PATH="m/44'/60'/0'/0/0" #Note, depending on what keys your ledger holds, this may be different.
MULTISIG_CONTRACT_ADDRESS="Your Multi-Sig address"
TEE_VERIFIER_ADDRESS="EspressoTEEVerifier contract address" #
PROPOSER_ADDRESS="Your Multi-sig signer address" #Note, this must be the same as the one provided by the derivation path.
ORIGINAL_OWNER_KEY="insert original EspressoTEEVerifier contract owner's private key here"
RPC_URL="RPC URL for the chain you wish to execute on"
CHAIN_ID="Chain ID of the network to execute transactions on"-
LEDGER_DERIVATION_PATH: This is the derivation path of the account you wish to propose the multi-sig transaction with. It is mandatory that this account is a
signer, ordelegate, in the multi-sig contract.You can obtain this by first opening Ledger Wallet and going to the accounts section. Then you should right click on the account you wish to use for signing, and select edit account. Finally, click on the
Advanceddropdown menu. For ethereum addresses you can expect the path to be listed in the freshAddressPath like so.
{
"index": 0,
"freshAddressPath": "44'/60'/0'/0/0",
"id": "js:2:ethereum:0x0000000000000000000000000000000000000000:",
"blockHeight": 00000001
}NOTE: you will need to prepend the m/ to this path for foundry to correctly locate the account.
-
MULTISIG_CONTRACT_ADDRESS: The address of your Multi-sig wallet on the chain you wish this transaction to occurr on.
-
TEE_VERIFIER_ADDRESS: The address of the outer EspressoTEEVerifier contract. Note: this must be the outer TEEVerifier contract. The script locates the inner contracts by calling espressoNitroVerifier and espressoSGXVerifier on the outer contract.
-
PROPOSER_ADDRESS: The address used to propose the transaction to the multi-sig wallet. Note: This must be the address associated with the account located at the wallets provided derivation path.
-
ORIGINAL_OWNER_KEY: The private key of the account that owns the TEEVerifier contracts before the transfer of ownership.
-
RPC_URL: Used for the
forge scriptcommand for specifying the network to send the transaction to. -
CHAIN_ID: This is used for the
forge scriptcommand for specifying the network to send the transaction to.
- Run the MultiSigTransfer script.
Note: it is important to pass the --ffi flag, otherwise due to some restrictions in forge, the script will not be able to use the ledger for signing the proposal.
Before running the script, make sure your ledger is plugged into your machine, unlocked, and has the Ethereum app open.
forge script scripts/MultiSigTransfer.s.sol:MultiSigTransfer --rpc-url "$RPC_URL" --sender $PROPOSER_ADDRESS --broadcast --verify --verifier etherscan --chain "$CHAIN_ID" --ffiYou may run into errors like the following:
Error: Could not connect to Ledger device.
Make sure it's connected and unlocked, with no other desktop wallet apps open.
Context:
- Sequence mismatch. Got 1 from device. Expected 0or
Error: Ledger device: APDU Response error `Code 6985 ([APDU_CODE_CONDITIONS_NOT_SATISFIED] Conditions of use not satisfied)`Or even
Error: Could not connect to Ledger device.
Make sure it's connected and unlocked, with no other desktop wallet apps open.
Context:
- Ledger device: APDU Response error `Code 6983 ([APDU_CODE_OUTPUT_BUFFER_TOO_SMALL])`
If you encounter any of these, it's best to try quitting the Ethereum app and lock your ledger. Then re-unlock the ledger, open the Ethereum app, and try the script again.
I'm not entirely sure why these happen, but it seems to be related to when the ledger was unlocked and opened the Ethereum app. The first error related to a sequence mismatch also just goes away sometimes.
After running this script you will be asked to create one signature with your ledger. Given this is a batch transaction the signature will have a large number of parameters to verify. This is expected.
After you complete the signature with your ledger, forge will broadcast the appropriate transactions on chain starting the ownership transfer.
To finish this ownership transfer, you should go to the web UI for your Safe wallet, and note that a new transaction should be present with a signature from your ledger device!
The final step is to gather the other signatures required to reach your wallets threshold, and execute the transaction with the web UI.
Required separate deployments per chain:
EspressoTEEVerifier- Your main TEE verifier contractEspressoNitroTEEVerifier- Nitro-specific verifierEspressoSGXTEEVerifier- SGX-specific verifier- Automata
NitroEnclaveVerifier-⚠️ MUST be chain-specific! - Automata
V3QuoteVerifier(for SGX) -⚠️ MUST be chain-specific!
Why separate deployments are CRITICAL:
-
Independent State Management
- Each contract maintains on-chain state (approved enclave hashes, registered signers)
- State is NOT synchronized across chains
- Revoking a hash on one chain does NOT affect other chains
-
ZK Configuration Control
- Each Automata contract has its own ZK verifier configuration
- You validate against specific verifier IDs per chain
- Shared Automata contracts would create single point of failure across all chains
-
Security Isolation
- Different chains may have different threat models
- Security policies can be chain-specific
- Compromise on one chain should NOT affect others
- Prevents cross-chain authorization bypass
-
Governance Independence
- Each chain can have different owners/multisigs
- Approval workflows can differ per chain
- No single entity controls all chains
- Distributed trust model
Do NOT use the same Automata contract across multiple chains! Each chain needs:
For Nitro TEE:
✅ Chain A: NitroEnclaveVerifier at 0xAAA...
✅ Chain B: NitroEnclaveVerifier at 0xBBB... (different!)
❌ DO NOT: Use same NitroEnclaveVerifier on both chains
For SGX TEE:
✅ Chain A: V3QuoteVerifier at 0xCCC...
✅ Chain B: V3QuoteVerifier at 0xDDD... (different!)
❌ DO NOT: Use same V3QuoteVerifier on both chainsWhy this matters:
- Each Automata contract has mutable ZK configuration
- Your security validation caches the expected config per deployment
- Shared Automata = single point of configuration control across chains
- Separate Automata = isolated security boundaries
Verify Automata deployments:
Check Automata's documentation for chain-specific addresses:
- Nitro: https://github.com/automata-network/aws-nitro-enclave-attestation
- SGX: https://github.com/automata-network/automata-dcap-attestation
Example: Current Mainnet Deployments
EspressoTEEVerifier Contracts (Our Deployments):
ApeChain (33139): 0x4fd6D0995B3016726D5674992c1Ec1bDe0989cF5
AppChain (466): 0xcC758349CBd99bAA7fAD0558634dAaB176c777D0
Huddle01: 0x2E01FA49cB3C3Ff09a5908165A5b5cB7f5cDF271
NodeOps: 0xE0032d5a83f082aC05E66C31dcAbd84bc461b767
Rufus: 0xFcb6371757DE81DeaDbE8c13e36bFD7A261dD263
T3rn: 0xf252DDe41C679B2959d7C3a2Ea0bC2fA9dE7Eab7
✅ Each chain has unique address (correct!)
✅ Each references chain-specific Automata contractsBefore deploying to production, verify:
-
External dependencies verified:
- Automata
NitroEnclaveVerifieraddress confirmed for target chain -
⚠️ Verify NitroEnclaveVerifier is DIFFERENT for each chain (do not reuse!) - Automata
V3QuoteVerifieraddress confirmed for target chain (SGX) -
⚠️ Verify V3QuoteVerifier is DIFFERENT for each chain (do not reuse!) - Verify external contracts on block explorer
- Check Automata contract owner and governance model
- Automata
-
Initial configuration prepared:
- SGX mrEnclave hashes computed and documented
- Initial approved hashes ready
-
Governance setup:
- Owner address configured (recommend multisig)
- Ownership transfer process documented
- Emergency response procedures established
Immediately after deployment:
-
Verify contracts on block explorer
forge verify-contract <address> EspressoNitroTEEVerifier --chain <chain-id>
-
Register initial enclave hashes
cast send <verifier-address> \ "setEnclaveHash(bytes32,bool,uint8)" \ <pcr0-hash> true 0 \ --private-key $PRIVATE_KEY
-
Transfer ownership to multisig (recommended)
forge script scripts/MultiSigTransfer.s.sol --broadcast
-
Test with actual TEE attestation
- Generate test attestation from your TEE
- Call registerService() with real data
- Verify signer is registered correctly
-
Set up monitoring
- Monitor Automata contract configuration changes
- Alert on unexpected registration patterns
- Track signer counts per enclave hash