From 1995b56c2c519baab18520f03bd04c82be90b8e3 Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 22 Nov 2024 10:53:23 -0600 Subject: [PATCH 1/6] create TEE plugin with derive key and remote attestation providers --- .env.example | 3 ++ packages/plugin-tee/.npmignore | 6 +++ packages/plugin-tee/package.json | 23 ++++++++++ packages/plugin-tee/src/index.ts | 22 ++++++++++ .../src/providers/deriveKeyProvider.ts | 44 +++++++++++++++++++ .../providers/remoteAttestationProvider.ts | 24 ++++++++++ packages/plugin-tee/src/types/tee.ts | 13 ++++++ packages/plugin-tee/tsconfig.json | 8 ++++ packages/plugin-tee/tsup.config.ts | 21 +++++++++ scripts/build.sh | 1 + scripts/docker.sh | 1 + 11 files changed, 166 insertions(+) create mode 100644 packages/plugin-tee/.npmignore create mode 100644 packages/plugin-tee/package.json create mode 100644 packages/plugin-tee/src/index.ts create mode 100644 packages/plugin-tee/src/providers/deriveKeyProvider.ts create mode 100644 packages/plugin-tee/src/providers/remoteAttestationProvider.ts create mode 100644 packages/plugin-tee/src/types/tee.ts create mode 100644 packages/plugin-tee/tsconfig.json create mode 100644 packages/plugin-tee/tsup.config.ts diff --git a/.env.example b/.env.example index c2d1e182193..02b49129f36 100644 --- a/.env.example +++ b/.env.example @@ -91,4 +91,7 @@ STARKNET_ADDRESS= STARKNET_PRIVATE_KEY= STARKNET_RPC_URL= +# TEE Configuration +DSTACK_SIMULATOR_ENDPOINT= +WALLET_SECRET_SALT=secret_salt diff --git a/packages/plugin-tee/.npmignore b/packages/plugin-tee/.npmignore new file mode 100644 index 00000000000..078562eceab --- /dev/null +++ b/packages/plugin-tee/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-tee/package.json b/packages/plugin-tee/package.json new file mode 100644 index 00000000000..0c0ac054e3a --- /dev/null +++ b/packages/plugin-tee/package.json @@ -0,0 +1,23 @@ +{ + "name": "@ai16z/plugin-tee", + "version": "0.1.3", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@ai16z/eliza": "workspace:*", + "@phala/dstack-sdk": "^0.1.4", + "@solana/web3.js": "1.95.4", + "bignumber": "1.1.0", + "bignumber.js": "9.1.2", + "bs58": "^6.0.0", + "tsup": "^8.3.5" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --watch" + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-tee/src/index.ts b/packages/plugin-tee/src/index.ts new file mode 100644 index 00000000000..db4267bc6aa --- /dev/null +++ b/packages/plugin-tee/src/index.ts @@ -0,0 +1,22 @@ +import { Plugin, Action, Evaluator, Provider } from "@ai16z/eliza"; +import { remoteAttestationProvider } from "./providers/remoteAttestationProvider"; +import { deriveKeyProvider } from "./providers/deriveKeyProvider"; + +export const teePlugin: Plugin = { + name: "tee", + description: "TEE plugin with actions to generate remote attestations and derive keys", + actions: [ + /* custom actions */ + ], + evaluators: [ + /* custom evaluators */ + ], + providers: [ + /* custom providers */ + remoteAttestationProvider, + deriveKeyProvider, + ], + services: [ + /* custom services */ + ], +}; \ No newline at end of file diff --git a/packages/plugin-tee/src/providers/deriveKeyProvider.ts b/packages/plugin-tee/src/providers/deriveKeyProvider.ts new file mode 100644 index 00000000000..b3847aa814e --- /dev/null +++ b/packages/plugin-tee/src/providers/deriveKeyProvider.ts @@ -0,0 +1,44 @@ +import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; +import { Keypair } from "@solana/web3.js"; +import crypto from 'crypto'; +import {TappdClient} from '@phala/dstack-sdk' + +const deriveKeyProvider: Provider = { + get: async (runtime: IAgentRuntime, _message?: Memory, _state?: State) => { + const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); + const client = (endpoint) ? new TappdClient(endpoint) : new TappdClient(); + try { + // Validate wallet configuration + if (!runtime.getSetting("WALLET_SECRET_SALT")) { + console.error( + "Wallet secret salt is not configured in settings" + ); + return ""; + } + + let keypair: Keypair; + try { + const secretSalt = runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; + const derivedKey = await client.deriveKey('/', secretSalt); + console.log("Deriving Key in TEE..."); + const uint8ArrayDerivedKey = derivedKey.asUint8Array(); + const hash = crypto.createHash('sha256'); + hash.update(uint8ArrayDerivedKey); + const seed = hash.digest(); + const seedArray = new Uint8Array(seed); + keypair = Keypair.fromSeed(seedArray.slice(0, 32)); + console.log("Key Derived Successfully!"); + } catch (error) { + console.error("Error creating PublicKey:", error); + return ""; + } + + return `Your Agent's public key is: ${keypair.publicKey.toBase58()}`; + } 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"}`; + } + }, +}; + +export { deriveKeyProvider }; diff --git a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts new file mode 100644 index 00000000000..fb4dc8077f3 --- /dev/null +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -0,0 +1,24 @@ +import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; +import {TappdClient} from '@phala/dstack-sdk' + +const remoteAttestationProvider: Provider = { + get: async (runtime: IAgentRuntime, _message: Memory, _state?: State) => { + // TODO: Generate Remote Attestations on Memories and States? + const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); + const client = (endpoint) ? new TappdClient(endpoint) : new TappdClient(); + try { + try { + const tdxQuote = await client.tdxQuote('test'); + return `Your Agent's remote attestation is: ${JSON.stringify(tdxQuote)}`; + } catch (error) { + console.error("Error creating remote attestation:", error); + return ""; + } + } catch (error) { + console.error("Error in remote attestation provider:", error.message); + return `Failed to fetch TDX Quote information: ${error instanceof Error ? error.message : "Unknown error"}`; + } + }, +}; + +export { remoteAttestationProvider }; diff --git a/packages/plugin-tee/src/types/tee.ts b/packages/plugin-tee/src/types/tee.ts new file mode 100644 index 00000000000..369592b3fdc --- /dev/null +++ b/packages/plugin-tee/src/types/tee.ts @@ -0,0 +1,13 @@ + +export interface DeriveKeyResponse { + key: string; + certificate_chain: string[]; + asUint8Array: (max_length?: number) => Uint8Array; +} + +export type Hex = `0x${string}` + +export interface TdxQuoteResponse { + quote: Hex; + event_log: string; +} \ No newline at end of file diff --git a/packages/plugin-tee/tsconfig.json b/packages/plugin-tee/tsconfig.json new file mode 100644 index 00000000000..7541efa69db --- /dev/null +++ b/packages/plugin-tee/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/plugin-tee/tsup.config.ts b/packages/plugin-tee/tsup.config.ts new file mode 100644 index 00000000000..7cadeb6bd04 --- /dev/null +++ b/packages/plugin-tee/tsup.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + // Add other modules you want to externalize + "@phala/dstack-sdk" + ], +}); diff --git a/scripts/build.sh b/scripts/build.sh index 1a93101dcc6..99b2ae80ee3 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -24,6 +24,7 @@ PACKAGES=( "plugin-trustdb" "plugin-solana" "plugin-starknet" + "plugin-tee" "adapter-postgres" "adapter-sqlite" "adapter-sqljs" diff --git a/scripts/docker.sh b/scripts/docker.sh index 66800ca5e63..e159d8e4b65 100755 --- a/scripts/docker.sh +++ b/scripts/docker.sh @@ -44,6 +44,7 @@ case "$1" in "plugin-image-generation" "plugin-node" "plugin-solana" + "plugin-tee" ) # Start building the docker run command From 66637cd244b7e820114783f9547227f00c019740 Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 22 Nov 2024 14:25:54 -0600 Subject: [PATCH 2/6] feat: add wallet provider to use derived key to generate wallet --- agent/package.json | 1 + packages/plugin-tee/package.json | 3 + packages/plugin-tee/src/index.ts | 37 ++- .../src/providers/deriveKeyProvider.ts | 15 +- .../providers/remoteAttestationProvider.ts | 11 +- .../src/providers/walletProvider.ts | 311 ++++++++++++++++++ packages/plugin-tee/src/types/tee.ts | 7 +- packages/plugin-tee/tsup.config.ts | 9 +- 8 files changed, 361 insertions(+), 33 deletions(-) create mode 100644 packages/plugin-tee/src/providers/walletProvider.ts diff --git a/agent/package.json b/agent/package.json index bd8967d569d..3001ae45706 100644 --- a/agent/package.json +++ b/agent/package.json @@ -25,6 +25,7 @@ "@ai16z/plugin-node": "workspace:*", "@ai16z/plugin-solana": "workspace:*", "@ai16z/plugin-starknet": "workspace:*", + "@ai16z/plugin-tee": "workspace:*", "readline": "^1.3.0", "ws": "^8.18.0", "yargs": "17.7.2" diff --git a/packages/plugin-tee/package.json b/packages/plugin-tee/package.json index 0c0ac054e3a..c48002ab63c 100644 --- a/packages/plugin-tee/package.json +++ b/packages/plugin-tee/package.json @@ -8,9 +8,12 @@ "@ai16z/eliza": "workspace:*", "@phala/dstack-sdk": "^0.1.4", "@solana/web3.js": "1.95.4", + "@solana/spl-token": "0.4.9", "bignumber": "1.1.0", "bignumber.js": "9.1.2", "bs58": "^6.0.0", + "node-cache": "5.1.2", + "pumpdotfun-sdk": "1.3.2", "tsup": "^8.3.5" }, "scripts": { diff --git a/packages/plugin-tee/src/index.ts b/packages/plugin-tee/src/index.ts index db4267bc6aa..2a6ec057ad2 100644 --- a/packages/plugin-tee/src/index.ts +++ b/packages/plugin-tee/src/index.ts @@ -1,22 +1,25 @@ import { Plugin, Action, Evaluator, Provider } from "@ai16z/eliza"; import { remoteAttestationProvider } from "./providers/remoteAttestationProvider"; import { deriveKeyProvider } from "./providers/deriveKeyProvider"; +import { walletProvider } from "./providers/walletProvider"; export const teePlugin: Plugin = { - name: "tee", - description: "TEE plugin with actions to generate remote attestations and derive keys", - actions: [ - /* custom actions */ - ], - evaluators: [ - /* custom evaluators */ - ], - providers: [ - /* custom providers */ - remoteAttestationProvider, - deriveKeyProvider, - ], - services: [ - /* custom services */ - ], -}; \ No newline at end of file + name: "tee", + description: + "TEE plugin with actions to generate remote attestations and derive keys", + actions: [ + /* custom actions */ + ], + evaluators: [ + /* custom evaluators */ + ], + providers: [ + /* custom providers */ + remoteAttestationProvider, + deriveKeyProvider, + walletProvider, + ], + services: [ + /* custom services */ + ], +}; diff --git a/packages/plugin-tee/src/providers/deriveKeyProvider.ts b/packages/plugin-tee/src/providers/deriveKeyProvider.ts index b3847aa814e..cbb4a7ba5d4 100644 --- a/packages/plugin-tee/src/providers/deriveKeyProvider.ts +++ b/packages/plugin-tee/src/providers/deriveKeyProvider.ts @@ -1,12 +1,12 @@ import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; import { Keypair } from "@solana/web3.js"; -import crypto from 'crypto'; -import {TappdClient} from '@phala/dstack-sdk' +import crypto from "crypto"; +import { TappdClient } from "@phala/dstack-sdk"; const deriveKeyProvider: Provider = { get: async (runtime: IAgentRuntime, _message?: Memory, _state?: State) => { const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); - const client = (endpoint) ? new TappdClient(endpoint) : new TappdClient(); + const client = endpoint ? new TappdClient(endpoint) : new TappdClient(); try { // Validate wallet configuration if (!runtime.getSetting("WALLET_SECRET_SALT")) { @@ -18,11 +18,12 @@ const deriveKeyProvider: Provider = { let keypair: Keypair; try { - const secretSalt = runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; - const derivedKey = await client.deriveKey('/', secretSalt); + const secretSalt = + runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; + const derivedKey = await client.deriveKey("/", secretSalt); console.log("Deriving Key in TEE..."); const uint8ArrayDerivedKey = derivedKey.asUint8Array(); - const hash = crypto.createHash('sha256'); + const hash = crypto.createHash("sha256"); hash.update(uint8ArrayDerivedKey); const seed = hash.digest(); const seedArray = new Uint8Array(seed); @@ -33,7 +34,7 @@ const deriveKeyProvider: Provider = { return ""; } - return `Your Agent's public key is: ${keypair.publicKey.toBase58()}`; + 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 fb4dc8077f3..1f6b66eb8ef 100644 --- a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -1,21 +1,24 @@ import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; -import {TappdClient} from '@phala/dstack-sdk' +import { TappdClient } from "@phala/dstack-sdk"; const remoteAttestationProvider: Provider = { get: async (runtime: IAgentRuntime, _message: Memory, _state?: State) => { // TODO: Generate Remote Attestations on Memories and States? const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); - const client = (endpoint) ? new TappdClient(endpoint) : new TappdClient(); + const client = endpoint ? new TappdClient(endpoint) : new TappdClient(); try { try { - const tdxQuote = await client.tdxQuote('test'); + const tdxQuote = await client.tdxQuote("test"); return `Your Agent's remote attestation is: ${JSON.stringify(tdxQuote)}`; } catch (error) { console.error("Error creating remote attestation:", error); return ""; } } catch (error) { - console.error("Error in remote attestation provider:", error.message); + console.error( + "Error in remote attestation provider:", + error.message + ); return `Failed to fetch TDX Quote information: ${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 new file mode 100644 index 00000000000..f0dc21fc268 --- /dev/null +++ b/packages/plugin-tee/src/providers/walletProvider.ts @@ -0,0 +1,311 @@ +import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; +import { Connection, Keypair, PublicKey } from "@solana/web3.js"; +import BigNumber from "bignumber.js"; +import NodeCache from "node-cache"; +import { deriveKeyProvider } from "./deriveKeyProvider"; +// Provider configuration +const PROVIDER_CONFIG = { + BIRDEYE_API: "https://public-api.birdeye.so", + MAX_RETRIES: 3, + RETRY_DELAY: 2000, + DEFAULT_RPC: "https://api.mainnet-beta.solana.com", + TOKEN_ADDRESSES: { + SOL: "So11111111111111111111111111111111111111112", + BTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", + ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + }, +}; + +export interface Item { + name: string; + address: string; + symbol: string; + decimals: number; + balance: string; + uiAmount: string; + priceUsd: string; + valueUsd: string; + valueSol?: string; +} + +interface WalletPortfolio { + totalUsd: string; + totalSol?: string; + items: Array; +} + +interface BirdEyePriceData { + data: { + [key: string]: { + price: number; + priceChange24h: number; + }; + }; +} + +interface Prices { + solana: { usd: string }; + bitcoin: { usd: string }; + ethereum: { usd: string }; +} + +export class WalletProvider { + private cache: NodeCache; + + constructor( + private connection: Connection, + private walletPublicKey: PublicKey + ) { + this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes + } + + private async fetchWithRetry( + runtime, + url: string, + options: RequestInit = {} + ): Promise { + let lastError: Error; + + for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { + try { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + console.log("API KEY: ", apiKey); + const response = await fetch(url, { + ...options, + headers: { + Accept: "application/json", + "x-chain": "solana", + "X-API-KEY": apiKey || "", + ...options.headers, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `HTTP error! status: ${response.status}, message: ${errorText}` + ); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error(`Attempt ${i + 1} failed:`, error); + lastError = error; + if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { + const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + } + } + + console.error( + "All attempts failed. Throwing the last error:", + lastError + ); + throw lastError; + } + + async fetchPortfolioValue(runtime): Promise { + try { + const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; + const cachedValue = this.cache.get(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPortfolioValue"); + return cachedValue; + } + console.log("Cache miss for fetchPortfolioValue"); + + const walletData = await this.fetchWithRetry( + runtime, + `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}` + ); + + if (!walletData?.success || !walletData?.data) { + console.error("No portfolio data available", walletData); + throw new Error("No portfolio data available"); + } + + const data = walletData.data; + const totalUsd = new BigNumber(data.totalUsd.toString()); + const prices = await this.fetchPrices(runtime); + const solPriceInUSD = new BigNumber(prices.solana.usd.toString()); + + const items = data.items.map((item: any) => ({ + ...item, + valueSol: new BigNumber(item.valueUsd || 0) + .div(solPriceInUSD) + .toFixed(6), + name: item.name || "Unknown", + symbol: item.symbol || "Unknown", + priceUsd: item.priceUsd || "0", + valueUsd: item.valueUsd || "0", + })); + + const totalSol = totalUsd.div(solPriceInUSD); + const portfolio = { + totalUsd: totalUsd.toString(), + totalSol: totalSol.toFixed(6), + items: items.sort((a, b) => + new BigNumber(b.valueUsd) + .minus(new BigNumber(a.valueUsd)) + .toNumber() + ), + }; + this.cache.set(cacheKey, portfolio); + return portfolio; + } catch (error) { + console.error("Error fetching portfolio:", error); + throw error; + } + } + + async fetchPrices(runtime): Promise { + try { + const cacheKey = "prices"; + const cachedValue = this.cache.get(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPrices"); + return cachedValue; + } + console.log("Cache miss for fetchPrices"); + + const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; + const tokens = [SOL, BTC, ETH]; + const prices: Prices = { + solana: { usd: "0" }, + bitcoin: { usd: "0" }, + ethereum: { usd: "0" }, + }; + + for (const token of tokens) { + const response = await this.fetchWithRetry( + runtime, + `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, + { + headers: { + "x-chain": "solana", + }, + } + ); + + if (response?.data?.value) { + const price = response.data.value.toString(); + prices[ + token === SOL + ? "solana" + : token === BTC + ? "bitcoin" + : "ethereum" + ].usd = price; + } else { + console.warn(`No price data available for token: ${token}`); + } + } + + this.cache.set(cacheKey, prices); + return prices; + } catch (error) { + console.error("Error fetching prices:", error); + throw error; + } + } + + formatPortfolio( + runtime, + portfolio: WalletPortfolio, + prices: Prices + ): string { + let output = `${runtime.character.description}\n`; + output += `Wallet Address: ${this.walletPublicKey.toBase58()}\n\n`; + + const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); + const totalSolFormatted = portfolio.totalSol; + + output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL)\n\n`; + output += "Token Balances:\n"; + + const nonZeroItems = portfolio.items.filter((item) => + new BigNumber(item.uiAmount).isGreaterThan(0) + ); + + if (nonZeroItems.length === 0) { + output += "No tokens found with non-zero balance\n"; + } else { + for (const item of nonZeroItems) { + const valueUsd = new BigNumber(item.valueUsd).toFixed(2); + output += `${item.name} (${item.symbol}): ${new BigNumber( + item.uiAmount + ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL)\n`; + } + } + + output += "\nMarket Prices:\n"; + output += `SOL: $${new BigNumber(prices.solana.usd).toFixed(2)}\n`; + output += `BTC: $${new BigNumber(prices.bitcoin.usd).toFixed(2)}\n`; + output += `ETH: $${new BigNumber(prices.ethereum.usd).toFixed(2)}\n`; + + return output; + } + + async getFormattedPortfolio(runtime): Promise { + try { + const [portfolio, prices] = await Promise.all([ + this.fetchPortfolioValue(runtime), + this.fetchPrices(runtime), + ]); + + return this.formatPortfolio(runtime, portfolio, prices); + } catch (error) { + console.error("Error generating portfolio report:", error); + return "Unable to fetch wallet information. Please try again later."; + } + } +} + +const walletProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + try { + // Validate wallet configuration + if (!runtime.getSetting("WALLET_SECRET_SALT")) { + console.error( + "Wallet secret salt is not configured in settings" + ); + return ""; + } + + let publicKey: PublicKey; + try { + const derivedKeyPair: Keypair = await deriveKeyProvider.get( + runtime, + _message, + _state + ); + publicKey = derivedKeyPair.publicKey; + console.log("Wallet Public Key: ", publicKey.toBase58()); + } catch (error) { + console.error("Error creating PublicKey:", error); + return ""; + } + + const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC); + const provider = new WalletProvider(connection, publicKey); + + const porfolio = await provider.getFormattedPortfolio(runtime); + return porfolio; + } catch (error) { + console.error("Error in wallet provider:", error.message); + return `Failed to fetch wallet information: ${error instanceof Error ? error.message : "Unknown error"}`; + } + }, +}; + +// Module exports +export { walletProvider }; diff --git a/packages/plugin-tee/src/types/tee.ts b/packages/plugin-tee/src/types/tee.ts index 369592b3fdc..0cda39740fb 100644 --- a/packages/plugin-tee/src/types/tee.ts +++ b/packages/plugin-tee/src/types/tee.ts @@ -1,13 +1,12 @@ - export interface DeriveKeyResponse { key: string; certificate_chain: string[]; asUint8Array: (max_length?: number) => Uint8Array; } - -export type Hex = `0x${string}` + +export type Hex = `0x${string}`; export interface TdxQuoteResponse { quote: Hex; event_log: string; -} \ No newline at end of file +} diff --git a/packages/plugin-tee/tsup.config.ts b/packages/plugin-tee/tsup.config.ts index 7cadeb6bd04..b94c126be77 100644 --- a/packages/plugin-tee/tsup.config.ts +++ b/packages/plugin-tee/tsup.config.ts @@ -16,6 +16,13 @@ export default defineConfig({ "http", "agentkeepalive", // Add other modules you want to externalize - "@phala/dstack-sdk" + "@phala/dstack-sdk", + "safe-buffer", + "base-x", + "bs58", + "borsh", + "@solana/buffer-layout", + "stream", + "buffer", ], }); From aa5d009df083840a36ef55ff70e3619cae789f9a Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 22 Nov 2024 15:53:26 -0600 Subject: [PATCH 3/6] chore: remove log print of api key --- packages/plugin-tee/src/providers/walletProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/plugin-tee/src/providers/walletProvider.ts b/packages/plugin-tee/src/providers/walletProvider.ts index f0dc21fc268..b6d355d702a 100644 --- a/packages/plugin-tee/src/providers/walletProvider.ts +++ b/packages/plugin-tee/src/providers/walletProvider.ts @@ -69,7 +69,6 @@ export class WalletProvider { for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { try { const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - console.log("API KEY: ", apiKey); const response = await fetch(url, { ...options, headers: { From 13f921afcfdc49361149c409b8ab7b4de580c213 Mon Sep 17 00:00:00 2001 From: hashwarlock Date: Mon, 25 Nov 2024 19:15:14 -0600 Subject: [PATCH 4/6] feat: update dockerfile and add docker-compose yaml file to deploy in TEE --- Dockerfile | 16 +++++++++++++--- docker-compose.yaml | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile index d7bfcd15143..e48a87ab1c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,10 +21,20 @@ RUN pnpm i ADD packages /app/packages RUN pnpm i -# Add the environment variables +# Add agent +ADD agent /app/agent +RUN pnpm i + +# Add the scripts and characters ADD scripts /app/scripts ADD characters /app/characters -ADD .env /app/.env + +# Build the project +RUN pnpm build + +EXPOSE 3000 + +ENV PORT=3000 # Command to run the container -CMD ["tail", "-f", "/dev/null"] \ No newline at end of file +CMD ["tail", "-f", "/dev/null"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000000..4d0758d668a --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,40 @@ +services: + tee: + command: ["pnpm", "start"] + image: hashwarlock/tee-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: + - OPENAI_API_KEY= + - REDPILL_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_PASSWORD= + - TWITTER_EMAIL= + - X_SERVER_URL=https://api.red-pill.ai/v1 + - BIRDEYE_API_KEY= + - SOL_ADDRESS=So11111111111111111111111111111111111111112 + - SLIPPAGE=1 + - RPC_URL=https://api.mainnet-beta.solana.com + - HELIUS_API_KEY= + - SERVER_PORT=3000 + - WALLET_SECRET_SALT=secret_salt + ports: + - "3000:80" + restart: always + +volumes: + tee: From 02647806546bd558ce9eabaa33f3a8c100a17fa4 Mon Sep 17 00:00:00 2001 From: hashwarlock Date: Mon, 25 Nov 2024 23:04:48 -0600 Subject: [PATCH 5/6] fix: reduce docker image size from 11.1GB to 5.79GB --- Dockerfile | 83 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index e48a87ab1c1..07ce2382f75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,17 @@ -FROM node:23.1.0 -# Install pnpm globally -RUN npm install -g pnpm@9.4.0 +# Stage 1: Build dependencies in a temporary stage +FROM node:23.1.0 AS builder + +# Install required global dependencies +RUN apt-get update && apt-get install -y \ + python3 \ + build-essential \ + git \ + curl \ + sqlite3 && \ + apt-get clean \ + && npm install -g pnpm@9.4.0 -# Set the working directory +# Set working directory WORKDIR /app # Add configuration files and install dependencies @@ -11,30 +20,64 @@ ADD package.json /app/package.json ADD .npmrc /app/.npmrc ADD tsconfig.json /app/tsconfig.json ADD pnpm-lock.yaml /app/pnpm-lock.yaml -RUN pnpm i -# Add the documentation -ADD docs /app/docs -RUN pnpm i +# Install dependencies +RUN pnpm install -# Add the rest of the application code +# Copy source code +ADD docs /app/docs ADD packages /app/packages -RUN pnpm i - -# Add agent -ADD agent /app/agent -RUN pnpm i - -# Add the scripts and characters ADD scripts /app/scripts ADD characters /app/characters +ADD agent /app/agent + +# Add dependencies to workspace root +RUN pnpm add -w -D ts-node typescript @types/node + +WORKDIR /app/packages/agent + +# Add dependencies to the agent package specifically +RUN pnpm add -D ts-node typescript @types/node --filter "@ai16z/agent" -# Build the project +WORKDIR /app/packages/core +RUN pnpm add -D ts-node typescript @types/node --filter "@ai16z/eliza" + +WORKDIR /app + +# Optional: build step if using TypeScript or other build process RUN pnpm build +# Stage 2: Production image +FROM node:23.1.0 + +# Install dependencies required for the final runtime +RUN apt-get update && apt-get install -y \ + python3 \ + build-essential \ + git \ + curl \ + sqlite3 && \ + apt-get clean \ + && npm install -g pnpm@9.4.0 + +# Set working directory +WORKDIR /app + +# Copy built files from the builder stage +COPY --from=builder /app /app + +# install playwright +RUN pnpm exec playwright install +RUN pnpm exec playwright install-deps + +# Expose application port if running a web server EXPOSE 3000 -ENV PORT=3000 +# Add health check to ensure the app is running +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s CMD curl -f http://localhost:3000 || exit 1 + +# Set environment variables to configure runtime model settings +ENV NODE_ENV=production -# Command to run the container -CMD ["tail", "-f", "/dev/null"] +# Default command to run the application +CMD ["pnpm", "start"] From 147adde8d6a7596d831064dc0be2ca0872c42a7c Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Wed, 27 Nov 2024 11:32:53 -0600 Subject: [PATCH 6/6] feat: add class to remote attestation and derive key to call provider functions --- agent/src/index.ts | 2 + packages/plugin-tee/README.md | 52 +++++++++++++++++++ packages/plugin-tee/src/index.ts | 2 +- .../src/providers/deriveKeyProvider.ts | 46 ++++++++++++---- .../providers/remoteAttestationProvider.ts | 44 ++++++++++------ 5 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 packages/plugin-tee/README.md diff --git a/agent/src/index.ts b/agent/src/index.ts index cb580eed776..4f763f97bf5 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -25,6 +25,7 @@ import { import { bootstrapPlugin } from "@ai16z/plugin-bootstrap"; import { solanaPlugin } from "@ai16z/plugin-solana"; import { nodePlugin } from "@ai16z/plugin-node"; +import { teePlugin } from "@ai16z/plugin-tee"; import Database from "better-sqlite3"; import fs from "fs"; import readline from "readline"; @@ -249,6 +250,7 @@ export function createAgent( bootstrapPlugin, nodePlugin, character.settings.secrets?.WALLET_PUBLIC_KEY ? solanaPlugin : null, + teePlugin, ].filter(Boolean), providers: [], actions: [], diff --git a/packages/plugin-tee/README.md b/packages/plugin-tee/README.md new file mode 100644 index 00000000000..917e3b93bf1 --- /dev/null +++ b/packages/plugin-tee/README.md @@ -0,0 +1,52 @@ +# Plugin TEE + +A plugin for handling Trusted Execution Environment (TEE) operations. + +## Providers + +This plugin includes several providers for handling different TEE-related operations. + +### DeriveKeyProvider + +The `DeriveKeyProvider` allows for secure key derivation within a TEE environment. + +#### Usage + +```typescript +import { DeriveKeyProvider } from "@ai16z/plugin-tee"; +// Initialize the provider +const provider = new DeriveKeyProvider(); +// Derive a key +try { + const keypair = await provider.deriveKey("/path/to/derive", "subject-identifier"); + // keypair can now be used for cryptographic operations +} catch (error) { + console.error("Key derivation failed:", error); +} +``` + +### RemoteAttestationProvider + +The `RemoteAttestationProvider` allows for generating a remote attestation within a TEE environment. + +#### Usage + +```typescript +const provider = new RemoteAttestationProvider(); + +try { + const attestation = await provider.generateAttestation("your-report-data"); + console.log("Attestation:", attestation); +} catch (error) { + console.error("Failed to generate attestation:", error); +} +``` + +### Configuration + +When using the provider through the runtime environment, ensure the following settings are configured: + +```env +DSTACK_SIMULATOR_ENDPOINT="your-endpoint-url" # Optional, for simulator purposes if testing on mac or windows +WALLET_SECRET_SALT=your-secret-salt // Required to single agent deployments +``` diff --git a/packages/plugin-tee/src/index.ts b/packages/plugin-tee/src/index.ts index 2a6ec057ad2..0a3c1e6adf4 100644 --- a/packages/plugin-tee/src/index.ts +++ b/packages/plugin-tee/src/index.ts @@ -1,4 +1,4 @@ -import { Plugin, Action, Evaluator, Provider } from "@ai16z/eliza"; +import { Plugin } from "@ai16z/eliza"; import { remoteAttestationProvider } from "./providers/remoteAttestationProvider"; import { deriveKeyProvider } from "./providers/deriveKeyProvider"; import { walletProvider } from "./providers/walletProvider"; diff --git a/packages/plugin-tee/src/providers/deriveKeyProvider.ts b/packages/plugin-tee/src/providers/deriveKeyProvider.ts index cbb4a7ba5d4..0e7cf9ac833 100644 --- a/packages/plugin-tee/src/providers/deriveKeyProvider.ts +++ b/packages/plugin-tee/src/providers/deriveKeyProvider.ts @@ -3,10 +3,42 @@ import { Keypair } from "@solana/web3.js"; import crypto from "crypto"; import { TappdClient } from "@phala/dstack-sdk"; +class DeriveKeyProvider { + private client: TappdClient; + + constructor(endpoint?: string) { + this.client = endpoint ? new TappdClient(endpoint) : new TappdClient(); + } + + async deriveKey(path: string, subject: string): Promise { + try { + if (!path || !subject) { + console.error("Path and Subject are required for key derivation"); + } + + console.log("Deriving Key in TEE..."); + const derivedKey = await this.client.deriveKey(path, subject); + const uint8ArrayDerivedKey = derivedKey.asUint8Array(); + + const hash = crypto.createHash("sha256"); + hash.update(uint8ArrayDerivedKey); + const seed = hash.digest(); + const seedArray = new Uint8Array(seed); + const keypair = Keypair.fromSeed(seedArray.slice(0, 32)); + + console.log("Key Derived Successfully!"); + return keypair; + } catch (error) { + console.error("Error deriving key:", error); + throw error; + } + } +} + const deriveKeyProvider: Provider = { get: async (runtime: IAgentRuntime, _message?: Memory, _state?: State) => { const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); - const client = endpoint ? new TappdClient(endpoint) : new TappdClient(); + const provider = new DeriveKeyProvider(endpoint); try { // Validate wallet configuration if (!runtime.getSetting("WALLET_SECRET_SALT")) { @@ -20,15 +52,7 @@ const deriveKeyProvider: Provider = { try { const secretSalt = runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; - const derivedKey = await client.deriveKey("/", secretSalt); - console.log("Deriving Key in TEE..."); - const uint8ArrayDerivedKey = derivedKey.asUint8Array(); - const hash = crypto.createHash("sha256"); - hash.update(uint8ArrayDerivedKey); - const seed = hash.digest(); - const seedArray = new Uint8Array(seed); - keypair = Keypair.fromSeed(seedArray.slice(0, 32)); - console.log("Key Derived Successfully!"); + return await provider.deriveKey("/", secretSalt); } catch (error) { console.error("Error creating PublicKey:", error); return ""; @@ -42,4 +66,4 @@ const deriveKeyProvider: Provider = { }, }; -export { deriveKeyProvider }; +export { deriveKeyProvider, DeriveKeyProvider }; diff --git a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts index 1f6b66eb8ef..d8076afcafb 100644 --- a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -1,27 +1,41 @@ import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; import { TappdClient } from "@phala/dstack-sdk"; +class RemoteAttestationProvider { + private client: TappdClient; + + constructor(endpoint?: string) { + this.client = endpoint ? new TappdClient(endpoint) : new TappdClient(); + } + + 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); + } catch (error) { + console.error("Error generating remote attestation:", error); + return `Failed to generate TDX Quote: ${error instanceof Error ? error.message : "Unknown error"}`; + } + } +} + +// Keep the original provider for backwards compatibility const remoteAttestationProvider: Provider = { get: async (runtime: IAgentRuntime, _message: Memory, _state?: State) => { - // TODO: Generate Remote Attestations on Memories and States? const endpoint = runtime.getSetting("DSTACK_SIMULATOR_ENDPOINT"); - const client = endpoint ? new TappdClient(endpoint) : new TappdClient(); + const provider = new RemoteAttestationProvider(endpoint); + const agentId = runtime.agentId; + try { - try { - const tdxQuote = await client.tdxQuote("test"); - return `Your Agent's remote attestation is: ${JSON.stringify(tdxQuote)}`; - } catch (error) { - console.error("Error creating remote attestation:", error); - return ""; - } + const attestation = await provider.generateAttestation(agentId); + return `Your Agent's remote attestation is: ${attestation}`; } catch (error) { - console.error( - "Error in remote attestation provider:", - error.message - ); - return `Failed to fetch TDX Quote information: ${error instanceof Error ? error.message : "Unknown error"}`; + console.error("Error in remote attestation provider:", error); + return ""; } }, }; -export { remoteAttestationProvider }; +export { remoteAttestationProvider, RemoteAttestationProvider };