From 9c31cd72ea4314f47c59c2be70a3d968d01d067a Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 3 Jan 2025 15:39:43 -0600 Subject: [PATCH 1/5] feat: add remote attestation action and upload to proof[.]t16z[.]com explorer --- agent/src/index.ts | 2 +- packages/plugin-tee/package.json | 2 +- .../src/actions/remoteAttestation.ts | 104 ++++++++++++++++++ packages/plugin-tee/src/index.ts | 2 + .../providers/remoteAttestationProvider.ts | 9 +- pnpm-lock.yaml | 10 +- 6 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 packages/plugin-tee/src/actions/remoteAttestation.ts diff --git a/agent/src/index.ts b/agent/src/index.ts index d6840e7e9ff..49e91d7bdfe 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -572,7 +572,7 @@ export async function createAgent( ] : []), ...(teeMode !== TEEMode.OFF && walletSecretSalt - ? [teePlugin, solanaPlugin] + ? [teePlugin] : []), getSecret(character, "COINBASE_API_KEY") && getSecret(character, "COINBASE_PRIVATE_KEY") && diff --git a/packages/plugin-tee/package.json b/packages/plugin-tee/package.json index ac931632d3a..5ce404fbc44 100644 --- a/packages/plugin-tee/package.json +++ b/packages/plugin-tee/package.json @@ -6,7 +6,7 @@ "types": "dist/index.d.ts", "dependencies": { "@elizaos/core": "workspace:*", - "@phala/dstack-sdk": "0.1.6", + "@phala/dstack-sdk": "0.1.7", "@solana/spl-token": "0.4.9", "@solana/web3.js": "1.95.8", "bignumber": "1.1.0", diff --git a/packages/plugin-tee/src/actions/remoteAttestation.ts b/packages/plugin-tee/src/actions/remoteAttestation.ts new file mode 100644 index 00000000000..23d944cf291 --- /dev/null +++ b/packages/plugin-tee/src/actions/remoteAttestation.ts @@ -0,0 +1,104 @@ +import type { IAgentRuntime, Memory, State, HandlerCallback } from "@elizaos/core"; +import { RemoteAttestationProvider } from "../providers/remoteAttestationProvider"; + +function hexToUint8Array(hex: string) { + hex = hex.trim(); + if (!hex) { + throw new Error("Invalid hex string"); + } + if (hex.startsWith("0x")) { + hex = hex.substring(2); + } + if (hex.length % 2 !== 0) { + throw new Error("Invalid hex string"); + } + + const array = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + const byte = parseInt(hex.slice(i, i + 2), 16); + if (isNaN(byte)) { + throw new Error("Invalid hex string"); + } + array[i / 2] = byte; + } + return array; +} + +async function uploadUint8Array(data: Uint8Array) { + const blob = new Blob([data], { type: "application/octet-stream" }); + const file = new File([blob], "quote.bin", { + type: "application/octet-stream", + }); + const formData = new FormData(); + formData.append("file", file); + + return await fetch("https://proof.t16z.com/api/upload", { + method: "POST", + body: formData, + }); +} + +export const remoteAttestationAction = { + name: "REMOTE_ATTESTATION", + similes: ["REMOTE_ATTESTATION", "TEE_REMOTE_ATTESTATION", "TEE_ATTESTATION"], + description: "Generate a remote attestation to prove that the agent is running in a TEE", + handler: async ( + runtime: IAgentRuntime, + _message: Memory, + _state: State, + _options: { [key: string]: unknown }, + callback: HandlerCallback, + ) => { + try { + // Get the remote attestation of the agentId + const agentId = runtime.agentId; + const agentName = runtime.character.name; + const teeMode = runtime.getSetting("TEE_MODE"); + const provider = new RemoteAttestationProvider(teeMode); + const attestation = await provider.generateAttestation(agentId, 'raw'); + const attestationData = hexToUint8Array(attestation.quote); + const response = await uploadUint8Array(attestationData); + const data = await response.json(); + callback({ + text: `Uploading attestation to proof.t16z.com...`, + action: "NONE", + }); + callback({ + text: `Attestation report for ${agentName}: https://proof.t16z.com/reports/${data.checksum}`, + action: "NONE", + }); + return true; + } catch (error) { + console.error("Failed to fetch remote attestation: ", error); + return false; + } + }, + validate: async (_runtime: IAgentRuntime) => { + return true; + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "If you are running in a TEE, generate a remote attestation", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'm running in a TEE, generate a remote attestation", + action: "REMOTE_ATTESTATION", + }, + }, + { + user: "{{user2}}", + content: { + text: `Sure thing, here is my attestation report + https://proof.t16z.com/reports/a0fd21c0602374388addb2f5272ab61008f5adf9c9b9a85978b799accb9c3442 + `, + }, + }, + ], + ], +}; \ No newline at end of file diff --git a/packages/plugin-tee/src/index.ts b/packages/plugin-tee/src/index.ts index 868078b6f5d..4ebc26b1b7e 100644 --- a/packages/plugin-tee/src/index.ts +++ b/packages/plugin-tee/src/index.ts @@ -1,6 +1,7 @@ import { Plugin } from "@elizaos/core"; import { remoteAttestationProvider } from "./providers/remoteAttestationProvider"; import { deriveKeyProvider } from "./providers/deriveKeyProvider"; +import { remoteAttestationAction } from "./actions/remoteAttestation"; export { DeriveKeyProvider } from "./providers/deriveKeyProvider"; export { RemoteAttestationProvider } from "./providers/remoteAttestationProvider"; @@ -12,6 +13,7 @@ export const teePlugin: Plugin = { "TEE plugin with actions to generate remote attestations and derive keys", actions: [ /* custom actions */ + remoteAttestationAction, ], evaluators: [ /* custom evaluators */ diff --git a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts index 1e13c56092c..dc7d5989b02 100644 --- a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -1,5 +1,5 @@ import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -import { TdxQuoteResponse, TappdClient } from "@phala/dstack-sdk"; +import { TdxQuoteResponse, TappdClient, TdxQuoteHashAlgorithms } from "@phala/dstack-sdk"; import { RemoteAttestationQuote, TEEMode } from "../types/tee"; class RemoteAttestationProvider { @@ -38,12 +38,13 @@ class RemoteAttestationProvider { } async generateAttestation( - reportData: string + reportData: string, + hashAlgorithm?: TdxQuoteHashAlgorithms ): Promise { try { console.log("Generating attestation for: ", reportData); const tdxQuote: TdxQuoteResponse = - await this.client.tdxQuote(reportData); + await this.client.tdxQuote(reportData, hashAlgorithm); const rtmrs = tdxQuote.replayRtmrs(); console.log( `rtmr0: ${rtmrs[0]}\nrtmr1: ${rtmrs[1]}\nrtmr2: ${rtmrs[2]}\nrtmr3: ${rtmrs[3]}f` @@ -74,7 +75,7 @@ const remoteAttestationProvider: Provider = { try { console.log("Generating attestation for: ", agentId); - const attestation = await provider.generateAttestation(agentId); + const attestation = await provider.generateAttestation(agentId, 'raw'); return `Your Agent's remote attestation is: ${JSON.stringify(attestation)}`; } catch (error) { console.error("Error in remote attestation provider:", error); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a4683f236c..766dca2ca31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1773,8 +1773,8 @@ importers: specifier: workspace:* version: link:../core '@phala/dstack-sdk': - specifier: 0.1.6 - version: 0.1.6(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + specifier: 0.1.7 + version: 0.1.7(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) '@solana/spl-token': specifier: 0.4.9 version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) @@ -6218,8 +6218,8 @@ packages: resolution: {integrity: sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==} engines: {node: '>=10.12.0'} - '@phala/dstack-sdk@0.1.6': - resolution: {integrity: sha512-/JNlCDvgQmqAs+3N9qbRjqQdm4UCd1iYmkjH7cE7ejwWcoF4b4bSikiQdMK+fQ3be8T7FJupjWw52ysHWsnwmQ==} + '@phala/dstack-sdk@0.1.7': + resolution: {integrity: sha512-/+8S6XpAnN9X6pCiA7eBD+QtEWOtYhlN7Osrf9K59G6E8q6SdXUbJzuNpNBHtlZV+Pm7zm4zOhnwnpvShLD0Xg==} engines: {node: '>=18.0.0'} '@pinata/sdk@2.1.0': @@ -26279,7 +26279,7 @@ snapshots: tslib: 2.8.1 webcrypto-core: 1.8.1 - '@phala/dstack-sdk@0.1.6(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@phala/dstack-sdk@0.1.7(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': optionalDependencies: viem: 2.21.54(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: From f5034bef8a97f844083534867ad8ab5df2f2274c Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 3 Jan 2025 17:30:57 -0600 Subject: [PATCH 2/5] fix: attestation from double post --- .../src/actions/remoteAttestation.ts | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/plugin-tee/src/actions/remoteAttestation.ts b/packages/plugin-tee/src/actions/remoteAttestation.ts index 23d944cf291..a4f3be090c4 100644 --- a/packages/plugin-tee/src/actions/remoteAttestation.ts +++ b/packages/plugin-tee/src/actions/remoteAttestation.ts @@ -1,5 +1,6 @@ import type { IAgentRuntime, Memory, State, HandlerCallback } from "@elizaos/core"; import { RemoteAttestationProvider } from "../providers/remoteAttestationProvider"; +import { File } from "formdata-node"; function hexToUint8Array(hex: string) { hex = hex.trim(); @@ -33,9 +34,12 @@ async function uploadUint8Array(data: Uint8Array) { formData.append("file", file); return await fetch("https://proof.t16z.com/api/upload", { - method: "POST", - body: formData, - }); + method: "POST", + headers: { + "Content-type": "multipart/form-data", + }, + body: formData, + }); } export const remoteAttestationAction = { @@ -87,18 +91,10 @@ export const remoteAttestationAction = { { user: "{{user2}}", content: { - text: "I'm running in a TEE, generate a remote attestation", + text: "One second, let me generate and upload my attestation report", action: "REMOTE_ATTESTATION", }, - }, - { - user: "{{user2}}", - content: { - text: `Sure thing, here is my attestation report - https://proof.t16z.com/reports/a0fd21c0602374388addb2f5272ab61008f5adf9c9b9a85978b799accb9c3442 - `, - }, - }, + } ], ], }; \ No newline at end of file From aadf8cb865e55a98ffc567d1b3e9cf37a71ecb11 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Sat, 4 Jan 2025 10:08:35 +0800 Subject: [PATCH 3/5] fix: the the quote upload no work --- .../plugin-tee/src/actions/remoteAttestation.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/plugin-tee/src/actions/remoteAttestation.ts b/packages/plugin-tee/src/actions/remoteAttestation.ts index a4f3be090c4..1b118ac34b3 100644 --- a/packages/plugin-tee/src/actions/remoteAttestation.ts +++ b/packages/plugin-tee/src/actions/remoteAttestation.ts @@ -1,6 +1,6 @@ import type { IAgentRuntime, Memory, State, HandlerCallback } from "@elizaos/core"; import { RemoteAttestationProvider } from "../providers/remoteAttestationProvider"; -import { File } from "formdata-node"; +import { fetch, type BodyInit } from 'undici' function hexToUint8Array(hex: string) { hex = hex.trim(); @@ -27,18 +27,12 @@ function hexToUint8Array(hex: string) { async function uploadUint8Array(data: Uint8Array) { const blob = new Blob([data], { type: "application/octet-stream" }); - const file = new File([blob], "quote.bin", { - type: "application/octet-stream", - }); const formData = new FormData(); - formData.append("file", file); + formData.append("file", blob, 'quote.bin'); return await fetch("https://proof.t16z.com/api/upload", { method: "POST", - headers: { - "Content-type": "multipart/form-data", - }, - body: formData, + body: formData as BodyInit, }); } @@ -97,4 +91,4 @@ export const remoteAttestationAction = { } ], ], -}; \ No newline at end of file +}; From 6f24839464f363f5b08c6610037fc9f48399af56 Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 3 Jan 2025 21:13:28 -0600 Subject: [PATCH 4/5] fix last items for cleanup --- docs/docs/packages/plugins.md | 12 ++++++++++-- packages/plugin-tee/src/actions/remoteAttestation.ts | 12 +++++------- packages/plugin-tee/tsup.config.ts | 1 + 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/docs/packages/plugins.md b/docs/docs/packages/plugins.md index 8e13cf70626..f93f28e522d 100644 --- a/docs/docs/packages/plugins.md +++ b/docs/docs/packages/plugins.md @@ -449,6 +449,10 @@ const response = await runtime.triggerAction("INVOKE_CONTRACT", { Integrates [Dstack SDK](https://github.com/Dstack-TEE/dstack) to enable TEE (Trusted Execution Environment) functionality and deploy secure & privacy-enhanced Eliza Agents: +**Actions:** + +- `REMOTE_ATTESTATION` - Generate a Remote Attestation Quote based on `runtime.agentId` when the agent is prompted for a remote attestation. The quote is uploaded to the [proof.t16z.com](https://proof.t16z.com) service and the agent is informed of the attestation report URL. + **Providers:** - `deriveKeyProvider` - Allows for secure key derivation within a TEE environment. It supports deriving keys for both Solana (Ed25519) and Ethereum (ECDSA) chains. @@ -526,8 +530,12 @@ docker run --rm -p 8090:8090 phalanetwork/tappd-simulator:latest When using the provider through the runtime environment, ensure the following settings are configured: ```env - # Optional, for simulator purposes if testing on mac or windows. Leave empty for Linux x86 machines. -DSTACK_SIMULATOR_ENDPOINT="http://host.docker.internal:8090" +# 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=your-secret-salt // Required to single agent deployments ``` diff --git a/packages/plugin-tee/src/actions/remoteAttestation.ts b/packages/plugin-tee/src/actions/remoteAttestation.ts index 1b118ac34b3..e3df40727cb 100644 --- a/packages/plugin-tee/src/actions/remoteAttestation.ts +++ b/packages/plugin-tee/src/actions/remoteAttestation.ts @@ -1,6 +1,6 @@ import type { IAgentRuntime, Memory, State, HandlerCallback } from "@elizaos/core"; import { RemoteAttestationProvider } from "../providers/remoteAttestationProvider"; -import { fetch, type BodyInit } from 'undici' +import { fetch, type BodyInit } from "undici"; function hexToUint8Array(hex: string) { hex = hex.trim(); @@ -58,11 +58,9 @@ export const remoteAttestationAction = { const response = await uploadUint8Array(attestationData); const data = await response.json(); callback({ - text: `Uploading attestation to proof.t16z.com...`, - action: "NONE", - }); - callback({ - text: `Attestation report for ${agentName}: https://proof.t16z.com/reports/${data.checksum}`, + text: `Here's my 🧾 RA Quote 🫡 + + https://proof.t16z.com/reports/${data.checksum}`, action: "NONE", }); return true; @@ -85,7 +83,7 @@ export const remoteAttestationAction = { { user: "{{user2}}", content: { - text: "One second, let me generate and upload my attestation report", + text: "Of course, one second...", action: "REMOTE_ATTESTATION", }, } diff --git a/packages/plugin-tee/tsup.config.ts b/packages/plugin-tee/tsup.config.ts index b94c126be77..153f6658f9c 100644 --- a/packages/plugin-tee/tsup.config.ts +++ b/packages/plugin-tee/tsup.config.ts @@ -24,5 +24,6 @@ export default defineConfig({ "@solana/buffer-layout", "stream", "buffer", + "undici", ], }); From 4c5b06757e343e3c8ebe51a2792edf4defeeb5ab Mon Sep 17 00:00:00 2001 From: HashWarlock Date: Fri, 3 Jan 2025 21:27:35 -0600 Subject: [PATCH 5/5] lint fix --- packages/plugin-tee/src/actions/remoteAttestation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/plugin-tee/src/actions/remoteAttestation.ts b/packages/plugin-tee/src/actions/remoteAttestation.ts index e3df40727cb..9ab28b5ac04 100644 --- a/packages/plugin-tee/src/actions/remoteAttestation.ts +++ b/packages/plugin-tee/src/actions/remoteAttestation.ts @@ -50,7 +50,6 @@ export const remoteAttestationAction = { try { // Get the remote attestation of the agentId const agentId = runtime.agentId; - const agentName = runtime.character.name; const teeMode = runtime.getSetting("TEE_MODE"); const provider = new RemoteAttestationProvider(teeMode); const attestation = await provider.generateAttestation(agentId, 'raw');