Using Nautilus
Nautilus is a verifiable offchain compute layer on Sui. It enables builders to delegate sensitive or resource-intensive tasks to a self-managed trusted execution environment (TEE) or a TEE marketplace like Marlin Oyster while preserving trust onchain through smart contract-based verification.
The Nautilus repo provides scaffolding such as reproducible builds, signature formatting, and HTTPS traffic forwarding. This allows you to focus on implementing the offchain computation logic inside the enclave.
An onchain template includes the minimal smart contract code required to register a Nautilus instance and its public key.
Nautilus is not just about running code in privacy-preserving manner in a TEE. Part of the overall value proposition is onchain verification of computation integrity:
- You must register and verify PCRs (enclave measurements) onchain.
- You can optionally verify every computation result onchain.
Developer workflow
To use Nautilus in your app:
-
Implement the enclave in Rust with the desired computation logic.
-
Deploy a Move smart contract that stores the expected platform configuration registers (PCRs) and allows updates by the contract deployer.
-
Deploy the enclave instance on AWS and register it onchain using its attestation document.
-
Upload signed responses from the registered enclave, verify them onchain, and consume the results in your smart contract.
To build the weather example, you complete the following steps:
-
Write and deploy a basic Nautilus offchain instance using AWS Nitro Enclaves. The example instance runs a server that fetches weather data for a specific location.
-
Write a Move smart contract that registers the enclave by verifying its attestation and public key, then verifies the Nautilus response (signature and payload) onchain and mints an NFT with the location and temperature data.
The weather example walkthrough does not include frontend code.
- Prerequisites
-
Install AWS CLI v2.
-
Install Make.
-
Download or git clone the Nautilus repo.
Step 1: Create an AWS developer account
Set up an AWS developer account and install the AWS CLI. For detailed instructions, see the AWS Nitro Enclaves getting started guide.
Step 2: Configure AWS SSO
Confirm AWS CLI version:
aws --version
This should print a version that begins with 2:
aws-cli/2.x.x
Configure single sign-on (SSO) for the AWS CLI tool using the command:
aws configure sso
When prompted, fill in the following fields:
-
SSO start URL:
<your-sso-start-url> -
SSO region:
us-east-1
If you see expired credentials, reset them with:
rm ~/.aws/credentials
rm ~/.aws/config
Step 3: Log in to AWS SSO
After you configure AWS SSO, AWS redirects you to log in through a browser window. Choose an account and the role AdministratorAccess. AWS should create a profile such as AdministratorAccess-094557288217.
The number 094557288217 is an example AWS account ID. Replace it with your own account ID wherever it appears in the commands on this page.
This walkthrough uses AdministratorAccess only for simplicity. In production, do not grant broad administrator permissions: scope an IAM role to the least privilege the enclave deployment actually needs (EC2, Nitro Enclaves, and KMS as applicable). Never log AWS credentials, enclave secrets, or attestation private material, and always verify the enclave's PCRs against the values registered onchain before trusting any output it produces. See the Nautilus section of the Security Best Practices guide.
Step 4: List existing EC2 SSH key pairs
Run the following command to list all key pairs created in your AWS account. You use these only for SSH into EC2.
aws ec2 describe-key-pairs \
--region us-east-1 \
--profile AdministratorAccess-094557288217
Step 4.1: Create a new EC2 SSH key pair (optional)
If you don't already have one you want to use, create a new EC2 SSH key pair with the command:
aws ec2 create-key-pair \
--key-name <your-alias> \
--query 'KeyMaterial' \
--output text \
--region us-east-1 > ~/.ssh/<your-alias>.pem
Configure the SSH key's permissions:
chmod 400 ~/.ssh/<your-alias>.pem
Step 5: Set environment variables
Set the KEY_PAIR variable with the name of your EC2 key pair:
export KEY_PAIR=<your-key-pair-name>
Then, you must set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables. The Nautilus provisioning script requires these.
Export the values for these variables using the command:
aws configure export-credentials \
--profile AdministratorAccess-094557288217 \
--format env > ~/aws-temp-creds.sh
Then, load these credentials:
source ~/aws-temp-creds.sh
Confirm that you set them properly:
env | grep AWS_
You can also verify your AWS identity with the command:
aws sts get-caller-identity
Expected output:
{
"UserId": "AROARMBANW4MXIS7SZ7S6:george.melas@mystenlabs.com",
"Account": "094557288217",
"Arn": "arn:aws:sts::094557288217:assumed-role/AWSReservedSSO_AdministratorAccess_bb69586677f0373c/george.melas@mystenlabs.com"
}
If this works, then your AWS environment is ready to use with Nautilus.
If these temporary credentials expire later in the walkthrough, refresh them using the commands under SSO expired in Troubleshooting.
Step 6: View the Nautilus repository structure
View the contents of the Nautilus repository that you have downloaded or cloned.
/move
/enclave # Utility functions for enclave config and public key registration
/weather-example # Example onchain logic using enclave functions
/twitter-example # Alternative example onchain logic
/src
/aws # AWS boilerplate; does not require modification.
/init # AWS boilerplate; does not require modification.
/system # AWS boilerplate; does not require modification.
/nautilus-server # Nautilus server that runs inside the enclave.
/src
/apps
/weather-example # Example directory. Replace with your own application logic as needed.
mod.rs # Defines the process_data endpoint and related logic. Replace with your offchain computation logic.
allowed_endpoints.yaml # Lists all endpoints the enclave can access. By default, the enclave has no internet access. During configuration, this file generates code for traffic forwarding.
/twitter-example # Another example directory with a similar structure.
run.sh # Runs the Rust server inside the enclave. Do not modify.
common.rs # Common code for retrieving attestation. Do not modify.
Key implementation files:
-
allowed_endpoints.yaml: Defines external API access permissions. -
mod.rs: Contains application-specific computation logic. -
run.sh: Handles server startup and configuration. -
common.rs: Manages retrieving attestation.
To create your own Nautilus app:
-
Add a directory under
move/my_appfor your Move modules. -
Add a directory under
src/nautilus-server/src/apps/my_appfor your Rust server logic. -
Use existing app directories as references.
-
Build frontend logic to interact with the deployed Move contract and enclave-hosted Rust server.
Most of the template can remain unmodified, which streamlines development while giving you full control over app-specific logic.
Step 7: Run the Nautilus provisioning script
Run from the root of the Nautilus directory:
cd /nautilus
sh configure_enclave.sh weather-example
The setup script:
-
Launches a preconfigured EC2 instance and allocates a Nitro Enclave.
-
Builds the Rust-based template application into an enclave image format (EIF) binary and runs it inside the enclave.
-
Configures required HTTP domains so the enclave can access external APIs through the parent EC2 instance (as the enclave itself has no internet access).
-
Exposes 3 endpoints to allow client-side communication with the enclave.
When the enclave starts, it generates a fresh key pair and exposes the following endpoints:
-
health_check: Probes allowed domains inside the enclave. The template includes this logic, and it does not require modification. -
get_attestation: Returns a signed attestation document over the enclave public key. Use this during onchain registration. The template includes this logic, and it does not require modification. -
process_data: Fetches weather data from an external API, signs it with the enclave key, and returns the result. You must implement this customizable logic.
Run sh configure_enclave.sh -h to view additional instructions. If your AWS account is not in us-east-1, you might need to configure REGION and AMI_ID values specific to your region. Refer to this guide to find a suitable Amazon Linux image ID.
$ export REGION=<your-region>
$ export AMI_ID=<find-an-amazon-linux-ami-for-your-region>
You might need to create a VPC with a public subnet. Refer to this AWS guide for instructions.
Step 8: Run the example
To run the weather example as is, you do not need to modify allowed_endpoints.yaml because it already includes api.weatherapi.com. Follow the prompts to enter the required values. This step demonstrates how to store a secret (an API key) using AWS Secrets Manager, so you can avoid including the secret in the public application code.
Enter EC2 instance base name: weather # anything you like
Do you want to use a secret? (y/n): y
Do you want to create a new secret or use an existing secret ARN? (new/existing): new
Enter secret name: weather-api-key # anything you like
Enter secret value: 045a27812dbe456392913223221306 # this is an example api key, you can get your own at weatherapi.com
The output includes:
-
Instance ID
-
Public IP
-
Security group
-
IAM role created
Save the public IP, such as:
Instance launched with ID: i-0728fc72a13834bc8
Public IP: 18.208.226.122
Wait 2 to 3 minutes for EC2 initialization.
Step 9: Copy the Nautilus repo to EC2
When the script completes successfully, it generates changes in /src/nautilus-server/run.sh and expose_enclave.sh. You must copy these changes to EC2, because the enclave image build requires them.
The provisioning script launches the EC2 instance and generates these files on your local machine. After you copy the repo, you build the enclave image and run it on the EC2 instance with make, as shown in the next step.
rsync -avz -e "ssh -i ~/.ssh/<your-alias>.pem" \
/Users/<you>/suistack/nautilus/ \
ec2-user@<public-ip>:~/nautilus/
# example: rsync -avz -e "ssh -i ~/.ssh/melas-se-3.pem" \
# /Users/geomel/suistack/nautilus/ \
# ec2-user@18.208.226.122:~/nautilus/
To allow the enclave to access additional external domains, add them to allowed_endpoints.yaml. If you update this file, you must rerun configure_enclave.sh to generate a new instance, because the build compiles the endpoint list into the enclave.
You can optionally create a secret to store any sensitive value you do not want included in the codebase. The enclave receives the secret as an environment variable. You can verify newly created secrets or find existing ARNs in the AWS Secrets Manager console.
Step 10: SSH into EC2
ssh -i ~/.ssh/<your-alias>.pem ec2-user@<public-ip>
# example: ssh -i ~/.ssh/melas-se-3.pem ec2-user@18.208.226.122
You are now inside the directory containing the server code, including the committed file changes from the previous step. Next, build the enclave image, run it, and expose the HTTP endpoint on port 3000.
$ cd nautilus/
# Build the enclave and run it, for example with ENCLAVE_APP=weather-example
$ make ENCLAVE_APP=<APP> && make run
# Expose port 3000 to the Internet for traffic
$ sh expose_enclave.sh
Replace <APP> with the name of an app directory under src/nautilus-server/src/apps, such as weather-example or twitter-example.
Use make run-debug instead of make run to run the enclave in debug mode. This prints all logs, where the production build does not. In debug mode, the PCR values are all zeros and are not valid for production use.
The build writes the enclave's PCR values to out/nitro.pcrs. Print them and record them as environment variables, because you need them to register the enclave onchain:
$ cat out/nitro.pcrs
$ PCR0=<PCR0-value-from-nitro.pcrs>
$ PCR1=<PCR1-value-from-nitro.pcrs>
$ PCR2=<PCR2-value-from-nitro.pcrs>
Set these variables in the shell where you run the commands in the Register the enclave onchain step. For an example of the file's contents, see Artifacts for Twitter example.
You can now interact with the enclave from the outside world. You can find the PUBLIC_IP in the AWS console. To confirm the enclave is running, run the health check below; a successful response lists each allowed domain with a true status, as shown under Troubleshooting.
# Run a health check:
$ curl -H 'Content-Type: application/json' -X GET http://<PUBLIC_IP>:3000/health_check
# View the attestation document:
$ curl -H 'Content-Type: application/json' -X GET http://<PUBLIC_IP>:3000/get_attestation
# Get weather data for a location:
$ curl -H 'Content-Type: application/json' -d '{"payload": { "location": "San Francisco"}}' -X POST http://<PUBLIC_IP>:3000/process_data
Optionally, you can set up an application load balancer (ALB) for the EC2 instance with an SSL/TLS certificate from AWS Certificate Manager (ACM), and configure Amazon Route 53 for DNS routing. For more information, see the ACM User Guide and the ALB Guide.
Step 11: Register the enclave onchain
After finalizing the Rust code, the app administrator can register the enclave with the corresponding PCRs and public key.
The onchain half of the weather example is in the move/weather-example directory of the Nautilus repo, and the enclave utility functions are in move/enclave.
# Optionally
$ sui client switch --env testnet # or appropriate network
$ sui client faucet
$ sui client gas
# deploy the enclave package
$ cd move/enclave
$ sui move build
$ sui client publish
# Record ENCLAVE_PACKAGE_ID as env var from publish output
$ ENCLAVE_PACKAGE_ID=0x3b009f952e11f0fa0612d0a8e07461fb69edc355d732e5d6e39267b1b4fd7138
# Deploy your app logic
$ cd ../<APP>
$ sui move build
$ sui client publish
# Record CAP_OBJECT_ID (owned object of type Cap), ENCLAVE_CONFIG_OBJECT_ID (shared object), APP_PACKAGE_ID (package containing weather module) as env var from publish output
$ CAP_OBJECT_ID=0xb232d20245ba2d624d1c1628c4fc062bd1d3249601385476d9736fc60c897d2b
$ ENCLAVE_CONFIG_OBJECT_ID=0x9a50017ab37090ef4b5704eb24201c88b2e4bbad2aad1d4e69ecf1bdfbae9ccb
$ APP_PACKAGE_ID=0x097b551dec72f0c47e32e5f8114d0d12a98ab31762d21adff295f6d95d353154
# Record the deployed enclave url, for example http://<PUBLIC_IP>:3000
$ ENCLAVE_URL=<DEPLOYED_URL>
# The module name and otw name used to create the app, defined in your Move code `fun init`
$ MODULE_NAME=weather
$ OTW_NAME=WEATHER
# Make sure all env vars are populated
# PCR0, PCR1, and PCR2 are the values you recorded from out/nitro.pcrs after building the enclave
$ echo $APP_PACKAGE_ID
$ echo $ENCLAVE_PACKAGE_ID
$ echo $CAP_OBJECT_ID
$ echo $ENCLAVE_CONFIG_OBJECT_ID
$ echo 0x$PCR0
$ echo 0x$PCR1
$ echo 0x$PCR2
$ echo $MODULE_NAME
$ echo $OTW_NAME
$ echo $ENCLAVE_URL
# =======
# The two steps that follow (update pcrs, register enclave) can be reused if enclave server is updated
# =======
# This calls the update_pcrs onchain with the enclave cap and built PCRs, this can be reused to update PCRs if Rust server code is updated
$ sui client call --function update_pcrs --module enclave --package $ENCLAVE_PACKAGE_ID --type-args "$APP_PACKAGE_ID::$MODULE_NAME::$OTW_NAME" --args $ENCLAVE_CONFIG_OBJECT_ID $CAP_OBJECT_ID 0x$PCR0 0x$PCR1 0x$PCR2
# Optional, give it a name you like
$ sui client call --function update_name --module enclave --package $ENCLAVE_PACKAGE_ID --type-args "$APP_PACKAGE_ID::$MODULE_NAME::$OTW_NAME" --args $ENCLAVE_CONFIG_OBJECT_ID $CAP_OBJECT_ID "weather enclave, updated 2025-05-13"
# This script calls the get_attestation endpoint from your enclave url and uses it to call register_enclave onchain to register the public key, resulting in the created enclave object
$ sh ../../register_enclave.sh $ENCLAVE_PACKAGE_ID $APP_PACKAGE_ID $ENCLAVE_CONFIG_OBJECT_ID $ENCLAVE_URL $MODULE_NAME $OTW_NAME
# Record the created shared object ENCLAVE_OBJECT_ID as env var from register output
$ ENCLAVE_OBJECT_ID=0x1c9ccfc0f391f5e679e1f9f7d53c7fa455bf977e0f6dc71222990401f359c42a
If you modify the Rust server code, copy the changes to EC2, rebuild the enclave with make, record the new values from out/nitro.pcrs, and then rerun update_pcrs and register_enclave.sh as shown above.
You can view an example enclave config object containing PCR values on SuiScan. Additionally, see an example enclave object that includes the registered enclave public key.
Step 12: Stop your EC2 instance
It is important that you stop your EC2 instance when done, otherwise AWS charges $0.19/hour.
aws ec2 stop-instances --instance-ids <instance-id>
To start the instance later:
aws ec2 start-instances --instance-ids <instance-id>
To reconnect to your instance:
ssh -i ~/.ssh/<your-alias>.pem ec2-user@<public-ip>
cd nautilus
make ENCLAVE_APP=weather-example
make run
sh expose_enclave.sh
You don't need to rerun configure_enclave.sh when you reconnect: the run.sh and expose_enclave.sh changes it generated are already on the instance from when you copied the repo. Rerun it only if you change allowed_endpoints.yaml.
Artifacts for Twitter example
If you'd like to try out the other example, apps/twitter-example, use the following artifacts:
$ cd nautilus/
$ make ENCLAVE_APP=twitter-example
$ cat out/nitro.pcrs
Output
968f1266150cf8c4d62c9692b9f9b8fd6889d9331019d323f67a6ae6ab88b9378ad23f49f58c349526b9fdd5865da879 PCR0
968f1266150cf8c4d62c9692b9f9b8fd6889d9331019d323f67a6ae6ab88b9378ad23f49f58c349526b9fdd5865da879 PCR1
21b9efbc184807662e966d34f390821309eeac6802309798826296bf3e8bec7c10edb30948c90ba67310f7b964fc500a PCR2
# Add environment variables to use later when registering the enclave.
$ PCR0=968f1266150cf8c4d62c9692b9f9b8fd6889d9331019d323f67a6ae6ab88b9378ad23f49f58c349526b9fdd5865da879
$ PCR1=968f1266150cf8c4d62c9692b9f9b8fd6889d9331019d323f67a6ae6ab88b9378ad23f49f58c349526b9fdd5865da879
$ PCR2=21b9efbc184807662e966d34f390821309eeac6802309798826296bf3e8bec7c10edb30948c90ba67310f7b964fc500a
$ MODULE_NAME=twitter
$ OTW_NAME=TWITTER
# replace with your registered enclave
$ ENCLAVE_PACKAGE_ID=0xcca410b231d0acfa92c7709d490ab2f15fb5619be719ee0786099ffc3f6c9ab8
$ APP_PACKAGE_ID=0x652875162b566bb04187c76f93215e56c28aa05487393056279e331598ba4978
$ CAP_OBJECT_ID=0x44f3b57aa3870762ad334424cccb7f4c785cac007baab7e987c6d6a43c6aa100
$ ENCLAVE_CONFIG_OBJECT_ID=0xe33641a2dae5eb4acad3859e603ec4e25641af05f837c85058645c7d8d9d831a
$ ENCLAVE_OBJECT_ID=0x53db077721140910697668f9b2ee80fbecd104ac076d60fc1fb49ae57cd96c0d
# replace with your own enclave IP
$ ENCLAVE_URL=http://<PUBLIC_IP>:3000
You can view an example enclave config object containing PCR values. You can also view an example enclave object that includes the registered enclave public key.
You can find the frontend code for the Twitter example in this repository.
How do I develop against Nautilus locally?
For non-production development, you can inject a private key instead of generating it inside the enclave and run the enclave in debug mode anywhere.
- Modify the code to inject a deterministic private key into the enclave. This allows you to run the server anywhere with the same private key without needing an AWS Nitro instance.
- Use
make run-debuginstead ofmake run. Debug mode produces all-zero PCR values. You can register the enclave onchain with these all-zero PCRs once and reuse it for development without needing to update PCRs onchain.
Use this approach for development only. Never use it in production, because all-zero PCRs provide no attestation guarantees and injected keys compromise the TEE security model.
Troubleshooting
- Traffic forwarder error: Ensure that
allowed_endpoints.yamllists all targeted domains. Use the following command to test enclave connections to all domains.
$ curl -H 'Content-Type: application/json' -X GET http://<PUBLIC_IP>:3000/health_check
Output
{
"pk":"f343dae1df7f2c4676612368e40bf42878e522349e4135c2caa52bc79f0fc6e2",
"endpoints_status":
{
"api.weatherapi.com":true
}
}
-
Docker is not running: The EC2 instance might still be starting up. Wait a few moments, then try again.
-
Cannot connect to enclave: This might be due to a VSOCK communication issue. Verify that the enclave is running and that you exposed it with
sh expose_enclave.sh. -
SSO expired: Fix this error with the following commands:
aws sso login --profile AdministratorAccess-094557288217
aws configure export-credentials --profile AdministratorAccess-094557288217 --format env > ~/aws-temp-creds.sh
source ~/aws-temp-creds.sh
-
PEM invalid format: Your file is empty or corrupted. Recreate the key pair.
-
rsync permission denied: Fix permissions with the following commands:
chmod 700 ~/.ssh
chmod 400 ~/.ssh/<your-alias>.pem
- Enclave endpoint unreachable: Check that
expose_enclave.shis running, the instance is running, and you are using the correct public IP.