diff --git a/.env.example b/.env.example index 35f4863f0df..de009630dca 100644 --- a/.env.example +++ b/.env.example @@ -184,7 +184,12 @@ ZEROG_PRIVATE_KEY= ZEROG_FLOW_ADDRESS= # TEE Configuration -DSTACK_SIMULATOR_ENDPOINT= +# TEE_MODE options: +# - LOCAL: Uses simulator at localhost:8090 (for local development) +# - DOCKER: Uses simulator at host.docker.internal:8090 (for docker development) +# - PRODUCTION: No simulator, uses production endpoints +# Defaults to OFF if not specified +TEE_MODE=OFF #LOCAL|DOCKER|PRODUCTION WALLET_SECRET_SALT= # ONLY DEFINE IF YOU WANT TO USE TEE Plugin, otherwise it will throw errors # Galadriel Configuration diff --git a/agent/src/index.ts b/agent/src/index.ts index e866ad264d1..aa6ad9aeac5 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -41,9 +41,9 @@ import { imageGenerationPlugin } from "@ai16z/plugin-image-generation"; import { evmPlugin } from "@ai16z/plugin-evm"; import { createNodePlugin } from "@ai16z/plugin-node"; import { solanaPlugin } from "@ai16z/plugin-solana"; +import { teePlugin, TEEMode } from "@ai16z/plugin-tee"; import { aptosPlugin, TransferAptosToken } from "@ai16z/plugin-aptos"; import { flowPlugin } from "@ai16z/plugin-flow"; -import { teePlugin } from "@ai16z/plugin-tee"; import Database from "better-sqlite3"; import fs from "fs"; import path from "path"; @@ -368,6 +368,15 @@ export function createAgent( nodePlugin ??= createNodePlugin(); + const teeMode = getSecret(character, "TEE_MODE") || "OFF"; + const walletSecretSalt = getSecret(character, "WALLET_SECRET_SALT"); + + // Validate TEE configuration + if (teeMode !== TEEMode.OFF && !walletSecretSalt) { + elizaLogger.error("WALLET_SECRET_SALT required when TEE_MODE is enabled"); + throw new Error("Invalid TEE configuration"); + } + return new AgentRuntime({ databaseAdapter: db, token, @@ -403,12 +412,14 @@ export function createAgent( getSecret(character, "COINBASE_PRIVATE_KEY") ? [coinbaseMassPaymentsPlugin, tradePlugin, tokenContractPlugin, advancedTradePlugin] : []), + ...(teeMode !== TEEMode.OFF && walletSecretSalt + ? [teePlugin, solanaPlugin] + : []), getSecret(character, "COINBASE_API_KEY") && getSecret(character, "COINBASE_PRIVATE_KEY") && getSecret(character, "COINBASE_NOTIFICATION_URI") ? webhookPlugin : null, - getSecret(character, "WALLET_SECRET_SALT") ? teePlugin : null, getSecret(character, "ALCHEMY_API_KEY") ? goatPlugin : null, getSecret(character, "FLOW_ADDRESS") && getSecret(character, "FLOW_PRIVATE_KEY") diff --git a/docs/docs/advanced/eliza-in-tee.md b/docs/docs/advanced/eliza-in-tee.md new file mode 100644 index 00000000000..3763005ae7e --- /dev/null +++ b/docs/docs/advanced/eliza-in-tee.md @@ -0,0 +1,314 @@ +--- +sidebar_position: 17 +--- + +# 🫖 Eliza in TEE + +![](/img/eliza_in_tee.jpg) + +## Overview + +The Eliza agent can be deployed in a TEE environment to ensure the security and privacy of the agent's data. This guide will walk you through the process of setting up and running an Eliza agent in a TEE environment by utilizing the TEE Plugin in the Eliza Framework. + +### Background + +The TEE Plugin in the Eliza Framework is built on top of the [Dstack SDK](https://github.com/Dstack-TEE/dstack), which is designed to simplify the steps for developers to deploy programs to CVM (Confidential VM), and to follow the security best practices by default. The main features include: + +- Convert any docker container to a CVM image to deploy on supported TEEs +- Remote Attestation API and a chain-of-trust visualization on Web UI +- Automatic RA-HTTPS wrapping with content addressing domain on 0xABCD.dstack.host +- Decouple the app execution and state persistent from specific hardware with decentralized Root-of-Trust + +--- + +## Core Components + +Eliza's TEE implementation consists of two primary providers that handle secure key managementoperations and remote attestations. + +These components work together to provide: + +1. Secure key derivation within the TEE +2. Verifiable proof of TEE execution +3. Support for both development (simulator) and production environments + +The providers are typically used together, as seen in the wallet key derivation process where each derived key includes an attestation quote to prove it was generated within the TEE environment. + +--- + +### Derive Key Provider + +The DeriveKeyProvider enables secure key derivation within TEE environments. It supports: + +- Multiple TEE modes: + - `LOCAL`: Connects to simulator at `localhost:8090` for local development on Mac/Windows + - `DOCKER`: Connects to simulator via `host.docker.internal:8090` for local development on Linux + - `PRODUCTION`: Connects to actual TEE environment when deployed to the [TEE Cloud](https://teehouse.vercel.app) + +Key features: + +- Support to deriveEd25519 (Solana) and ECDSA (EVM) keypairs +- Generates deterministic keys based on a secret salt and agent ID +- Includes remote attestation for each derived key +- Supports raw key derivation for custom use cases + +Example usage: + +```typescript +const provider = new DeriveKeyProvider(teeMode); +// For Solana +const { keypair, attestation } = await provider.deriveEd25519Keypair( + "/", + secretSalt, + agentId +); +// For EVM +const { keypair, attestation } = await provider.deriveEcdsaKeypair( + "/", + secretSalt, + agentId +); +``` + +--- + +### Remote Attestation Provider + +The RemoteAttestationProvider handles TEE environment verification and quote generation. It: + +- Connects to the same TEE modes as DeriveKeyProvider +- Generates TDX quotes with replay protection (RTMRs) +- Provides attestation data that can be verified by third parties + +Key features: + +- Generates attestation quotes with custom report data +- Includes timestamp for quote verification +- Supports both simulator and production environments + +Example usage: + +```typescript +const provider = new RemoteAttestationProvider(teeMode); +const quote = await provider.generateAttestation(reportData); +``` + +## Tutorial + +--- + +### Prerequisites + +Before getting started with Eliza, ensure you have: + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or [Orbstack](https://orbstack.dev/) (Orbstack is recommended) +- For Mac/Windows: Check the prerequisites from [Quickstart Guide](./quickstart.md) +- For Linux: You just need Docker + +--- + +### Environment Setup + +To set up your environment for TEE development: + +1. **Configure TEE Mode** + + Set the `TEE_MODE` environment variable to one of: + + ```env + # For Mac/Windows local development + TEE_MODE=LOCAL + + # For Linux/Docker local development + TEE_MODE=DOCKER + + # For production deployment + TEE_MODE=PRODUCTION + ``` + +2. **Set Required Environment Variables** + + ```env + # Required for key derivation + WALLET_SECRET_SALT=your_secret_salt + ``` + +3. **Start the TEE Simulator** + + ```bash + docker pull phalanetwork/tappd-simulator:latest + # by default the simulator is available in localhost:8090 + docker run --rm -p 8090:8090 phalanetwork/tappd-simulator:latest + ``` + +### Run an Eliza Agent Locally with TEE Simulator + +1. **Configure Eliza Agent** + + Go through the [configuration guide](./configuration.md) to set up your Eliza agent. +2. **Start the TEE Simulator** + Follow the simulator setup instructions above based on your TEE mode. + +3. **For Mac/Windows** + + Make sure to set the `TEE_MODE` environment variable to `LOCAL`. Then you can install the dependencies and run the agent locally: + + ```bash + pnpm i + pnpm build + pnpm start --character=./characters/yourcharacter.character.json + ``` + +4. **Verify TEE Attestation** + + You can verify the TEE attestation quote by going to the [TEE RA Explorer](https://ra-quote-explorer.vercel.app/) and pasting the attestation quote from the agent logs. Here's an example of interacting with the Eliza agent to ask for the agent's wallet address: + + ```bash + You: what's your wallet address? + ``` + + Log output from the agent: + + ```bash + Generating attestation for: {"agentId":"025e0996-69d7-0dce-8189-390e354fd1c1","publicKey":"9yZBmCRRFEBtA3KYokxC24igv1ijFp6tyvzKxRs3khTE"} + rtmr0: a4a17452e7868f62f77ea2039bd2840e7611a928c26e87541481256f57bfbe3647f596abf6e8f6b5a0e7108acccc6e89 + rtmr1: db6bcc74a3ac251a6398eca56b2fcdc8c00a9a0b36bc6299e06fb4bb766cb9ecc96de7e367c56032c7feff586f9e557e + rtmr2: 2cbe156e110b0cc4b2418600dfa9fb33fc60b3f04b794ec1b8d154b48f07ba8c001cd31f75ca0d0fb516016552500d07 + rtmr3: eb7110de9956d7b4b1a3397f843b39d92df4caac263f5083e34e3161e4d6686c46c3239e7fbf61241a159d8da6dc6bd1f + Remote attestation quote: { + quote: '0x0400030081000000736940f888442c8ca8cb432d7a87145f9b7aeab1c5d129ce901716a7506375426ea8741ca69be68e92c5df29f539f103eb60ab6780c56953b0d81af523a031617b32d5e8436cceb019177103f4aceedbf114a846baf8e8e2b8e6d3956e96d6b89d94a0f1a366e6c309d77c77c095a13d2d5e2f8e2d7f51ece4ae5ffc5fe8683a37387bfdb9acb8528f37342360abb64ec05ff438f7e4fad73c69a627de245a31168f69823883ed8ba590c454914690946b7b07918ded5b89dc663c70941f8704978b91a24b54d88038c30d20d14d85016a524f7176c7a7cff7233a2a4405da9c31c8569ac3adfe5147bdb92faee0f075b36e8ce794aaf596facd881588167fbcf5a7d059474c1e4abff645bba8a813f3083c5a425fcc88cd706b19494dedc04be2bc3ab1d71b2a062ddf62d0393d8cb421393cccc932a19d43e315a18a10d216aea4a1752cf3f3b0b2fb36bea655822e2b27c6156970d18e345930a4a589e1850fe84277e0913ad863dffb1950fbeb03a4a17452e7868f62f77ea2039bd2840e7611a928c26e87541481256f57bfbe3647f596abf6e8f6b5a0e7108acccc6e89db6bcc74a3ac251a6398eca56b2fcdc8c00a9a0b36bc6299e06fb4bb766cb9ecc96de7e367c56032c7feff586f9e557e2cbe156e110b0cc4b2418600dfa9fb33fc60b3f04b794ec1b8d154b48f07ba8c001cd31f75ca0d0fb516016552500d07eb7110de9956d7b4b1a3397f843b39d92df4caac263f5083e34e3161e4d6686c46c3239e7fbf61241a159d8da6dc6bd13df734883d4d0d78d670a1d17e28ef09dffbbfbd15063b73113cb5bed692d68cc30c38cb9389403fe6a1c32c35dbac75464b77597e27b854839db51dfde0885462020000530678b9eb99d1b9e08a6231ef00055560f7d3345f54ce355da68725bb38cab0caf84757ddb93db87577758bb06de7923c4ee3583453f284c8b377a1ec2ef613491e051c801a63da5cb42b9c12e26679fcf489f3b14bd5e8f551227b09d976975e0fbd68dcdf129110a5ca8ed8d163dafb60e1ec4831d5285a7fbae81d0e39580000dc010000ebb282d5c6aca9053a21814e9d65a1516ebeaacf6fc88503e794d75cfc5682e86aa04e9d6e58346e013c5c1203afc5c72861e2a7052afcdcb3ddcccd102dd0daeb595968edb6a6c513db8e2155fc302eeca7a34c9ba81289d6941c4c813db9bf7bd0981d188ab131e5ae9c4bb831e4243b20edb7829a6a7a9cf0eae1214b450109d990e2c824c2a60a47faf90c24992583bc5c3da3b58bd8830a4f0ad5c650aa08ae0e067d4251d251e56d70972ad901038082ee9340f103fd687ec7d91a9b8b8652b1a2b7befb4cbfdb6863f00142e0b2e67198ddc8ddbe96dc02762d935594394f173114215cb5abcf55b9815eb545683528c990bfae34c34358dbb19dfc1426f56cba12af325d7a2941c0d45d0ea4334155b790554d3829e3be618eb1bfc6f3a06f488bbeb910b33533c6741bff6c8a0ca43eb2417eec5ecc2f50f65c3b40d26174376202915337c7992cdd44471dee7a7b2038605415a7af593fd9066661e594b26f4298baf6d001906aa8fc1c460966fbc17b2c35e0973f613399936173802cf0453a4e7d8487b6113a77947eef190ea8d47ba531ce51abf5166448c24a54de09d671fd57cbd68154f5995aee6c2ccfd6738387cf3ad9f0ad5e8c7d46fb0a0000000000000000000000bd920a00000000000000000000000000', + timestamp: 1733606453433 + } + ``` + + Take the `quote` field and paste it into the [TEE RA Explorer](https://ra-quote-explorer.vercel.app/) to verify the attestation. **Note**: The verification will be unverified since the quote is generated from the TEE simulator. + + ![](https://i.imgur.com/xYGMeP4.png) + + ![](https://i.imgur.com/BugdNUy.png) + +### Build, Test, and Publish an Eliza Agent Docker Image + +Now that we have run the Eliza agent in the TEE simulator, we can build and publish an Eliza agent Docker image to prepare for deployment to a real TEE environment. + +First, you need to create a Docker account and publish your image to a container registry. Here we will use [Docker Hub](https://hub.docker.com/) as an example. + +Login to Docker Hub: + +```bash +docker login +``` + +Build the Docker image: + +```bash +# For Linux/AMD64 machines run +docker build -t username/eliza-agent:latest . + +# For architecture other than AMD64, run +docker buildx build --platform=linux/amd64 -t username/eliza-agent:latest . +``` + +For Linux/AMD64 machines, you can now test the agent locally by updating the `TEE_MODE` environment variable to `DOCKER` and setting the environment variables in the [docker-compose.yaml](https://github.com/ai16z/eliza/blob/main/docker-compose.yaml) file. Once you have done that, you can start the agent by running: + +> **Note**: Make sure the TEE simulator is running before starting the agent through docker compose. + +```bash +docker compose up +``` + +Publish the Docker image to a container registry: + +```bash +docker push username/eliza-agent:latest +``` + +Now we are ready to deploy the Eliza agent to a real TEE environment. + +### Run an Eliza Agent in a Real TEE Environment + +Before deploying the Eliza agent to a real TEE environment, you need to create a new TEE account on the [TEE Cloud](https://teehouse.vercel.app). Reach out to Phala Network on [Discord](https://discord.gg/phalanetwork) if you need help. + +Next, you will need to take the docker-compose.yaml file in the root folder of the project and edit it based on your agent configuration. + +> **Note**: The API Keys and other secret environment variables should be set in your secret environment variables configuration in the TEE Cloud dashboard. + +```yaml +# docker-compose.yaml +services: + tee: + command: ["pnpm", "start", "--character=./characters/yourcharacter.character.json"] + image: username/eliza-agent:latest + stdin_open: true + tty: true + volumes: + - /var/run/tappd.sock:/var/run/tappd.sock + - tee:/app/packages/client-twitter/src/tweetcache + - tee:/app/db.sqlite + environment: + - REDPILL_API_KEY=$REDPILL_API_KEY + - SMALL_REDPILL_MODEL=anthropic/claude-3-5-sonnet + - MEDIUM_REDPILL_MODEL=anthropic/claude-3-5-sonnet + - LARGE_REDPILL_MODEL=anthropic/claude-3-opus + - ELEVENLABS_XI_API_KEY=$ELEVENLABS_XI_API_KEY + - ELEVENLABS_MODEL_ID=eleven_multilingual_v2 + - ELEVENLABS_VOICE_ID=21m00Tcm4TlvDq8ikWAM + - ELEVENLABS_VOICE_STABILITY=0.5 + - ELEVENLABS_VOICE_SIMILARITY_BOOST=0.9 + - ELEVENLABS_VOICE_STYLE=0.66 + - ELEVENLABS_VOICE_USE_SPEAKER_BOOST=false + - ELEVENLABS_OPTIMIZE_STREAMING_LATENCY=4 + - ELEVENLABS_OUTPUT_FORMAT=pcm_16000 + - TWITTER_DRY_RUN=false + - TWITTER_USERNAME=$TWITTER_USERNAME + - TWITTER_PASSWORD=$TWITTER_PASSWORD + - TWITTER_EMAIL=$TWITTER_EMAIL + - X_SERVER_URL=$X_SERVER_URL + - BIRDEYE_API_KEY=$BIRDEYE_API_KEY + - SOL_ADDRESS=So11111111111111111111111111111111111111112 + - SLIPPAGE=1 + - RPC_URL=https://api.mainnet-beta.solana.com + - HELIUS_API_KEY=$HELIUS_API_KEY + - SERVER_PORT=3000 + - WALLET_SECRET_SALT=$WALLET_SECRET_SALT + - TEE_MODE=PRODUCTION + ports: + - "3000:80" + restart: always + +volumes: + tee: +``` + +Now you can deploy the Eliza agent to a real TEE environment. Go to the [TEE Cloud](https://teehouse.vercel.app) and click on the `Create VM` button to configure your Eliza agent deployment. + +Click on the `Compose Manifest Mode` tab and paste the docker-compose.yaml file content into the `Compose Manifest` field. + +![Compose Manifest](https://i.imgur.com/wl6pddX.png) + +Next, go to the `Resources` tab and configure your VM resources. + +> **Note**: The `CPU` and `Memory` resources should be greater than the minimum requirements for your agent configuration (Recommended: 2 CPU, 4GB Memory, 50GB Disk). + +![Resources](https://i.imgur.com/HsmupO1.png) + +Finally, click on the `Submit` button to deploy your Eliza agent. + +This will take a few minutes to complete. Once the deployment is complete, you can click on the `View` button to view your Eliza agent. + +Here is an example of a deployed agent named `vitailik2077`: + +![Deployed Agent](https://i.imgur.com/ie8gpg9.png) + +I can go to the dashboard and view the remote attestation info: + +![Agent Dashboard](https://i.imgur.com/vUqHGjF.png) + +Click on the `Logs` tab to view the agent logs. + +![Agent Logs](https://i.imgur.com/aU3i0Dv.png) + +Now we can verify the REAL TEE attestation quote by going to the [TEE RA Explorer](https://ra-quote-explorer.vercel.app/) and pasting the attestation quote from the agent logs. + +![TEE RA Explorer](https://i.imgur.com/TJ5299l.png) + +Congratulations! You have successfully run an Eliza agent in a real TEE environment. diff --git a/docs/docs/guides/advanced.md b/docs/docs/guides/advanced.md index 5b9ec91a644..a75cbb0a593 100644 --- a/docs/docs/guides/advanced.md +++ b/docs/docs/guides/advanced.md @@ -399,3 +399,4 @@ debug("Detailed operation info: %O", { - [Trust Engine Documentation](../advanced/trust-engine.md) for scoring system - [Autonomous Trading Guide](../advanced/autonomous-trading.md) for trading features - [Fine-tuning Guide](../advanced/fine-tuning.md) for model optimization +- [Eliza in TEE](../advanced/eliza-in-tee.md) for TEE integration diff --git a/docs/sidebars.js b/docs/sidebars.js index 680b8fef40b..eae0deb6841 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -107,6 +107,11 @@ const sidebars = { id: "advanced/autonomous-trading", label: "Autonomous Trading", }, + { + type: "doc", + id: "advanced/eliza-in-tee", + label: "Eliza in TEE", + }, ], }, { diff --git a/docs/sidebars.js.bak b/docs/sidebars.js.bak index 13b55a0f76c..d1b16db7307 100644 --- a/docs/sidebars.js.bak +++ b/docs/sidebars.js.bak @@ -102,6 +102,11 @@ const sidebars = { id: "advanced/autonomous-trading", label: "Autonomous Trading", }, + { + type: "doc", + id: "advanced/eliza-in-tee", + label: "Eliza in TEE", + }, ], }, { diff --git a/docs/static/img/eliza_in_tee.jpg b/docs/static/img/eliza_in_tee.jpg new file mode 100644 index 00000000000..507afc6f6aa Binary files /dev/null and b/docs/static/img/eliza_in_tee.jpg differ diff --git a/packages/plugin-solana/package.json b/packages/plugin-solana/package.json index 057246e23ae..2aa671fb7af 100644 --- a/packages/plugin-solana/package.json +++ b/packages/plugin-solana/package.json @@ -7,6 +7,7 @@ "dependencies": { "@ai16z/eliza": "workspace:*", "@ai16z/plugin-trustdb": "workspace:*", + "@ai16z/plugin-tee": "workspace:*", "@coral-xyz/anchor": "0.30.1", "@solana/spl-token": "0.4.9", "@solana/web3.js": "1.95.8", diff --git a/packages/plugin-solana/src/actions/pumpfun.ts b/packages/plugin-solana/src/actions/pumpfun.ts index 06858e00226..5cc1ebca18b 100644 --- a/packages/plugin-solana/src/actions/pumpfun.ts +++ b/packages/plugin-solana/src/actions/pumpfun.ts @@ -5,7 +5,6 @@ import { Connection, Keypair, PublicKey } from "@solana/web3.js"; import { CreateTokenMetadata, PriorityFee, PumpFunSDK } from "pumpdotfun-sdk"; import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import bs58 from "bs58"; import { settings, ActionExample, @@ -241,6 +240,7 @@ const promptConfirmation = async (): Promise => { // Save the base64 data to a file import * as fs from "fs"; import * as path from "path"; +import { getWalletKey } from "../keypairUtils.ts"; const pumpfunTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. @@ -387,11 +387,10 @@ export default { const slippage = "2000"; try { // Get private key from settings and create deployer keypair - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - const secretKey = bs58.decode(privateKeyString); - const deployerKeypair = Keypair.fromSecretKey(secretKey); + const { keypair: deployerKeypair } = await getWalletKey( + runtime, + true + ); // Generate new mint keypair const mintKeypair = Keypair.generate(); diff --git a/packages/plugin-solana/src/actions/swap.ts b/packages/plugin-solana/src/actions/swap.ts index 4fe2f311a84..434e2cb816d 100644 --- a/packages/plugin-solana/src/actions/swap.ts +++ b/packages/plugin-solana/src/actions/swap.ts @@ -1,7 +1,5 @@ -import bs58 from "bs58"; import { Connection, - Keypair, PublicKey, VersionedTransaction, } from "@solana/web3.js"; @@ -24,6 +22,7 @@ import { TokenProvider } from "../providers/token.ts"; import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; import { walletProvider, WalletProvider } from "../providers/wallet.ts"; import { getTokenDecimals } from "./swapUtils.ts"; +import { getWalletKey } from "../keypairUtils.ts"; async function swapToken( connection: Connection, @@ -145,12 +144,10 @@ Respond with a JSON markdown block containing only the extracted values. Use nul // get all the tokens in the wallet using the wallet provider async function getTokensInWallet(runtime: IAgentRuntime) { + const { publicKey } = await getWalletKey(runtime, false); const walletProvider = new WalletProvider( new Connection("https://api.mainnet-beta.solana.com"), - new PublicKey( - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY") - ) + publicKey ); const walletInfo = await walletProvider.fetchPortfolioValue(runtime); @@ -293,9 +290,9 @@ export const executeSwap: Action = { const connection = new Connection( "https://api.mainnet-beta.solana.com" ); - const walletPublicKey = new PublicKey( - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY") + const { publicKey: walletPublicKey } = await getWalletKey( + runtime, + false ); const provider = new WalletProvider(connection, walletPublicKey); @@ -322,44 +319,11 @@ export const executeSwap: Action = { VersionedTransaction.deserialize(transactionBuf); console.log("Preparing to sign transaction..."); - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - - // Handle different private key formats - let secretKey: Uint8Array; - try { - // First try to decode as base58 - secretKey = bs58.decode(privateKeyString); - // eslint-disable-next-line - } catch (e) { - try { - // If that fails, try base64 - secretKey = Uint8Array.from( - Buffer.from(privateKeyString, "base64") - ); - // eslint-disable-next-line - } catch (e2) { - throw new Error("Invalid private key format"); - } - } - - // Verify the key length - if (secretKey.length !== 64) { - console.error("Invalid key length:", secretKey.length); - throw new Error( - `Invalid private key length: ${secretKey.length}. Expected 64 bytes.` - ); - } console.log("Creating keypair..."); - const keypair = Keypair.fromSecretKey(secretKey); - + const { keypair } = await getWalletKey(runtime, true); // Verify the public key matches what we expect - const expectedPublicKey = - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY"); - if (keypair.publicKey.toBase58() !== expectedPublicKey) { + if (keypair.publicKey.toBase58() !== walletPublicKey.toBase58()) { throw new Error( "Generated public key doesn't match expected public key" ); diff --git a/packages/plugin-solana/src/actions/swapDao.ts b/packages/plugin-solana/src/actions/swapDao.ts index e2928c7441c..2cadb4f78fc 100644 --- a/packages/plugin-solana/src/actions/swapDao.ts +++ b/packages/plugin-solana/src/actions/swapDao.ts @@ -6,6 +6,7 @@ import { } from "@ai16z/eliza"; import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js"; import { getQuote } from "./swapUtils.ts"; +import { getWalletKey } from "../keypairUtils.ts"; async function invokeSwapDao( connection: Connection, @@ -66,15 +67,9 @@ export const executeSwapForDAO: Action = { const connection = new Connection( runtime.getSetting("RPC_URL") as string ); - const authority = Keypair.fromSecretKey( - Uint8Array.from( - Buffer.from( - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"), // should be the authority private key - "base64" - ) - ) - ); + + const { keypair: authority } = await getWalletKey(runtime, true); + const daoMint = new PublicKey(runtime.getSetting("DAO_MINT")); // DAO mint address // Derive PDAs diff --git a/packages/plugin-solana/src/actions/transfer.ts b/packages/plugin-solana/src/actions/transfer.ts index 13403be7a3a..b8b0d12aecf 100644 --- a/packages/plugin-solana/src/actions/transfer.ts +++ b/packages/plugin-solana/src/actions/transfer.ts @@ -2,12 +2,10 @@ import { getAssociatedTokenAddressSync, createTransferInstruction, } from "@solana/spl-token"; -import bs58 from "bs58"; import { elizaLogger, settings } from "@ai16z/eliza"; import { Connection, - Keypair, PublicKey, TransactionMessage, VersionedTransaction, @@ -24,6 +22,7 @@ import { type Action, } from "@ai16z/eliza"; import { composeContext } from "@ai16z/eliza"; +import { getWalletKey } from "../keypairUtils"; import { generateObjectDEPRECATED } from "@ai16z/eliza"; export interface TransferContent extends Content { @@ -138,11 +137,10 @@ export default { } try { - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - const secretKey = bs58.decode(privateKeyString); - const senderKeypair = Keypair.fromSecretKey(secretKey); + const { keypair: senderKeypair } = await getWalletKey( + runtime, + true + ); const connection = new Connection(settings.RPC_URL!); diff --git a/packages/plugin-solana/src/environment.ts b/packages/plugin-solana/src/environment.ts index b20f375ec3f..995f5979f0d 100644 --- a/packages/plugin-solana/src/environment.ts +++ b/packages/plugin-solana/src/environment.ts @@ -2,14 +2,26 @@ import { IAgentRuntime } from "@ai16z/eliza"; import { z } from "zod"; export const solanaEnvSchema = z.object({ - WALLET_SECRET_KEY: z.string().min(1, "Wallet secret key is required"), - WALLET_PUBLIC_KEY: z.string().min(1, "Wallet public key is required"), - SOL_ADDRESS: z.string().min(1, "SOL address is required"), - SLIPPAGE: z.string().min(1, "Slippage is required"), - RPC_URL: z.string().min(1, "RPC URL is required"), - HELIUS_API_KEY: z.string().min(1, "Helius API key is required"), - BIRDEYE_API_KEY: z.string().min(1, "Birdeye API key is required"), -}); + WALLET_SECRET_SALT: z.string().optional(), + }).and( + z.union([ + z.object({ + WALLET_SECRET_KEY: z.string().min(1, "Wallet secret key is required"), + WALLET_PUBLIC_KEY: z.string().min(1, "Wallet public key is required"), + }), + z.object({ + WALLET_SECRET_SALT: z.string().min(1, "Wallet secret salt is required"), + }) + ]) + ).and( + z.object({ + SOL_ADDRESS: z.string().min(1, "SOL address is required"), + SLIPPAGE: z.string().min(1, "Slippage is required"), + RPC_URL: z.string().min(1, "RPC URL is required"), + HELIUS_API_KEY: z.string().min(1, "Helius API key is required"), + BIRDEYE_API_KEY: z.string().min(1, "Birdeye API key is required"), + }) +); export type SolanaConfig = z.infer; @@ -18,6 +30,9 @@ export async function validateSolanaConfig( ): Promise { try { const config = { + WALLET_SECRET_SALT: + runtime.getSetting("WALLET_SECRET_SALT") || + process.env.WALLET_SECRET_SALT, WALLET_SECRET_KEY: runtime.getSetting("WALLET_SECRET_KEY") || process.env.WALLET_SECRET_KEY, diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index 2d04eaab02b..d98282206be 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -15,7 +15,8 @@ import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; import { TokenProvider } from "../providers/token.ts"; import { WalletProvider } from "../providers/wallet.ts"; import { TrustScoreDatabase } from "@ai16z/plugin-trustdb"; -import { Connection, PublicKey } from "@solana/web3.js"; +import { Connection } from "@solana/web3.js"; +import { getWalletKey } from "../keypairUtils.ts"; const shouldProcessTemplate = `# Task: Decide if the recent messages should be processed for token recommendations. @@ -144,6 +145,8 @@ async function handler(runtime: IAgentRuntime, message: Memory) { ); }); + const { publicKey } = await getWalletKey(runtime, false); + for (const rec of filteredRecommendations) { // create the wallet provider and token provider const walletProvider = new WalletProvider( @@ -151,10 +154,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { runtime.getSetting("RPC_URL") || "https://api.mainnet-beta.solana.com" ), - new PublicKey( - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY") - ) + publicKey ); const tokenProvider = new TokenProvider( rec.contractAddress, diff --git a/packages/plugin-solana/src/keypairUtils.ts b/packages/plugin-solana/src/keypairUtils.ts new file mode 100644 index 00000000000..53885ec05a0 --- /dev/null +++ b/packages/plugin-solana/src/keypairUtils.ts @@ -0,0 +1,80 @@ +import { Keypair, PublicKey } from "@solana/web3.js"; +import { DeriveKeyProvider, TEEMode } from "@ai16z/plugin-tee"; +import bs58 from "bs58"; +import { IAgentRuntime } from "@ai16z/eliza"; + +export interface KeypairResult { + keypair?: Keypair; + publicKey?: PublicKey; +} + +/** + * Gets either a keypair or public key based on TEE mode and runtime settings + * @param runtime The agent runtime + * @param requirePrivateKey Whether to return a full keypair (true) or just public key (false) + * @returns KeypairResult containing either keypair or public key + */ +export async function getWalletKey( + runtime: IAgentRuntime, + requirePrivateKey: boolean = true +): Promise { + const teeMode = runtime.getSetting("TEE_MODE") || TEEMode.OFF; + + if (teeMode !== TEEMode.OFF) { + const walletSecretSalt = runtime.getSetting("WALLET_SECRET_SALT"); + if (!walletSecretSalt) { + throw new Error("WALLET_SECRET_SALT required when TEE_MODE is enabled"); + } + + const deriveKeyProvider = new DeriveKeyProvider(teeMode); + const deriveKeyResult = await deriveKeyProvider.deriveEd25519Keypair( + "/", + walletSecretSalt, + runtime.agentId + ); + + return requirePrivateKey + ? { keypair: deriveKeyResult.keypair } + : { publicKey: deriveKeyResult.keypair.publicKey }; + } + + // TEE mode is OFF + if (requirePrivateKey) { + const privateKeyString = + runtime.getSetting("SOLANA_PRIVATE_KEY") ?? + runtime.getSetting("WALLET_PRIVATE_KEY"); + + if (!privateKeyString) { + throw new Error("Private key not found in settings"); + } + + try { + // First try base58 + const secretKey = bs58.decode(privateKeyString); + return { keypair: Keypair.fromSecretKey(secretKey) }; + } catch (e) { + console.log("Error decoding base58 private key:", e); + try { + // Then try base64 + console.log("Try decoding base64 instead"); + const secretKey = Uint8Array.from( + Buffer.from(privateKeyString, "base64") + ); + return { keypair: Keypair.fromSecretKey(secretKey) }; + } catch (e2) { + console.error("Error decoding private key: ", e2); + throw new Error("Invalid private key format"); + } + } + } else { + const publicKeyString = + runtime.getSetting("SOLANA_PUBLIC_KEY") ?? + runtime.getSetting("WALLET_PUBLIC_KEY"); + + if (!publicKeyString) { + throw new Error("Public key not found in settings"); + } + + return { publicKey: new PublicKey(publicKeyString) }; + } +} \ No newline at end of file diff --git a/packages/plugin-solana/src/providers/simulationSellingService.ts b/packages/plugin-solana/src/providers/simulationSellingService.ts index f77a2c8e197..39d5e5b08d7 100644 --- a/packages/plugin-solana/src/providers/simulationSellingService.ts +++ b/packages/plugin-solana/src/providers/simulationSellingService.ts @@ -12,6 +12,7 @@ import { IAgentRuntime } from "@ai16z/eliza"; import { WalletProvider } from "./wallet.ts"; import * as amqp from "amqplib"; import { ProcessedTokenData } from "../types/token.ts"; +import { getWalletKey } from "../keypairUtils.ts"; interface SellDetails { sell_amount: number; @@ -39,13 +40,7 @@ export class SimulationSellingService { this.trustScoreDb = trustScoreDb; this.connection = new Connection(runtime.getSetting("RPC_URL")); - this.walletProvider = new WalletProvider( - this.connection, - new PublicKey( - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY") - ) - ); + this.initializeWalletProvider(); this.baseMint = new PublicKey( runtime.getSetting("BASE_MINT") || "So11111111111111111111111111111111111111112" @@ -175,6 +170,17 @@ export class SimulationSellingService { } } + /** + * Derives the public key based on the TEE (Trusted Execution Environment) mode and initializes the wallet provider. + * If TEE mode is enabled, derives a keypair using the DeriveKeyProvider with the wallet secret salt and agent ID. + * If TEE mode is disabled, uses the provided Solana public key or wallet public key from settings. + */ + private async initializeWalletProvider(): Promise { + const { publicKey } = await getWalletKey(this.runtime, false); + + this.walletProvider = new WalletProvider(this.connection, publicKey); + } + public async startService() { // starting the service console.log("Starting SellingService..."); diff --git a/packages/plugin-solana/src/providers/token.ts b/packages/plugin-solana/src/providers/token.ts index 802b4c3f0a6..a854cf392d9 100644 --- a/packages/plugin-solana/src/providers/token.ts +++ b/packages/plugin-solana/src/providers/token.ts @@ -15,7 +15,8 @@ import NodeCache from "node-cache"; import * as path from "path"; import { toBN } from "../bignumber.ts"; import { WalletProvider, Item } from "./wallet.ts"; -import { Connection, PublicKey } from "@solana/web3.js"; +import { Connection } from "@solana/web3.js"; +import { getWalletKey } from "../keypairUtils.ts"; const PROVIDER_CONFIG = { BIRDEYE_API: "https://public-api.birdeye.so", @@ -1102,9 +1103,11 @@ const tokenProvider: Provider = { _state?: State ): Promise => { try { + const { publicKey } = await getWalletKey(runtime, false); + const walletProvider = new WalletProvider( connection, - new PublicKey(PROVIDER_CONFIG.MAIN_WALLET) + publicKey ); const provider = new TokenProvider( diff --git a/packages/plugin-solana/src/providers/wallet.ts b/packages/plugin-solana/src/providers/wallet.ts index 65c4e14938e..e712aabe69f 100644 --- a/packages/plugin-solana/src/providers/wallet.ts +++ b/packages/plugin-solana/src/providers/wallet.ts @@ -2,6 +2,7 @@ import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; import { Connection, PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import NodeCache from "node-cache"; +import { getWalletKey } from "../keypairUtils"; // Provider configuration const PROVIDER_CONFIG = { @@ -370,13 +371,7 @@ const walletProvider: Provider = { _state?: State ): Promise => { try { - const publicKey = runtime.getSetting("SOLANA_PUBLIC_KEY"); - if (!publicKey) { - console.error( - "SOLANA_PUBLIC_KEY not configured, skipping wallet injection" - ); - return ""; - } + const { publicKey } = await getWalletKey(runtime, false); const connection = new Connection( runtime.getSetting("RPC_URL") || PROVIDER_CONFIG.DEFAULT_RPC @@ -384,7 +379,7 @@ const walletProvider: Provider = { const provider = new WalletProvider( connection, - new PublicKey(publicKey) + publicKey ); return await provider.getFormattedPortfolio(runtime); diff --git a/packages/plugin-tee/eslint.config.mjs b/packages/plugin-tee/eslint.config.mjs new file mode 100644 index 00000000000..92fe5bbebef --- /dev/null +++ b/packages/plugin-tee/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-tee/package.json b/packages/plugin-tee/package.json index 92719d7e90e..6793587c8cc 100644 --- a/packages/plugin-tee/package.json +++ b/packages/plugin-tee/package.json @@ -6,7 +6,7 @@ "types": "dist/index.d.ts", "dependencies": { "@ai16z/eliza": "workspace:*", - "@phala/dstack-sdk": "0.1.4", + "@phala/dstack-sdk": "0.1.6", "@solana/spl-token": "0.4.9", "@solana/web3.js": "1.95.8", "bignumber": "1.1.0", @@ -19,7 +19,8 @@ }, "scripts": { "build": "tsup --format esm --dts", - "dev": "tsup --format esm --dts --watch" + "dev": "tsup --format esm --dts --watch", + "lint": "eslint . --fix" }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-tee/src/index.ts b/packages/plugin-tee/src/index.ts index dc8dd8d6ff0..7f0225b0be8 100644 --- a/packages/plugin-tee/src/index.ts +++ b/packages/plugin-tee/src/index.ts @@ -2,6 +2,10 @@ import { Plugin } from "@ai16z/eliza"; import { remoteAttestationProvider } from "./providers/remoteAttestationProvider"; import { deriveKeyProvider } from "./providers/deriveKeyProvider"; +export { DeriveKeyProvider } from "./providers/deriveKeyProvider"; +export { RemoteAttestationProvider } from "./providers/remoteAttestationProvider"; +export { RemoteAttestationQuote, TEEMode } from "./types/tee"; + export const teePlugin: Plugin = { name: "tee", description: diff --git a/packages/plugin-tee/src/providers/deriveKeyProvider.ts b/packages/plugin-tee/src/providers/deriveKeyProvider.ts index 26c4a71efae..65b6e83676e 100644 --- a/packages/plugin-tee/src/providers/deriveKeyProvider.ts +++ b/packages/plugin-tee/src/providers/deriveKeyProvider.ts @@ -4,14 +4,56 @@ import crypto from "crypto"; import { DeriveKeyResponse, TappdClient } from "@phala/dstack-sdk"; import { privateKeyToAccount } from "viem/accounts"; import { PrivateKeyAccount, keccak256 } from "viem"; +import { RemoteAttestationProvider } from "./remoteAttestationProvider"; +import { TEEMode, RemoteAttestationQuote } from "../types/tee"; + +interface DeriveKeyAttestationData { + agentId: string; + publicKey: string; +} class DeriveKeyProvider { private client: TappdClient; + private raProvider: RemoteAttestationProvider; + + constructor(teeMode?: string) { + let endpoint: string | undefined; + + // Both LOCAL and DOCKER modes use the simulator, just with different endpoints + switch(teeMode) { + case TEEMode.LOCAL: + endpoint = "http://localhost:8090"; + console.log("TEE: Connecting to local simulator at localhost:8090"); + break; + case TEEMode.DOCKER: + endpoint = "http://host.docker.internal:8090"; + console.log("TEE: Connecting to simulator via Docker at host.docker.internal:8090"); + break; + case TEEMode.PRODUCTION: + endpoint = undefined; + console.log("TEE: Running in production mode without simulator"); + break; + default: + throw new Error(`Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION`); + } - constructor(endpoint?: string) { this.client = endpoint ? new TappdClient(endpoint) : new TappdClient(); + this.raProvider = new RemoteAttestationProvider(teeMode); + } + + private async generateDeriveKeyAttestation(agentId: string, publicKey: string): Promise { + const deriveKeyData: DeriveKeyAttestationData = { + agentId, + publicKey + } + const reportdata = JSON.stringify(deriveKeyData); + console.log("Generating Remote Attestation Quote for Derive Key..."); + const quote = await this.raProvider.generateAttestation(reportdata); + console.log("Remote Attestation Quote generated successfully!"); + return quote; } + async rawDeriveKey( path: string, subject: string @@ -36,8 +78,9 @@ class DeriveKeyProvider { async deriveEd25519Keypair( path: string, - subject: string - ): Promise { + subject: string, + agentId: string + ): Promise<{ keypair: Keypair, attestation: RemoteAttestationQuote }> { try { if (!path || !subject) { console.error( @@ -55,8 +98,14 @@ class DeriveKeyProvider { const seedArray = new Uint8Array(seed); const keypair = Keypair.fromSeed(seedArray.slice(0, 32)); + // Generate an attestation for the derived key data for public to verify + const attestation = await this.generateDeriveKeyAttestation( + agentId, + keypair.publicKey.toBase58() + ); console.log("Key Derived Successfully!"); - return keypair; + + return { keypair, attestation }; } catch (error) { console.error("Error deriving key:", error); throw error; @@ -65,8 +114,9 @@ class DeriveKeyProvider { async deriveEcdsaKeypair( path: string, - subject: string - ): Promise { + subject: string, + agentId: string + ): Promise<{ keypair: PrivateKeyAccount, attestation: RemoteAttestationQuote }> { try { if (!path || !subject) { console.error( @@ -79,8 +129,15 @@ class DeriveKeyProvider { await this.client.deriveKey(path, subject); const hex = keccak256(deriveKeyResponse.asUint8Array()); const keypair: PrivateKeyAccount = privateKeyToAccount(hex); + + // Generate an attestation for the derived key data for public to verify + const attestation = await this.generateDeriveKeyAttestation( + agentId, + keypair.address + ); console.log("ECDSA Key Derived Successfully!"); - return keypair; + + return { keypair, attestation }; } catch (error) { console.error("Error deriving ecdsa key:", error); throw error; @@ -90,8 +147,9 @@ class DeriveKeyProvider { const deriveKeyProvider: Provider = { get: async (runtime: IAgentRuntime, _message?: Memory, _state?: State) => { - const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); - const provider = new DeriveKeyProvider(endpoint); + const teeMode = runtime.getSetting("TEE_MODE"); + const provider = new DeriveKeyProvider(teeMode); + const agentId = runtime.agentId; try { // Validate wallet configuration if (!runtime.getSetting("WALLET_SECRET_SALT")) { @@ -101,28 +159,27 @@ const deriveKeyProvider: Provider = { return ""; } - let keypair: Keypair; try { const secretSalt = runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; const solanaKeypair = await provider.deriveEd25519Keypair( "/", - secretSalt + secretSalt, + agentId ); const evmKeypair = await provider.deriveEcdsaKeypair( "/", - secretSalt + secretSalt, + agentId ); return JSON.stringify({ - solana: solanaKeypair.publicKey, - evm: evmKeypair.address, + solana: solanaKeypair.keypair.publicKey, + evm: evmKeypair.keypair.address, }); } catch (error) { console.error("Error creating PublicKey:", error); return ""; } - - return keypair; } catch (error) { console.error("Error in derive key provider:", error.message); return `Failed to fetch derive key information: ${error instanceof Error ? error.message : "Unknown error"}`; diff --git a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts index c41ae9bc1f5..a5024b6a283 100644 --- a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -1,22 +1,53 @@ import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; -import { TappdClient } from "@phala/dstack-sdk"; +import { TdxQuoteResponse, TappdClient } from "@phala/dstack-sdk"; +import { RemoteAttestationQuote, TEEMode } from "../types/tee"; class RemoteAttestationProvider { private client: TappdClient; - constructor(endpoint?: string) { + constructor(teeMode?: string) { + let endpoint: string | undefined; + + // Both LOCAL and DOCKER modes use the simulator, just with different endpoints + switch(teeMode) { + case TEEMode.LOCAL: + endpoint = "http://localhost:8090"; + console.log("TEE: Connecting to local simulator at localhost:8090"); + break; + case TEEMode.DOCKER: + endpoint = "http://host.docker.internal:8090"; + console.log("TEE: Connecting to simulator via Docker at host.docker.internal:8090"); + break; + case TEEMode.PRODUCTION: + endpoint = undefined; + console.log("TEE: Running in production mode without simulator"); + break; + default: + throw new Error(`Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION`); + } + this.client = endpoint ? new TappdClient(endpoint) : new TappdClient(); } - async generateAttestation(reportData: string): Promise { + async generateAttestation(reportData: string): Promise { try { - console.log("Generating remote attestation..."); - const tdxQuote = await this.client.tdxQuote(reportData); - console.log("Remote attestation generated successfully!"); - return JSON.stringify(tdxQuote); + console.log("Generating attestation for: ", reportData); + const tdxQuote: TdxQuoteResponse = await this.client.tdxQuote(reportData); + const rtmrs = tdxQuote.replayRtmrs(); + console.log(`rtmr0: ${rtmrs[0]}\nrtmr1: ${rtmrs[1]}\nrtmr2: ${rtmrs[2]}\nrtmr3: ${rtmrs[3]}f`); + const quote: RemoteAttestationQuote = { + quote: tdxQuote.quote, + timestamp: Date.now(), + }; + console.log("Remote attestation quote: ", quote); + return quote; } catch (error) { console.error("Error generating remote attestation:", error); - return `Failed to generate TDX Quote: ${error instanceof Error ? error.message : "Unknown error"}`; + throw new Error( + `Failed to generate TDX Quote: ${ + error instanceof Error ? error.message : "Unknown error" + }` + ); } } } @@ -24,16 +55,21 @@ class RemoteAttestationProvider { // Keep the original provider for backwards compatibility const remoteAttestationProvider: Provider = { get: async (runtime: IAgentRuntime, _message: Memory, _state?: State) => { - const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); - const provider = new RemoteAttestationProvider(endpoint); + const teeMode = runtime.getSetting("TEE_MODE"); + const provider = new RemoteAttestationProvider(teeMode); const agentId = runtime.agentId; try { + console.log("Generating attestation for: ", agentId); const attestation = await provider.generateAttestation(agentId); - return `Your Agent's remote attestation is: ${attestation}`; + return `Your Agent's remote attestation is: ${JSON.stringify(attestation)}`; } catch (error) { console.error("Error in remote attestation provider:", error); - return ""; + throw new Error( + `Failed to generate TDX Quote: ${ + error instanceof Error ? error.message : "Unknown error" + }` + ); } }, }; diff --git a/packages/plugin-tee/src/providers/walletProvider.ts b/packages/plugin-tee/src/providers/walletProvider.ts index 44ea2d163fc..a12ad711929 100644 --- a/packages/plugin-tee/src/providers/walletProvider.ts +++ b/packages/plugin-tee/src/providers/walletProvider.ts @@ -4,6 +4,7 @@ import { Connection, Keypair, PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import NodeCache from "node-cache"; import { DeriveKeyProvider } from "./deriveKeyProvider"; +import { RemoteAttestationQuote } from "../types/tee"; // Provider configuration const PROVIDER_CONFIG = { BIRDEYE_API: "https://public-api.birdeye.so", @@ -35,7 +36,7 @@ interface WalletPortfolio { items: Array; } -interface BirdEyePriceData { +interface _BirdEyePriceData { data: { [key: string]: { price: number; @@ -272,6 +273,9 @@ const walletProvider: Provider = { _message: Memory, _state?: State ): Promise => { + const agentId = runtime.agentId; + const teeMode = runtime.getSetting("TEE_MODE"); + const deriveKeyProvider = new DeriveKeyProvider(teeMode); try { // Validate wallet configuration if (!runtime.getSetting("WALLET_SECRET_SALT")) { @@ -283,13 +287,13 @@ const walletProvider: Provider = { let publicKey: PublicKey; try { - const deriveKeyProvider = new DeriveKeyProvider(); - const derivedKeyPair: Keypair = + const derivedKeyPair: { keypair: Keypair, attestation: RemoteAttestationQuote } = await deriveKeyProvider.deriveEd25519Keypair( "/", - runtime.getSetting("WALLET_SECRET_SALT") + runtime.getSetting("WALLET_SECRET_SALT"), + agentId ); - publicKey = derivedKeyPair.publicKey; + publicKey = derivedKeyPair.keypair.publicKey; console.log("Wallet Public Key: ", publicKey.toBase58()); } catch (error) { console.error("Error creating PublicKey:", error); diff --git a/packages/plugin-tee/src/types/tee.ts b/packages/plugin-tee/src/types/tee.ts index 0cda39740fb..8cdcae0b93a 100644 --- a/packages/plugin-tee/src/types/tee.ts +++ b/packages/plugin-tee/src/types/tee.ts @@ -1,12 +1,11 @@ -export interface DeriveKeyResponse { - key: string; - certificate_chain: string[]; - asUint8Array: (max_length?: number) => Uint8Array; +export enum TEEMode { + OFF = "OFF", + LOCAL = "LOCAL", // For local development with simulator + DOCKER = "DOCKER", // For docker development with simulator + PRODUCTION = "PRODUCTION" // For production without simulator } -export type Hex = `0x${string}`; - -export interface TdxQuoteResponse { - quote: Hex; - event_log: string; -} +export interface RemoteAttestationQuote { + quote: string; + timestamp: number; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a848765db75..a0ce1e04433 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1226,6 +1226,9 @@ importers: '@ai16z/eliza': specifier: workspace:* version: link:../core + '@ai16z/plugin-tee': + specifier: workspace:* + version: link:../plugin-tee '@ai16z/plugin-trustdb': specifier: workspace:* version: link:../plugin-trustdb