Build your first TDX iApp
In this tutorial, you will learn how to build and run a Confidential Computing app with Intel TDX (Trust Domain Extensions) on the iExec protocol.
Prerequisites
- Docker 17.05 or higher on the daemon and client.
- iExec SDK 8.24.0 or higher.
- Familiarity with the basics of Intel TDX and the iExec workflow described in Deploy and run an iApp.
Unlike the legacy SGX/SCONE flow, TDX does not require a separate sconification step: you build a standard linux/amd64 OCI image, push it to a registry, and configure the iExec app for the TDX framework (see Intel TDX Technology for what TDX provides).
Prepare your app
For this tutorial, create a new directory tree. Execute the following commands in ~/iexec-projects/:
cd ~/iexec-projects
mkdir tee-hello-world-tdx && cd tee-hello-world-tdx
iexec init --skip-wallet
mkdir src
touch DockerfileWrite the iApp logic
Develop your code logic as below. The following examples use JavaScript and Python for brevity; any workload that fits in a Docker image can be used on iExec.
Copy the following content in src/.
const fsPromises = require('fs').promises;
(async () => {
try {
const iexecOut = process.env.IEXEC_OUT;
// Do whatever you want (let's write hello world here)
const message = process.argv.length > 2 ? process.argv[2] : 'World';
const text = `Hello, ${message}!`;
console.log(text);
// Append some results in /iexec_out/
await fsPromises.writeFile(`${iexecOut}/result.txt`, text);
// Declare everything is computed
const computedJsonObj = {
'deterministic-output-path': `${iexecOut}/result.txt`,
};
await fsPromises.writeFile(
`${iexecOut}/computed.json`,
JSON.stringify(computedJsonObj)
);
} catch (e) {
console.log(e);
process.exit(1);
}
})();import os
import sys
import json
iexec_out = os.environ['IEXEC_OUT']
# Do whatever you want (let's write hello world here)
text = 'Hello, {}!'.format(sys.argv[1] if len(sys.argv) > 1 else "World")
print(text)
# Append some results in /iexec_out/
with open(iexec_out + '/result.txt', 'w+') as fout:
fout.write(text)
# Declare everything is computed
with open(iexec_out + '/computed.json', 'w+') as f:
json.dump({ "deterministic-output-path" : iexec_out + '/result.txt' }, f)WARNING
As a developer, make it a rule to never log sensitive information in your application. Execution logs are accessible by:
- worker(s) involved in the task
- the workerpool manager
- the requester of the task
Dockerize your iApp
Copy the following content in Dockerfile.
FROM node:22-alpine3.21
### install your dependencies if you have some
RUN mkdir /app && cd /app
COPY ./src /app
ENTRYPOINT [ "node", "/app/app.js"]FROM python:3.13.3-alpine3.21
### install python dependencies if you have some
COPY ./src /app
ENTRYPOINT ["python3", "/app/app.py"]Build the docker image.
WARNING
iExec expects your Docker container to be built for the linux/amd64 platform. On a Mac with an Apple Silicon chip, the default platform is linux/arm64. Use buildx to produce the image for linux/amd64.
brew install buildkit
# ARM64 variant for local testing only
docker buildx build --platform linux/arm64 --tag <docker-hub-user>/hello-world .
# AMD64 variant to deploy on iExec
docker buildx build --platform linux/amd64 --tag <docker-hub-user>/hello-world:1.0.0 --load .docker build --tag <docker-hub-user>/hello-world:1.0.0 .TIP
docker build produces an image id; using --tag <name>:<version> is a convenient way to name the image for the next steps.
Test your iApp locally
Basic test
Create local volumes to simulate input and output directories.
mkdir -p ./tmp/iexec_in
mkdir -p ./tmp/iexec_outRun your application locally (container volumes bound with local volumes).
docker run --rm \
-v ./tmp/iexec_in:/iexec_in \
-v ./tmp/iexec_out:/iexec_out \
-e IEXEC_IN=/iexec_in \
-e IEXEC_OUT=/iexec_out \
<docker-hub-user>/hello-world:1.0.0 arg1 arg2 arg3Docker run [options] image [args]
docker run usage:
docker run [OPTIONS] IMAGE [COMMAND] [ARGS...]
Use [COMMAND] and [ARGS...] to simulate the requester arguments.
Useful options for iExec:
-v : Bind mount a volume. Use it to bind input and output directories (/iexec_in and /iexec_out)
-e: Set environment variable. Use it to simulate iExec runtime variables
Test with input files
Starting with the basic test, you can simulate input files.
For each input file:
- Copy it in the local volume bound to
/iexec_in. - Add
-e IEXEC_INPUT_FILE_NAME_x=NAMEto docker run options (xis the index of the file starting at 1 andNAMEis the name of the file)
Add -e IEXEC_INPUT_FILES_NUMBER=n to docker run options (n is the total number of input files).
Example with two input files:
touch ./tmp/iexec_in/file1 && \
touch ./tmp/iexec_in/file2 && \
docker run \
-v ./tmp/iexec_in:/iexec_in \
-v ./tmp/iexec_out:/iexec_out \
-e IEXEC_IN=/iexec_in \
-e IEXEC_OUT=/iexec_out \
-e IEXEC_INPUT_FILE_NAME_1=file1 \
-e IEXEC_INPUT_FILE_NAME_2=file2 \
-e IEXEC_INPUT_FILES_NUMBER=2 \
<docker-hub-user>/hello-world:1.0.0 \
arg1 arg2 arg3Build and push your Docker image for TDX
For TDX, you use the same image you built and tested: there is no enclave packaging step. Ensure the image is built for linux/amd64, then push it to Docker Hub (or another registry you reference in iexec.json).
docker login
docker push <docker-hub-user>/hello-world:1.0.0You are now ready to register and run this image as a TDX iApp on iExec.
Test your iApp on iExec
At this stage, your app is ready to be tested on iExec. The process is similar to testing a non-TEE app, with TDX-specific settings below.
Update chain.json
Point the iExec client to the TDX Secret Management Service (SMS) for your target network. Edit chain.json as follows (or create it if missing):
{
"default": "arbitrum-sepolia-testnet",
"chains": {
"arbitrum-sepolia-testnet": {
"sms": { "tdx": "https://sms.labs.iex.ec" }
}
}
}{
"default": "arbitrum-mainnet",
"chains": {
"arbitrum-mainnet": {
"sms": { "tdx": "https://sms.arbitrum-mainnet.iex.ec" }
}
}
}Deploy the TEE iApp on iExec
TEE apps require additional fields during deployment. Prepare the TEE app template and select the TDX framework:
iexec app init --tee-framework tdxEdit iexec.json and fill in the standard keys and the TDX mrenclave object:
{
...
"app": {
"owner": "<your-wallet-address>", // starts with 0x
"name": "tee-tdx-hello-world", // app name
"type": "DOCKER",
"multiaddr": "docker.io/<docker-hub-user>/hello-world:1.0.0", // app image
"checksum": "<checksum>", // starts with 0x, update with your image digest
},
...
}INFO
See Deploy your iApp on iExec to obtain your image <checksum> (digest).
Deploy the iApp:
iexec app deploy --chain arbitrum-mainnetList your last deployed app:
iexec app show --chain arbitrum-mainnetRun the iApp
iExec runs applications on decentralized infrastructure; execution is paid in RLC on Arbitrum networks.
INFO
To run an application you must have enough RLC staked on your iExec account to pay for the computing resources.
When you request an execution, the task cost is reserved from your account’s stake, then distributed to workers (see Proof of Contribution).
At any time you can:
- view your balance
iexec account show --chain arbitrum-mainnet- deposit RLC from your wallet to your iExec account
iexec account deposit --chain arbitrum-mainnet <amount>- withdraw RLC from your iExec account to your wallet (only stake can be withdrawn)
iexec account withdraw --chain arbitrum-mainnet <amount>To run a TDX iApp, use the TEE tee and tdx tags and a TDX workerpool for the target network.
iexec app run --chain arbitrum-mainnet --tag tee,tdx --workerpool 0x8ef2ec3ef9535d4b4349bfec7d8b31a580e60244 --watchiexec app run --chain arbitrum-sepolia-testnet --tag tee,tdx --workerpool 0x2956f0cb779904795a5f30d3b3ea88b714c3123f --watchiexec app run --chain arbitrum-mainnet --tag tee,tdx --workerpool 0x8ef2ec3ef9535d4b4349bfec7d8b31a580e60244 --watchTask execution on iExec is asynchronous.
Guarantees about completion times (fast/slow) are described in the category section: maximum deal/task time, maximum computing time, etc.
When the task completes, copy the taskid from the iexec app run output (a 32-byte hex string).
Download the result:
iexec task show --chain arbitrum-mainnet <taskid> --download my-resultYou can get the taskid for a dealid with:
iexec deal show --chain arbitrum-mainnet <dealid>INFO
A task result is a zip file containing the application output files.
For this hello-world app, the output includes result.txt. Unpack and read it:
unzip my-result.zip -d my-result
cat my-result/result.txtCongratulations! You have executed your application on iExec in a TDX Trust Domain.
Publish your app on the iExec Marketplace
Your app is deployed and you have completed an execution. To let others run it, publish an apporder (see iApp access and pricing for how orders work).
iexec app publish --chain arbitrum-mainnetINFO
iexec app publish allows custom access rules (iexec app publish --help).
Check published app orders:
iexec orderbook app --chain arbitrum-mainnet <your app address>Next steps
In this tutorial you used Intel TDX on iExec to run a confidential workload. To go further with confidential data and result protection:
Deeper TEE context:
Using iApp Generator
The iApp Generator can deploy and run TDX apps with less manual iexec.json editing.
Enabling TDX in iApp Generator
Enable TDX for deployment and execution:
iapp deploy
iapp run <app-address>Per command:
iapp deploy
iapp run <app-address>
iapp debug <taskId>Verify TEE tags on the app:
iexec app show <app-address>DataProtector SDK with TDX
To use DataProtector with TDX, point the SDK at the TDX SMS (same hosts as in Update chain.json above).
const dataProtector = new IExecDataProtector(web3Provider, {
iexecOptions: {
smsURL: 'https://sms.labs.iex.ec',
},
});const dataProtector = new IExecDataProtector(web3Provider, {
iexecOptions: {
smsURL: 'https://sms.arbitrum-mainnet.iex.ec',
},
});Pass the TDX workerpool in processProtectedData:
await dataProtector.core.processProtectedData({
protectedData: protectedData.address,
workerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f',
app: '0x456def...',
});await dataProtector.core.processProtectedData({
protectedData: protectedData.address,
workerpool: '0x8ef2ec3ef9535d4b4349bfec7d8b31a580e60244',
app: '0x456def...',
});TDX iApps may require TDX-compatible protected data. Check the latest DataProtector documentation for requirements.
Local test (same as non-TEE):
iapp test --protectedData "mock_name"