diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..44062b7 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index fe42b3c..fdf2c2b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,4 @@ dist elizaConfig.yaml custom_actions/ cache/ -*.tsbuildinfo.env -.elizadb -target/ -__pycache__ -*.pyc -.venv -*.egg-info -.DS_Store -package-lock.json -.env +*.tsbuildinfo \ No newline at end of file diff --git a/.npmignore b/.npmignore index 2c8c5b3..9f74001 100644 --- a/.npmignore +++ b/.npmignore @@ -1,36 +1,7 @@ -# Environment files - CRITICAL: Never publish secrets -**/.env -**/.env.* -!**/.env.example +* -# Database and runtime files -**/.eliza/ -**/.elizadb/ - -# Dependencies -**/node_modules/ - -# Source files (dist is published) -**/src/ -**/tsconfig.json -**/tsconfig.*.json -**/*.tsbuildinfo - -# Tests -**/__tests__/ -**/test/ -**/tests/ -**/*.test.ts -**/*.spec.ts - -# Development -**/.turbo/ -**/.cache/ -**/coverage/ - -# IDE -**/.vscode/ -**/.idea/ - -# OS files -**/.DS_Store +!dist/** +!package.json +!README.md +!tsup.config.ts +!LICENSE \ No newline at end of file diff --git a/README.md b/README.md index 79b13dd..524f58e 100644 --- a/README.md +++ b/README.md @@ -1,258 +1,128 @@ -# @elizaos/plugin-tee +# TEE Core Plugin for Eliza -Multi-language Trusted Execution Environment (TEE) integration plugin for elizaOS, providing secure key management and remote attestation capabilities. +The TEE Core Plugin for Eliza provides foundational capabilities for agents operating within a Trusted Execution Environment (TEE). It enables agents to perform remote attestation to prove their execution within a secure enclave and manage cryptographic keys securely. -## 🌐 Multi-Language Support +## Background -This plugin is implemented in three languages for maximum flexibility: +For Eliza agents running in a TEE, it's crucial to demonstrate this secure execution environment to external parties. Remote attestation allows an agent to generate a verifiable report, proving it's running genuine code within a specific TEE (like Intel TDX). This plugin provides the mechanisms for agents to leverage these TEE features, enhancing trust and security. Secure key derivation within the TEE is also essential for managing sensitive cryptographic operations. -| Language | Package | Registry | -| ---------- | --------------------- | --------- | -| TypeScript | `@elizaos/plugin-tee` | npm | -| Rust | `elizaos-plugin-tee` | crates.io | -| Python | `elizaos-plugin-tee` | PyPI | - -All implementations share the same API design and behavior. - -## Features - -- 🔐 **Remote Attestation** - Generate verifiable proofs that your agent is running in a secure TEE -- 🔑 **Key Derivation** - Securely derive Ed25519 (Solana) and ECDSA (EVM) keypairs within the TEE -- 🛡️ **Vendor Support** - Extensible vendor system (currently supports Phala Network) -- ⚡ **Type Safe** - Strong typing in all languages (TypeScript, Rust, Python/Pydantic) -- 🔒 **No Unsafe Code** - Rust implementation uses `#![deny(unsafe_code)]` - -## Quick Start - -### TypeScript - -```typescript -import { teePlugin, TEEService } from "@elizaos/plugin-tee"; -import { AgentRuntime } from "@elizaos/core"; - -// Register the plugin -const runtime = new AgentRuntime({ - plugins: [teePlugin], -}); - -// Or use the service directly -const service = await TEEService.start(runtime); -const solanaKeys = await service.deriveEd25519Keypair( - "salt", - "solana", - agentId, -); -const evmKeys = await service.deriveEcdsaKeypair("salt", "evm", agentId); -``` - -### Rust - -```rust -use elizaos_plugin_tee::{TEEService, TeeMode}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let service = TEEService::start(Some("LOCAL"), None)?; - - let solana = service.derive_ed25519_keypair("salt", "solana", "agent-id").await?; - println!("Solana: {}", solana.public_key); - - let evm = service.derive_ecdsa_keypair("salt", "evm", "agent-id").await?; - println!("EVM: {}", evm.address); - - Ok(()) -} -``` - -### Python - -```python -from elizaos_plugin_tee import TEEService, TeeMode +## Requirements -async def main(): - service = await TEEService.start(tee_mode="LOCAL") +- A TEE-enabled environment is required (e.g., Intel TDX) use [Phala Cloud](https://cloud.phala.network) for easy deployment. +- Configuration within Eliza to enable and utilize this plugin's features. - solana = await service.derive_ed25519_keypair("salt", "solana", "agent-id") - print(f"Solana: {solana.public_key}") +The plugin requires the following environment variables: - evm = await service.derive_ecdsa_keypair("salt", "evm", "agent-id") - print(f"EVM: {evm.address}") +```env +# For the environment you are running the TEE plugin. For local and container development, use `LOCAL` or `DOCKER`. For production deployments, use `PRODUCTION`. +TEE_MODE=LOCAL|DOCKER|PRODUCTION +# Secret salt for your default agent to generate a key from through the derive key provider +WALLET_SECRET_SALT=your_secret_salt +# TEE_VENDOR only supports Phala at this time, but adding a vendor is easy and can be done to support more TEE Vendors in the TEE Plugin +TEE_VENDOR=phala - await service.stop() -``` - -## Configuration +## Features -### Environment Variables +This plugin offers the following core TEE functionalities: -| Variable | Description | Required | Default | -| -------------------- | ----------------------------------------------- | -------- | ------- | -| `TEE_MODE` | Operation mode: `LOCAL`, `DOCKER`, `PRODUCTION` | Yes | - | -| `WALLET_SECRET_SALT` | Secret salt for deterministic key derivation | Yes | - | -| `TEE_VENDOR` | TEE vendor to use | No | `phala` | +1. **Remote Attestation**: -### TEE Modes + - Provides actions and providers (`remoteAttestationAction`, `remoteAttestationProvider`) allowing agents to request and receive remote attestation reports. + - These reports can be presented to third parties to verify the agent's TEE residency. + - Includes support for specific TEE vendors/attestation services (e.g., Phala Network). -- **LOCAL**: Development mode using simulator at `localhost:8090` -- **DOCKER**: Docker development mode using simulator at `host.docker.internal:8090` -- **PRODUCTION**: Production mode connecting to real TEE infrastructure +2. **Key Derivation**: + - Offers a `deriveKeyProvider` for securely deriving cryptographic keys within the TEE. + - Ensures that key material is generated and managed within the protected enclave memory. ## Components -### Actions +Based on the source code (`src/`): -| Action | Description | -| -------------------- | --------------------------------------------------------------------- | -| `REMOTE_ATTESTATION` | Generate and upload a remote attestation quote to prove TEE execution | +- **Actions**: + - `remoteAttestationAction.ts`: Likely handles agent requests to initiate the remote attestation process. +- **Providers**: + - `remoteAttestationProvider.ts`: Implements the logic for interacting with the underlying TEE platform or attestation service (like Phala) to generate the attestation report. + - `deriveKeyProvider.ts`: Implements the logic for TEE-specific key derivation. +- **Services** + - `service.ts`: TEE Service to allow agents to generate keys from `deriveKeyProvider` for EVM, Solana, and raw `DeriveKeyResponse` that will return the `key`, `certificate_chain` and the `Uint8Array` with `asUint8Array(max_length?: number)`. +- **Vendors**: + - `vendors/phala.ts`: Contains specific implementation details for interacting with the Phala Network's attestation services. + - `vendors/index.ts`, `vendors/types.ts`: Support vendor integration. +- **Utilities & Types**: + - `utils.ts`, `types.ts`: Contain helper functions and type definitions for the plugin. +- **Tests**: + - `__tests__/`: Includes unit tests for key derivation, remote attestation, etc. -### Providers +## Usage -| Provider | Description | -| -------------------------- | ----------------------------------------------- | -| `phala-derive-key` | Derive Solana and EVM keypairs with attestation | -| `phala-remote-attestation` | Generate remote attestation quotes | +_(This section may need further refinement based on how the plugin is integrated into the core Eliza system)_ -### Services +To utilize the features of this plugin: -| Service | Description | -| ------------ | ---------------------------------------------- | -| `TEEService` | Main service for key derivation and management | +1. **Ensure the plugin is enabled** in your Eliza agent's configuration. +2. **Configure the TEE vendor** (e.g., specify 'phala' if using Phala Network attestation) if required by the environment setup. +3. **Call the relevant actions or services** provided by this plugin from other agent logic or plugins when remote attestation or secure key derivation is needed. -## API Reference - -### TEEService +Example (Conceptual): ```typescript -class TEEService { - // Derive Ed25519 keypair for Solana - async deriveEd25519Keypair( - path: string, - subject: string, - agentId: UUID, - ): Promise<{ keypair: Keypair; attestation: RemoteAttestationQuote }>; - - // Derive ECDSA keypair for EVM - async deriveEcdsaKeypair( - path: string, - subject: string, - agentId: UUID, - ): Promise<{ - keypair: PrivateKeyAccount; - attestation: RemoteAttestationQuote; - }>; - - // Derive raw key for custom use cases - async rawDeriveKey(path: string, subject: string): Promise; +import import { PhalaDeriveKeyProvider, PhalaRemoteAttestationProvider } from '@elizaos/tee-plugin'; +// Assuming access to the runtime and its services/actions + +// Requesting remote attestation +async function getAttestation( + runtime: IAgentRuntime, + userData: string +): Promise { + try { + const provider = new PhalaRemoteAttestationProvider(teeMode); + + const attestation = await provider.generateAttestation(userData); + const attestationData = hexToUint8Array(attestation.quote); + const raQuote = await uploadUint8Array(attestationData); + return attestation; + } catch (error) { + console.error('Failed to get remote attestation:', error); + return null; + } } -``` - -### Remote Attestation -```typescript -class PhalaRemoteAttestationProvider { - // Generate attestation quote - async generateAttestation( - reportData: string, - hashAlgorithm?: TdxQuoteHashAlgorithm, - ): Promise; +// Deriving a key +async function deriveAgentKeys( + runtime: IAgentRuntime, salt: string + ): Promise { + try { + // Potentially using a service/provider interface + const provider = new PhalaDeriveKeyProvider(teeMode) + const secretSalt = runtime.getSetting('WALLET_SECRET_SALT') || 'secret_salt'; + const solanaKeypair = await provider.deriveEd25519Keypair(secretSalt, 'solana', agentId); + const evmKeypair = await provider.deriveEcdsaKeypair(secretSalt, 'evm', agentId); + + // Original data structure + const walletData = { + solana: solanaKeypair.keypair.publicKey, + evm: evmKeypair.keypair.address, + }; + + // Values for template injection + const values = { + solana_public_key: solanaKeypair.keypair.publicKey.toString(), + evm_address: evmKeypair.keypair.address, + }; + + // Text representation + const text = `Solana Public Key: ${values.solana_public_key}\nEVM Address: ${values.evm_address}`; + + return { + data: walletData, + values: values, + text: text, + }; + return key; + } catch (error) { + console.error('Failed to derive key:', error); + return null; + } } ``` - -## Directory Structure - -``` -plugins/plugin-tee/ -├── typescript/ # TypeScript implementation -│ ├── src/ -│ │ ├── actions/ # Remote attestation action -│ │ ├── providers/ # Key derivation & attestation providers -│ │ ├── services/ # TEE service -│ │ ├── types/ # Type definitions -│ │ ├── vendors/ # Vendor implementations -│ │ └── index.ts # Main entry point -│ └── __tests__/ # Unit tests -├── rust/ # Rust implementation -│ ├── src/ -│ │ ├── actions/ # Remote attestation action -│ │ ├── providers/ # Key derivation & attestation providers -│ │ ├── services/ # TEE service -│ │ ├── types.rs # Type definitions -│ │ └── lib.rs # Main entry point -│ ├── tests/ # Integration tests -│ └── Cargo.toml # Crate manifest -├── python/ # Python implementation -│ ├── elizaos_plugin_tee/ -│ │ ├── actions/ # Remote attestation action -│ │ ├── providers/ # Key derivation & attestation providers -│ │ ├── services/ # TEE service -│ │ ├── types.py # Pydantic models -│ │ └── __init__.py # Main entry point -│ ├── tests/ # Unit tests -│ └── pyproject.toml # Package manifest -├── package.json # NPM manifest -└── README.md # This file -``` - -## Development - -### Building - -```bash -# TypeScript -bun run build - -# Rust (native) -cd rust && cargo build --release - -# Rust (WASM) -bun run build:rust:wasm - -# Python (install in dev mode) -cd python && pip install -e ".[dev]" -``` - -### Testing - -```bash -# TypeScript -bun run test - -# Rust -bun run test:rust - -# Python -bun run test:python - -# All languages -bun run test:all -``` - -### Linting - -```bash -# TypeScript -bun run format:check - -# Rust -bun run lint:rust - -# Python -bun run lint:python -``` - -## Requirements - -- **TypeScript**: Node.js 18+ or Bun -- **Rust**: Rust 1.70+ -- **Python**: Python 3.11+ -- **TEE Environment**: Intel TDX-enabled environment or [Phala Cloud](https://cloud.phala.network) for production - -## License - -MIT - -## Related Links - -- [elizaOS Documentation](https://elizaos.ai/docs) -- [Phala Network](https://phala.network) -- [Intel TDX](https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html) diff --git a/package.json b/package.json index e055d48..4c951e9 100644 --- a/package.json +++ b/package.json @@ -1,66 +1,34 @@ { - "name": "@elizaos/plugin-tee-root", - "private": true, - "version": "2.0.0-alpha.1", - "description": "Trusted Execution Environment (TEE) integration plugin for elizaOS - Multi-language support (TypeScript, Python, Rust)", + "name": "@elizaos/plugin-tee", + "version": "1.0.3", + "main": "dist/index.js", "type": "module", - "main": "typescript/dist/index.js", - "types": "typescript/dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./typescript/dist/index.d.ts", - "import": "./typescript/dist/index.js", - "default": "./typescript/dist/index.js" - }, - "./rust": { - "import": "./rust/pkg/node/elizaos_plugin_plugin_tee.js" - } + "types": "dist/index.d.ts", + "dependencies": { + "@elizaos/core": "^1.0.0", + "@phala/dstack-sdk": "0.5.7", + "@solana/web3.js": "1.98.2", + "viem": "2.29.4" }, - "files": [ - "typescript/dist", - "README.md", - "python", - "rust/src", - "rust/Cargo.toml" - ], - "keywords": [], - "author": "elizaOS", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/elizaos-plugins/plugin-tee.git" + "devDependencies": { + "@types/node": "^22.15.3", + "prettier": "3.5.3", + "tsup": "8.5.0", + "typescript": "5.8.3", + "vitest": "^3.1.3" }, "scripts": { - "build": "bun run build:ts && bun run build:rust && bun run build:python", - "build:ts": "cd typescript && bun run build.ts", - "build:rust": "test -d rust && cd rust && cargo build --release || echo 'Rust build skipped - no rust directory'", - "build:python": "test -n \"$SKIP_PYTHON_BUILD\" && echo 'Python build skipped (SKIP_PYTHON_BUILD set)' || (test -d python && cd python && (timeout 120 python3 -m build 2>/dev/null || timeout 120 pyproject-build 2>/dev/null) || echo 'Python build skipped or timed out')", - "dev": "cd typescript && bun --hot build.ts", - "test": "bun run test:ts && bun run test:rust && bun run test:python", - "test:ts": "cd typescript && vitest run || echo 'TypeScript tests skipped - no tests found'", - "test:rust": "test -d rust && cd rust && cargo test || echo 'Rust tests skipped'", - "test:python": "test -d python && cd python && pytest -p no:anchorpy --asyncio-mode=auto || echo 'Python tests skipped'", - "typecheck": "tsc --noEmit -p typescript/tsconfig.json", - "lint": "bunx @biomejs/biome check --write ./typescript", - "lint:check": "bunx @biomejs/biome check ./typescript", - "lint:rust": "test -d rust && cd rust && cargo clippy --all-targets --fix --allow-dirty --allow-staged -- -D warnings && cargo fmt || echo 'Rust lint skipped'", - "lint:python": "test -d python && cd python && ruff check --fix . && ruff format . || echo 'Python lint skipped'", - "clean": "rm -rf typescript/dist python/dist rust/target .turbo node_modules", - "format": "bunx @biomejs/biome format --write ./typescript", - "format:check": "bunx @biomejs/biome format ./typescript", - "typecheck:python": "test -d python && cd python && mypy . --ignore-missing-imports || echo 'Python typecheck skipped'", - "typecheck:rust": "test -d rust && cd rust && cargo check || echo 'Rust typecheck skipped'" + "build": "tsup", + "dev": "tsup --watch", + "lint": "prettier --write ./src", + "clean": "rm -rf dist .turbo node_modules .turbo-tsconfig.json tsconfig.tsbuildinfo", + "format": "prettier --write ./src", + "format:check": "prettier --check ./src" }, - "devDependencies": { - "@biomejs/biome": "^2.3.11", - "@types/bun": "^1.3.5", - "@types/node": "^25.0.3", - "typescript": "^5.9.3" - }, - "peerDependencies": { - "@elizaos/core": "next" + "publishConfig": { + "access": "public" }, + "gitHead": "646c632924826e2b75c2304a75ee56959fe4a460", "agentConfig": { "pluginType": "elizaos:plugin:1.0.0", "pluginParameters": { @@ -86,5 +54,13 @@ } } }, - "gitHead": "05d4ca11d769db8c7f54a722ee24b2ce2b951543" + "repository": { + "type": "git", + "url": "git+https://github.com/elizaos-plugins/plugin-tee.git" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ] } diff --git a/prompts/evaluators.json b/prompts/evaluators.json deleted file mode 100644 index 33a075c..0000000 --- a/prompts/evaluators.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "version": "1.0.0", - "evaluators": [] -} diff --git a/prompts/scripts/generate-specs.js b/prompts/scripts/generate-specs.js deleted file mode 100755 index 1327e6c..0000000 --- a/prompts/scripts/generate-specs.js +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/env node -/** - * Plugin Discord Spec Generator - * - * Reads specs from prompts/specs/** and generates language-native docs modules for: - * - typescript - * - python - * - rust - * - * This is adapted from packages/prompts/scripts/generate-action-docs.js - */ - -import fs from "node:fs"; -import path from "path"; -import { fileURLToPath } from "node:url"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const PLUGIN_ROOT = path.resolve(__dirname, "../.."); -const PROMPTS_ROOT = path.resolve(__dirname, ".."); - -const ACTIONS_SPEC_PATH = path.join(PROMPTS_ROOT, "actions.json"); -const PROVIDERS_SPEC_PATH = path.join(PROMPTS_ROOT, "providers.json"); -const EVALUATORS_SPEC_PATH = path.join(PROMPTS_ROOT, "evaluators.json"); - -function readJson(filePath) { - if (!fs.existsSync(filePath)) { - return { version: "1.0.0", actions: [], providers: [], evaluators: [] }; - } - const raw = fs.readFileSync(filePath, "utf-8"); - return JSON.parse(raw); -} - -function listJsonFiles(rootDir) { - const out = []; - if (!fs.existsSync(rootDir)) { - return out; - } - const stack = [rootDir]; - while (stack.length > 0) { - const dir = stack.pop(); - if (!dir) break; - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - stack.push(full); - continue; - } - if (entry.isFile() && entry.name.endsWith(".json")) { - out.push(full); - } - } - } - return out.sort((a, b) => a.localeCompare(b)); -} - -function loadSpecs(specPath, kind) { - if (!fs.existsSync(specPath)) { - return { - core: { version: "1.0.0", items: [] }, - all: { version: "1.0.0", items: [] }, - }; - } - - const root = readJson(specPath); - const items = kind === "actions" ? root.actions : - kind === "providers" ? root.providers : - root.evaluators || []; - - return { - core: { - version: root.version || "1.0.0", - items: items, - }, - all: { - version: root.version || "1.0.0", - items: items, - }, - }; -} - -/** - * Ensures a directory exists, creating it and parent directories if necessary. - * @param {string} dir - The directory path to ensure exists - * @throws {Error} If the directory path is empty or whitespace-only - */ -function ensureDir(dir) { - if (!dir || dir.trim() === "") { - throw new Error("Directory path cannot be empty"); - } - fs.mkdirSync(dir, { recursive: true }); -} - -function escapeRustRawString(content) { - let hashCount = 1; - while (content.includes(`"${"#".repeat(hashCount)}`)) { - hashCount++; - } - return { content, hashCount }; -} - -function escapePythonTripleQuoted(content) { - return content.replace(/\\/g, "\\\\").replace(/"""/g, '\\"\\"\\"'); -} - -function generateTypeScript(actionsSpec, providersSpec, evaluatorsSpec) { - const outDir = path.join(PLUGIN_ROOT, "typescript", "generated", "specs"); - ensureDir(outDir); - - const actionsJson = JSON.stringify( - { version: actionsSpec.core.version, actions: actionsSpec.core.items }, - null, - 2, - ); - const actionsAllJson = JSON.stringify( - { version: actionsSpec.all.version, actions: actionsSpec.all.items }, - null, - 2, - ); - const providersJson = JSON.stringify( - { version: providersSpec.core.version, providers: providersSpec.core.items }, - null, - 2, - ); - const providersAllJson = JSON.stringify( - { version: providersSpec.all.version, providers: providersSpec.all.items }, - null, - 2, - ); - const evaluatorsJson = JSON.stringify( - { - version: evaluatorsSpec.core.version, - evaluators: evaluatorsSpec.core.items, - }, - null, - 2, - ); - const evaluatorsAllJson = JSON.stringify( - { - version: evaluatorsSpec.all.version, - evaluators: evaluatorsSpec.all.items, - }, - null, - 2, - ); - - const content = `/** - * Auto-generated canonical action/provider/evaluator docs for plugin-tee. - * DO NOT EDIT - Generated from prompts/specs/**. - */ - -export type ActionDoc = { - name: string; - description: string; - similes?: readonly string[]; - parameters?: readonly unknown[]; - examples?: readonly (readonly unknown[])[]; -}; - -export type ProviderDoc = { - name: string; - description: string; - position?: number; - dynamic?: boolean; -}; - -export type EvaluatorDoc = { - name: string; - description: string; - similes?: readonly string[]; - alwaysRun?: boolean; - examples?: readonly unknown[]; -}; - -export const coreActionsSpec = ${actionsJson} as const; -export const allActionsSpec = ${actionsAllJson} as const; -export const coreProvidersSpec = ${providersJson} as const; -export const allProvidersSpec = ${providersAllJson} as const; -export const coreEvaluatorsSpec = ${evaluatorsJson} as const; -export const allEvaluatorsSpec = ${evaluatorsAllJson} as const; - -export const coreActionDocs: readonly ActionDoc[] = coreActionsSpec.actions; -export const allActionDocs: readonly ActionDoc[] = allActionsSpec.actions; -export const coreProviderDocs: readonly ProviderDoc[] = coreProvidersSpec.providers; -export const allProviderDocs: readonly ProviderDoc[] = allProvidersSpec.providers; -export const coreEvaluatorDocs: readonly EvaluatorDoc[] = coreEvaluatorsSpec.evaluators; -export const allEvaluatorDocs: readonly EvaluatorDoc[] = allEvaluatorsSpec.evaluators; -`; - - fs.writeFileSync(path.join(outDir, "specs.ts"), content); - - // Generate spec-helpers.ts - const helpersContent = `/** - * Helper functions to lookup action/provider/evaluator specs by name. - * These allow language-specific implementations to import their text content - * (description, similes, examples) from the centralized specs. - * - * DO NOT EDIT the spec data - update prompts/actions.json, prompts/providers.json, prompts/evaluators.json and regenerate. - */ - -import { - coreActionDocs, - coreProviderDocs, - coreEvaluatorDocs, - allActionDocs, - allProviderDocs, - allEvaluatorDocs, - type ActionDoc, - type ProviderDoc, - type EvaluatorDoc, -} from "./specs"; - -// Build lookup maps for O(1) access -const coreActionMap = new Map( - coreActionDocs.map((doc) => [doc.name, doc]) -); -const allActionMap = new Map( - allActionDocs.map((doc) => [doc.name, doc]) -); -const coreProviderMap = new Map( - coreProviderDocs.map((doc) => [doc.name, doc]) -); -const allProviderMap = new Map( - allProviderDocs.map((doc) => [doc.name, doc]) -); -const coreEvaluatorMap = new Map( - coreEvaluatorDocs.map((doc) => [doc.name, doc]) -); -const allEvaluatorMap = new Map( - allEvaluatorDocs.map((doc) => [doc.name, doc]) -); - -/** - * Get an action spec by name from the core specs. - * @param name - The action name - * @returns The action spec or undefined if not found - */ -export function getActionSpec(name: string): ActionDoc | undefined { - return coreActionMap.get(name) ?? allActionMap.get(name); -} - -/** - * Get an action spec by name, throwing if not found. - * @param name - The action name - * @returns The action spec - * @throws Error if the action is not found - */ -export function requireActionSpec(name: string): ActionDoc { - const spec = getActionSpec(name); - if (!spec) { - throw new Error(\`Action spec not found: \${name}\`); - } - return spec; -} - -/** - * Get a provider spec by name from the core specs. - * @param name - The provider name - * @returns The provider spec or undefined if not found - */ -export function getProviderSpec(name: string): ProviderDoc | undefined { - return coreProviderMap.get(name) ?? allProviderMap.get(name); -} - -/** - * Get a provider spec by name, throwing if not found. - * @param name - The provider name - * @returns The provider spec - * @throws Error if the provider is not found - */ -export function requireProviderSpec(name: string): ProviderDoc { - const spec = getProviderSpec(name); - if (!spec) { - throw new Error(\`Provider spec not found: \${name}\`); - } - return spec; -} - -/** - * Get an evaluator spec by name from the core specs. - * @param name - The evaluator name - * @returns The evaluator spec or undefined if not found - */ -export function getEvaluatorSpec(name: string): EvaluatorDoc | undefined { - return coreEvaluatorMap.get(name) ?? allEvaluatorMap.get(name); -} - -/** - * Get an evaluator spec by name, throwing if not found. - * @param name - The evaluator name - * @returns The evaluator spec - * @throws Error if the evaluator is not found - */ -export function requireEvaluatorSpec(name: string): EvaluatorDoc { - const spec = getEvaluatorSpec(name); - if (!spec) { - throw new Error(\`Evaluator spec not found: \${name}\`); - } - return spec; -} - -// Re-export types for convenience -export type { ActionDoc, ProviderDoc, EvaluatorDoc }; -`; - - fs.writeFileSync(path.join(outDir, "spec-helpers.ts"), helpersContent); -} - -function generatePython(actionsSpec, providersSpec, evaluatorsSpec) { - const outDir = path.join(PLUGIN_ROOT, "python", "elizaos_plugin_tee", "generated", "specs"); - ensureDir(outDir); - - const initPath = path.join(outDir, "__init__.py"); - if (!fs.existsSync(initPath)) { - fs.writeFileSync(initPath, '"""Auto-generated module package."""\n'); - } - - const actionsJson = JSON.stringify( - { version: actionsSpec.core.version, actions: actionsSpec.core.items }, - null, - 2, - ); - const actionsAllJson = JSON.stringify( - { version: actionsSpec.all.version, actions: actionsSpec.all.items }, - null, - 2, - ); - const providersJson = JSON.stringify( - { version: providersSpec.core.version, providers: providersSpec.core.items }, - null, - 2, - ); - const providersAllJson = JSON.stringify( - { version: providersSpec.all.version, providers: providersSpec.all.items }, - null, - 2, - ); - const evaluatorsJson = JSON.stringify( - { - version: evaluatorsSpec.core.version, - evaluators: evaluatorsSpec.core.items, - }, - null, - 2, - ); - const evaluatorsAllJson = JSON.stringify( - { - version: evaluatorsSpec.all.version, - evaluators: evaluatorsSpec.all.items, - }, - null, - 2, - ); - - const content = `""" -Auto-generated canonical action/provider/evaluator docs for plugin-tee. -DO NOT EDIT - Generated from prompts/specs/**. -""" - -from __future__ import annotations - -import json -from typing import TypedDict - -class ActionDoc(TypedDict, total=False): - name: str - description: str - similes: list[str] - parameters: list[object] - examples: list[list[object]] - -class ProviderDoc(TypedDict, total=False): - name: str - description: str - position: int - dynamic: bool - -class EvaluatorDoc(TypedDict, total=False): - name: str - description: str - similes: list[str] - alwaysRun: bool - examples: list[object] - -_CORE_ACTION_DOCS_JSON = """${escapePythonTripleQuoted(actionsJson)}""" -_ALL_ACTION_DOCS_JSON = """${escapePythonTripleQuoted(actionsAllJson)}""" -_CORE_PROVIDER_DOCS_JSON = """${escapePythonTripleQuoted(providersJson)}""" -_ALL_PROVIDER_DOCS_JSON = """${escapePythonTripleQuoted(providersAllJson)}""" -_CORE_EVALUATOR_DOCS_JSON = """${escapePythonTripleQuoted(evaluatorsJson)}""" -_ALL_EVALUATOR_DOCS_JSON = """${escapePythonTripleQuoted(evaluatorsAllJson)}""" - -core_action_docs: dict[str, object] = json.loads(_CORE_ACTION_DOCS_JSON) -all_action_docs: dict[str, object] = json.loads(_ALL_ACTION_DOCS_JSON) -core_provider_docs: dict[str, object] = json.loads(_CORE_PROVIDER_DOCS_JSON) -all_provider_docs: dict[str, object] = json.loads(_ALL_PROVIDER_DOCS_JSON) -core_evaluator_docs: dict[str, object] = json.loads(_CORE_EVALUATOR_DOCS_JSON) -all_evaluator_docs: dict[str, object] = json.loads(_ALL_EVALUATOR_DOCS_JSON) - -__all__ = [ - "ActionDoc", - "ProviderDoc", - "EvaluatorDoc", - "core_action_docs", - "all_action_docs", - "core_provider_docs", - "all_provider_docs", - "core_evaluator_docs", - "all_evaluator_docs", -] -`; - - fs.writeFileSync(path.join(outDir, "specs.py"), content); -} - -function generateRust(actionsSpec, providersSpec, evaluatorsSpec) { - const outDir = path.join(PLUGIN_ROOT, "rust", "src", "generated", "specs"); - ensureDir(outDir); - - const actionsJson = JSON.stringify( - { version: actionsSpec.core.version, actions: actionsSpec.core.items }, - null, - 2, - ); - const actionsAllJson = JSON.stringify( - { version: actionsSpec.all.version, actions: actionsSpec.all.items }, - null, - 2, - ); - const providersJson = JSON.stringify( - { version: providersSpec.core.version, providers: providersSpec.core.items }, - null, - 2, - ); - const providersAllJson = JSON.stringify( - { version: providersSpec.all.version, providers: providersSpec.all.items }, - null, - 2, - ); - const evaluatorsJson = JSON.stringify( - { - version: evaluatorsSpec.core.version, - evaluators: evaluatorsSpec.core.items, - }, - null, - 2, - ); - const evaluatorsAllJson = JSON.stringify( - { - version: evaluatorsSpec.all.version, - evaluators: evaluatorsSpec.all.items, - }, - null, - 2, - ); - - const { content: actionsContent, hashCount: actionsHashCount } = - escapeRustRawString(actionsJson); - const { content: actionsAllContent, hashCount: actionsAllHashCount } = - escapeRustRawString(actionsAllJson); - const { content: providersContent, hashCount: providersHashCount } = - escapeRustRawString(providersJson); - const { content: providersAllContent, hashCount: providersAllHashCount } = - escapeRustRawString(providersAllJson); - const { content: evalContent, hashCount: evalHashCount } = - escapeRustRawString(evaluatorsJson); - const { content: evalAllContent, hashCount: evalAllHashCount } = - escapeRustRawString(evaluatorsAllJson); - - const actionsDelim = "#".repeat(actionsHashCount); - const actionsAllDelim = "#".repeat(actionsAllHashCount); - const providersDelim = "#".repeat(providersHashCount); - const providersAllDelim = "#".repeat(providersAllHashCount); - const evalDelim = "#".repeat(evalHashCount); - const evalAllDelim = "#".repeat(evalAllHashCount); - - const content = `//! Auto-generated canonical action/provider/evaluator docs for plugin-tee. -//! DO NOT EDIT - Generated from prompts/specs/**. - -pub const CORE_ACTION_DOCS_JSON: &str = r${actionsDelim}"${actionsContent}"${actionsDelim}; -pub const ALL_ACTION_DOCS_JSON: &str = r${actionsAllDelim}"${actionsAllContent}"${actionsAllDelim}; -pub const CORE_PROVIDER_DOCS_JSON: &str = r${providersDelim}"${providersContent}"${providersDelim}; -pub const ALL_PROVIDER_DOCS_JSON: &str = r${providersAllDelim}"${providersAllContent}"${providersAllDelim}; -pub const CORE_EVALUATOR_DOCS_JSON: &str = r${evalDelim}"${evalContent}"${evalDelim}; -pub const ALL_EVALUATOR_DOCS_JSON: &str = r${evalAllDelim}"${evalAllContent}"${evalAllDelim}; -`; - - fs.writeFileSync(path.join(outDir, "specs.rs"), content); - - const modPath = path.join(outDir, "mod.rs"); - const modContent = `//! Auto-generated specs module.\n\npub mod specs;\n`; - fs.writeFileSync(modPath, modContent); -} - -function main() { - const actionsSpec = loadSpecs(ACTIONS_SPEC_PATH, "actions"); - const providersSpec = loadSpecs(PROVIDERS_SPEC_PATH, "providers"); - const evaluatorsSpec = loadSpecs(EVALUATORS_SPEC_PATH, "evaluators"); - - generateTypeScript(actionsSpec, providersSpec, evaluatorsSpec); - generatePython(actionsSpec, providersSpec, evaluatorsSpec); - generateRust(actionsSpec, providersSpec, evaluatorsSpec); - - console.log("Generated plugin-tee action/provider/evaluator docs."); -} - -main(); diff --git a/python/LICENSE b/python/LICENSE deleted file mode 100644 index 6fa975f..0000000 --- a/python/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -MIT License - -Copyright (c) 2024 elizaOS Team - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - - - - - diff --git a/python/README.md b/python/README.md deleted file mode 100644 index c40883f..0000000 --- a/python/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# elizaos-plugin-tee (Python) - -Python implementation of the elizaOS TEE Plugin for Trusted Execution Environment integration. - -## Features - -- 🔐 **Remote Attestation** - Prove agent execution in TEE -- 🔑 **Key Derivation** - Secure Ed25519 and ECDSA key derivation -- 🛡️ **Vendor Support** - Extensible vendor system (Phala Network) -- ⚡ **Async** - Full async/await support -- 🔒 **Type Safe** - Pydantic models with strict validation - -## Installation - -```bash -pip install elizaos-plugin-tee -``` - -## Quick Start - -```python -from elizaos_plugin_tee import TEEService, TeeMode - -# Start the service -service = await TEEService.start(tee_mode="LOCAL") - -# Derive Ed25519 keypair (for Solana) -solana_result = await service.derive_ed25519_keypair( - path="my-secret-salt", - subject="solana", - agent_id="agent-123" -) -print(f"Solana Public Key: {solana_result.public_key}") - -# Derive ECDSA keypair (for EVM) -evm_result = await service.derive_ecdsa_keypair( - path="my-secret-salt", - subject="evm", - agent_id="agent-123" -) -print(f"EVM Address: {evm_result.address}") - -# Stop the service -await service.stop() -``` - -## Configuration - -| Variable | Description | Required | -| -------------------- | ----------------------------------------------- | -------- | -| `TEE_MODE` | Operation mode: `LOCAL`, `DOCKER`, `PRODUCTION` | Yes | -| `WALLET_SECRET_SALT` | Secret for key derivation | Yes | -| `TEE_VENDOR` | Vendor name (default: `phala`) | No | - -## API Reference - -### TEEService - -Main service for TEE operations. - -```python -# Initialize -service = await TEEService.start(tee_mode="LOCAL") - -# Derive keys -await service.derive_ed25519_keypair(path, subject, agent_id) -await service.derive_ecdsa_keypair(path, subject, agent_id) -await service.raw_derive_key(path, subject) - -# Cleanup -await service.stop() -``` - -### Remote Attestation - -```python -from elizaos_plugin_tee import ( - PhalaRemoteAttestationProvider, - handle_remote_attestation, -) - -# Using provider directly -provider = PhalaRemoteAttestationProvider("LOCAL") -quote = await provider.generate_attestation(report_data) -await provider.close() - -# Using action handler -result = await handle_remote_attestation( - tee_mode="LOCAL", - agent_id="agent-123", - entity_id="entity-456", - room_id="room-789", - content="Message content" -) -``` - -### Types - -```python -from elizaos_plugin_tee import ( - TeeMode, - TeeVendor, - RemoteAttestationQuote, - Ed25519KeypairResult, - EcdsaKeypairResult, -) -``` - -## Development - -```bash -# Install dev dependencies -pip install -e ".[dev]" - -# Run tests -pytest - -# Type checking -mypy elizaos_plugin_tee - -# Linting -ruff check . -ruff format . -``` - -## License - -MIT - - - diff --git a/python/elizaos_plugin_discord/generated/specs/__init__.py b/python/elizaos_plugin_discord/generated/specs/__init__.py deleted file mode 100644 index 49603bd..0000000 --- a/python/elizaos_plugin_discord/generated/specs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Auto-generated module package.""" diff --git a/python/elizaos_plugin_discord/generated/specs/specs.py b/python/elizaos_plugin_discord/generated/specs/specs.py deleted file mode 100644 index 2144117..0000000 --- a/python/elizaos_plugin_discord/generated/specs/specs.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Auto-generated canonical action/provider/evaluator docs for plugin-tee. -DO NOT EDIT - Generated from prompts/specs/**. -""" - -from __future__ import annotations - -import json -from typing import TypedDict - - -class ActionDoc(TypedDict, total=False): - name: str - description: str - similes: list[str] - parameters: list[object] - examples: list[list[object]] - - -class ProviderDoc(TypedDict, total=False): - name: str - description: str - position: int - dynamic: bool - - -class EvaluatorDoc(TypedDict, total=False): - name: str - description: str - similes: list[str] - alwaysRun: bool - examples: list[object] - - -_CORE_ACTION_DOCS_JSON = """{ - "version": "1.0.0", - "actions": [] -}""" -_ALL_ACTION_DOCS_JSON = """{ - "version": "1.0.0", - "actions": [] -}""" -_CORE_PROVIDER_DOCS_JSON = """{ - "version": "1.0.0", - "providers": [] -}""" -_ALL_PROVIDER_DOCS_JSON = """{ - "version": "1.0.0", - "providers": [] -}""" -_CORE_EVALUATOR_DOCS_JSON = """{ - "version": "1.0.0", - "evaluators": [] -}""" -_ALL_EVALUATOR_DOCS_JSON = """{ - "version": "1.0.0", - "evaluators": [] -}""" - -core_action_docs: dict[str, object] = json.loads(_CORE_ACTION_DOCS_JSON) -all_action_docs: dict[str, object] = json.loads(_ALL_ACTION_DOCS_JSON) -core_provider_docs: dict[str, object] = json.loads(_CORE_PROVIDER_DOCS_JSON) -all_provider_docs: dict[str, object] = json.loads(_ALL_PROVIDER_DOCS_JSON) -core_evaluator_docs: dict[str, object] = json.loads(_CORE_EVALUATOR_DOCS_JSON) -all_evaluator_docs: dict[str, object] = json.loads(_ALL_EVALUATOR_DOCS_JSON) - -__all__ = [ - "ActionDoc", - "ProviderDoc", - "EvaluatorDoc", - "core_action_docs", - "all_action_docs", - "core_provider_docs", - "all_provider_docs", - "core_evaluator_docs", - "all_evaluator_docs", -] diff --git a/python/elizaos_plugin_tee/__init__.py b/python/elizaos_plugin_tee/__init__.py deleted file mode 100644 index 6502f30..0000000 --- a/python/elizaos_plugin_tee/__init__.py +++ /dev/null @@ -1,112 +0,0 @@ -"""TEE plugin for secure key management and remote attestation.""" - -from elizaos_plugin_tee.actions import ( - REMOTE_ATTESTATION_ACTION, - handle_remote_attestation, -) -from elizaos_plugin_tee.errors import ( - AttestationError, - ConfigError, - InvalidModeError, - InvalidVendorError, - KeyDerivationError, - NetworkError, - TeeError, -) -from elizaos_plugin_tee.providers import ( - DeriveKeyProvider, - PhalaDeriveKeyProvider, - PhalaRemoteAttestationProvider, - RemoteAttestationProvider, - get_derived_keys, - get_remote_attestation, -) -from elizaos_plugin_tee.services import TEEService -from elizaos_plugin_tee.types import ( - DeriveKeyAttestationData, - DeriveKeyResult, - EcdsaKeypairResult, - Ed25519KeypairResult, - RemoteAttestationMessage, - RemoteAttestationQuote, - TdxQuoteHashAlgorithm, - TeeMode, - TeeProviderResult, - TeeServiceConfig, - TeeType, - TeeVendor, - parse_tee_mode, - parse_tee_vendor, -) -from elizaos_plugin_tee.utils import ( - bytes_to_hex, - calculate_sha256, - get_tee_endpoint, - hex_to_bytes, - upload_attestation_quote, -) -from elizaos_plugin_tee.vendors import ( - PhalaVendor, - TeeVendorInterface, - TeeVendorNames, - get_vendor, -) - -__version__ = "1.0.0" - -PLUGIN_NAME = "tee" -PLUGIN_DESCRIPTION = "TEE integration plugin for secure key management and remote attestation" - -__all__ = [ - # Version - "__version__", - # Plugin metadata - "PLUGIN_NAME", - "PLUGIN_DESCRIPTION", - # Types - "TeeMode", - "TeeVendor", - "TeeType", - "TdxQuoteHashAlgorithm", - "RemoteAttestationQuote", - "DeriveKeyAttestationData", - "RemoteAttestationMessage", - "DeriveKeyResult", - "Ed25519KeypairResult", - "EcdsaKeypairResult", - "TeeServiceConfig", - "TeeProviderResult", - "parse_tee_mode", - "parse_tee_vendor", - # Errors - "TeeError", - "ConfigError", - "AttestationError", - "KeyDerivationError", - "NetworkError", - "InvalidModeError", - "InvalidVendorError", - # Providers - "DeriveKeyProvider", - "RemoteAttestationProvider", - "PhalaDeriveKeyProvider", - "PhalaRemoteAttestationProvider", - "get_derived_keys", - "get_remote_attestation", - # Actions - "REMOTE_ATTESTATION_ACTION", - "handle_remote_attestation", - # Services - "TEEService", - # Vendors - "TeeVendorNames", - "TeeVendorInterface", - "PhalaVendor", - "get_vendor", - # Utils - "hex_to_bytes", - "bytes_to_hex", - "calculate_sha256", - "get_tee_endpoint", - "upload_attestation_quote", -] diff --git a/python/elizaos_plugin_tee/actions/__init__.py b/python/elizaos_plugin_tee/actions/__init__.py deleted file mode 100644 index 9ef53a6..0000000 --- a/python/elizaos_plugin_tee/actions/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from elizaos_plugin_tee.actions.remote_attestation import ( - REMOTE_ATTESTATION_ACTION, - handle_remote_attestation, -) - -__all__ = [ - "REMOTE_ATTESTATION_ACTION", - "handle_remote_attestation", -] diff --git a/python/elizaos_plugin_tee/actions/remote_attestation.py b/python/elizaos_plugin_tee/actions/remote_attestation.py deleted file mode 100644 index 3baf78d..0000000 --- a/python/elizaos_plugin_tee/actions/remote_attestation.py +++ /dev/null @@ -1,131 +0,0 @@ -from __future__ import annotations - -import logging -import time - -from elizaos_plugin_tee.providers.remote_attestation import PhalaRemoteAttestationProvider -from elizaos_plugin_tee.types import RemoteAttestationMessage, RemoteAttestationMessageContent -from elizaos_plugin_tee.utils import hex_to_bytes, upload_attestation_quote - -logger = logging.getLogger(__name__) - - -async def handle_remote_attestation( - tee_mode: str, - agent_id: str, - entity_id: str, - room_id: str, - content: str, -) -> dict[str, bool | str]: - try: - if not tee_mode: - logger.error("TEE_MODE is not configured") - return { - "success": False, - "text": "TEE_MODE is not configured. Cannot generate attestation.", - } - - attestation_message = RemoteAttestationMessage( - agent_id=agent_id, - timestamp=int(time.time() * 1000), - message=RemoteAttestationMessageContent( - entity_id=entity_id, - room_id=room_id, - content=content, - ), - ) - - provider = PhalaRemoteAttestationProvider(tee_mode) - try: - attestation = await provider.generate_attestation( - attestation_message.model_dump_json(by_alias=True) - ) - finally: - await provider.close() - - attestation_data = hex_to_bytes(attestation.quote) - upload_result = await upload_attestation_quote(attestation_data) - - proof_url = f"https://proof.t16z.com/reports/{upload_result['checksum']}" - - return { - "success": True, - "text": f"Remote attestation quote: {proof_url}", - } - - except Exception as e: - error_message = str(e) - logger.exception("Failed to generate remote attestation") - return { - "success": False, - "text": f"Failed to generate attestation: {error_message}", - } - - -REMOTE_ATTESTATION_ACTION = { - "name": "REMOTE_ATTESTATION", - "similes": [ - "REMOTE_ATTESTATION", - "TEE_REMOTE_ATTESTATION", - "TEE_ATTESTATION", - "TEE_QUOTE", - "ATTESTATION", - "TEE_ATTESTATION_QUOTE", - "PROVE_TEE", - "VERIFY_TEE", - ], - "description": ( - "Generate a remote attestation to prove that the agent is running in " - "a TEE (Trusted Execution Environment)" - ), - "examples": [ - [ - { - "name": "{{name1}}", - "content": { - "text": "If you are running in a TEE, generate a remote attestation", - }, - }, - { - "name": "{{agentName}}", - "content": { - "text": "Of course, one second...", - "actions": ["REMOTE_ATTESTATION"], - }, - }, - ], - [ - { - "name": "{{name1}}", - "content": { - "text": "Can you prove you're running in a trusted execution environment?", - }, - }, - { - "name": "{{agentName}}", - "content": { - "text": "Absolutely! Let me generate a TEE attestation quote for you.", - "actions": ["REMOTE_ATTESTATION"], - }, - }, - ], - [ - { - "name": "{{name1}}", - "content": { - "text": ( - "I need verification that this conversation is happening " - "in a secure enclave" - ), - }, - }, - { - "name": "{{agentName}}", - "content": { - "text": "I'll generate a remote attestation to prove I'm running in a TEE.", - "actions": ["REMOTE_ATTESTATION"], - }, - }, - ], - ], -} diff --git a/python/elizaos_plugin_tee/errors.py b/python/elizaos_plugin_tee/errors.py deleted file mode 100644 index 2c3c268..0000000 --- a/python/elizaos_plugin_tee/errors.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - - -class TeeError(Exception): - pass - - -class ConfigError(TeeError): - def __init__(self, message: str) -> None: - super().__init__(message) - - -class AttestationError(TeeError): - def __init__(self, message: str) -> None: - super().__init__(f"Failed to generate attestation: {message}") - - -class KeyDerivationError(TeeError): - def __init__(self, message: str) -> None: - super().__init__(f"Failed to derive key: {message}") - - -class NetworkError(TeeError): - def __init__(self, message: str) -> None: - super().__init__(f"Network error: {message}") - - -class InvalidModeError(ConfigError): - def __init__(self, mode: str) -> None: - super().__init__(f"Invalid TEE_MODE: {mode}. Must be one of: LOCAL, DOCKER, PRODUCTION") - - -class InvalidVendorError(ConfigError): - def __init__(self, vendor: str) -> None: - super().__init__(f"Invalid TEE_VENDOR: {vendor}. Must be one of: phala") diff --git a/python/elizaos_plugin_tee/providers/__init__.py b/python/elizaos_plugin_tee/providers/__init__.py deleted file mode 100644 index fb22cdf..0000000 --- a/python/elizaos_plugin_tee/providers/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -from elizaos_plugin_tee.providers.base import DeriveKeyProvider, RemoteAttestationProvider -from elizaos_plugin_tee.providers.derive_key import ( - PhalaDeriveKeyProvider, - get_derived_keys, -) -from elizaos_plugin_tee.providers.remote_attestation import ( - PhalaRemoteAttestationProvider, - get_remote_attestation, -) - -PHALA_DERIVE_KEY_PROVIDER_NAME = "phala-derive-key" -PHALA_REMOTE_ATTESTATION_PROVIDER_NAME = "phala-remote-attestation" - -__all__ = [ - "DeriveKeyProvider", - "RemoteAttestationProvider", - "PhalaDeriveKeyProvider", - "PhalaRemoteAttestationProvider", - "get_derived_keys", - "get_remote_attestation", - "PHALA_DERIVE_KEY_PROVIDER_NAME", - "PHALA_REMOTE_ATTESTATION_PROVIDER_NAME", -] diff --git a/python/elizaos_plugin_tee/providers/base.py b/python/elizaos_plugin_tee/providers/base.py deleted file mode 100644 index 2221de8..0000000 --- a/python/elizaos_plugin_tee/providers/base.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod - -from elizaos_plugin_tee.types import ( - DeriveKeyResult, - RemoteAttestationQuote, - TdxQuoteHashAlgorithm, -) - - -class DeriveKeyProvider(ABC): - @abstractmethod - async def raw_derive_key(self, path: str, subject: str) -> DeriveKeyResult: - pass - - -class RemoteAttestationProvider(ABC): - @abstractmethod - async def generate_attestation( - self, - report_data: str, - hash_algorithm: TdxQuoteHashAlgorithm | None = None, - ) -> RemoteAttestationQuote: - pass diff --git a/python/elizaos_plugin_tee/providers/derive_key.py b/python/elizaos_plugin_tee/providers/derive_key.py deleted file mode 100644 index 149df43..0000000 --- a/python/elizaos_plugin_tee/providers/derive_key.py +++ /dev/null @@ -1,200 +0,0 @@ -from __future__ import annotations - -import logging - -from eth_account import Account -from solders.keypair import Keypair - -from elizaos_plugin_tee.errors import KeyDerivationError -from elizaos_plugin_tee.providers.base import DeriveKeyProvider -from elizaos_plugin_tee.providers.remote_attestation import PhalaRemoteAttestationProvider -from elizaos_plugin_tee.types import ( - DeriveKeyAttestationData, - DeriveKeyResult, - EcdsaKeypairResult, - Ed25519KeypairResult, - RemoteAttestationQuote, -) -from elizaos_plugin_tee.utils import TeeClient, calculate_sha256, get_tee_endpoint - -logger = logging.getLogger(__name__) - - -class PhalaDeriveKeyProvider(DeriveKeyProvider): - def __init__(self, tee_mode: str) -> None: - endpoint = get_tee_endpoint(tee_mode) - logger.info( - f"TEE: Connecting to key derivation service at {endpoint}" - if endpoint - else "TEE: Running key derivation in production mode" - ) - self._client = TeeClient(endpoint) - self._ra_provider = PhalaRemoteAttestationProvider(tee_mode) - - async def _generate_derive_key_attestation( - self, - agent_id: str, - public_key: str, - subject: str | None = None, - ) -> RemoteAttestationQuote: - derive_key_data = DeriveKeyAttestationData( - agent_id=agent_id, - public_key=public_key, - subject=subject, - ) - return await self._ra_provider.generate_attestation( - derive_key_data.model_dump_json(by_alias=True) - ) - - async def raw_derive_key(self, path: str, subject: str) -> DeriveKeyResult: - if not path or not subject: - raise KeyDerivationError("Path and subject are required for key derivation") - - try: - key_bytes = await self._client.derive_key(path, subject) - return DeriveKeyResult( - key=key_bytes, - certificate_chain=[], - ) - except Exception as e: - logger.exception("Error deriving raw key") - raise KeyDerivationError(str(e)) from e - - async def derive_ed25519_keypair( - self, - path: str, - subject: str, - agent_id: str, - ) -> Ed25519KeypairResult: - if not path or not subject: - raise KeyDerivationError("Path and subject are required for key derivation") - - try: - derived_key = await self._client.derive_key(path, subject) - seed = calculate_sha256(derived_key)[:32] - keypair = Keypair.from_seed(seed) - public_key = str(keypair.pubkey()) - - attestation = await self._generate_derive_key_attestation( - agent_id, - public_key, - subject, - ) - - return Ed25519KeypairResult( - public_key=public_key, - secret_key=bytes(keypair), - attestation=attestation, - ) - except KeyDerivationError: - raise - except Exception as e: - logger.exception("Error deriving Ed25519 key") - raise KeyDerivationError(str(e)) from e - - async def derive_ecdsa_keypair( - self, - path: str, - subject: str, - agent_id: str, - ) -> EcdsaKeypairResult: - if not path or not subject: - raise KeyDerivationError("Path and subject are required for key derivation") - - try: - derived_key = await self._client.derive_key(path, subject) - from eth_hash.auto import keccak - - private_key_bytes = keccak(derived_key) - account = Account.from_key(private_key_bytes) - - attestation = await self._generate_derive_key_attestation( - agent_id, - account.address, - subject, - ) - - return EcdsaKeypairResult( - address=account.address, - private_key=private_key_bytes, - attestation=attestation, - ) - except KeyDerivationError: - raise - except Exception as e: - logger.exception("Error deriving ECDSA key") - raise KeyDerivationError(str(e)) from e - - async def close(self) -> None: - """Close the underlying HTTP clients.""" - await self._client.close() - await self._ra_provider.close() - - -async def get_derived_keys( - tee_mode: str, - secret_salt: str, - agent_id: str, -) -> dict[str, str | dict[str, str] | None]: - """ - Get derived keys for an agent. - - This is the provider function for elizaOS integration. - - Args: - tee_mode: The TEE operation mode. - secret_salt: The secret salt for key derivation. - agent_id: The agent ID. - - Returns: - The provider result with wallet data. - """ - if not tee_mode: - return { - "data": None, - "values": {}, - "text": "TEE_MODE is not configured", - } - - if not secret_salt: - logger.error("WALLET_SECRET_SALT is not configured") - return { - "data": None, - "values": {}, - "text": "WALLET_SECRET_SALT is not configured in settings", - } - - provider = PhalaDeriveKeyProvider(tee_mode) - - try: - solana_keypair = await provider.derive_ed25519_keypair(secret_salt, "solana", agent_id) - evm_keypair = await provider.derive_ecdsa_keypair(secret_salt, "evm", agent_id) - - wallet_data = { - "solana": solana_keypair.public_key, - "evm": evm_keypair.address, - } - - values = { - "solana_public_key": solana_keypair.public_key, - "evm_address": evm_keypair.address, - } - - text = f"Solana Public Key: {values['solana_public_key']}\nEVM Address: {values['evm_address']}" - - return { - "data": wallet_data, - "values": values, - "text": text, - } - - except Exception as e: - logger.exception("Error in derive key provider") - return { - "data": None, - "values": {}, - "text": f"Failed to derive keys: {e}", - } - - finally: - await provider.close() diff --git a/python/elizaos_plugin_tee/providers/remote_attestation.py b/python/elizaos_plugin_tee/providers/remote_attestation.py deleted file mode 100644 index bfbc1e4..0000000 --- a/python/elizaos_plugin_tee/providers/remote_attestation.py +++ /dev/null @@ -1,97 +0,0 @@ -from __future__ import annotations - -import json -import logging -import time - -from elizaos_plugin_tee.errors import AttestationError -from elizaos_plugin_tee.providers.base import RemoteAttestationProvider -from elizaos_plugin_tee.types import ( - RemoteAttestationQuote, - TdxQuoteHashAlgorithm, -) -from elizaos_plugin_tee.utils import TeeClient, get_tee_endpoint - -logger = logging.getLogger(__name__) - - -class PhalaRemoteAttestationProvider(RemoteAttestationProvider): - def __init__(self, tee_mode: str) -> None: - endpoint = get_tee_endpoint(tee_mode) - logger.info( - f"TEE: Connecting to simulator at {endpoint}" - if endpoint - else "TEE: Running in production mode without simulator" - ) - self._client = TeeClient(endpoint) - - async def generate_attestation( - self, - report_data: str, - hash_algorithm: TdxQuoteHashAlgorithm | None = None, - ) -> RemoteAttestationQuote: - try: - hash_algo = hash_algorithm.value if hash_algorithm else None - result = await self._client.tdx_quote(report_data, hash_algo) - - quote = RemoteAttestationQuote( - quote=str(result["quote"]), - timestamp=int(time.time() * 1000), - ) - - return quote - except Exception as e: - logger.exception("Error generating remote attestation") - raise AttestationError(str(e)) from e - - async def close(self) -> None: - await self._client.close() - - -async def get_remote_attestation( - tee_mode: str, - agent_id: str, - entity_id: str, - room_id: str, - content: str, -) -> dict[str, str | dict[str, str] | None]: - if not tee_mode: - return { - "data": None, - "values": {}, - "text": "TEE_MODE is not configured", - } - - provider = PhalaRemoteAttestationProvider(tee_mode) - - try: - attestation_message = { - "agentId": agent_id, - "timestamp": int(time.time() * 1000), - "message": { - "entityId": entity_id, - "roomId": room_id, - "content": content, - }, - } - - attestation = await provider.generate_attestation(json.dumps(attestation_message)) - - return { - "data": { - "quote": attestation.quote, - "timestamp": str(attestation.timestamp), - }, - "values": { - "quote": attestation.quote, - "timestamp": str(attestation.timestamp), - }, - "text": f"Remote attestation: {attestation.quote[:64]}...", - } - - except Exception as e: - logger.exception("Error in remote attestation provider") - raise AttestationError(str(e)) from e - - finally: - await provider.close() diff --git a/python/elizaos_plugin_tee/py.typed b/python/elizaos_plugin_tee/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/python/elizaos_plugin_tee/services/__init__.py b/python/elizaos_plugin_tee/services/__init__.py deleted file mode 100644 index 6b49982..0000000 --- a/python/elizaos_plugin_tee/services/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from elizaos_plugin_tee.services.tee import TEEService - -__all__ = [ - "TEEService", -] diff --git a/python/elizaos_plugin_tee/services/tee.py b/python/elizaos_plugin_tee/services/tee.py deleted file mode 100644 index 26b22e4..0000000 --- a/python/elizaos_plugin_tee/services/tee.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import logging - -from elizaos_plugin_tee.providers.derive_key import PhalaDeriveKeyProvider -from elizaos_plugin_tee.types import ( - DeriveKeyResult, - EcdsaKeypairResult, - Ed25519KeypairResult, - TeeMode, - TeeServiceConfig, - TeeVendor, -) - -logger = logging.getLogger(__name__) - - -class TEEService: - service_type = "tee" - capability_description = "Trusted Execution Environment for secure key management" - - def __init__( - self, - mode: TeeMode | str = TeeMode.LOCAL, - vendor: TeeVendor = TeeVendor.PHALA, - secret_salt: str | None = None, - ) -> None: - if isinstance(mode, str): - mode = TeeMode(mode.upper()) - - self.config = TeeServiceConfig( - mode=mode, - vendor=vendor, - secret_salt=secret_salt, - ) - - self._provider = PhalaDeriveKeyProvider(mode.value) - logger.info("TEE service initialized with mode: %s, vendor: %s", mode.value, vendor.value) - - @classmethod - async def start( - cls, - tee_mode: str | None = None, - secret_salt: str | None = None, - ) -> TEEService: - mode = TeeMode(tee_mode.upper()) if tee_mode else TeeMode.LOCAL - logger.info("Starting TEE service with mode: %s", mode.value) - return cls(mode=mode, secret_salt=secret_salt) - - async def stop(self) -> None: - logger.info("Stopping TEE service") - await self._provider.close() - - async def derive_ecdsa_keypair( - self, - path: str, - subject: str, - agent_id: str, - ) -> EcdsaKeypairResult: - return await self._provider.derive_ecdsa_keypair(path, subject, agent_id) - - async def derive_ed25519_keypair( - self, - path: str, - subject: str, - agent_id: str, - ) -> Ed25519KeypairResult: - return await self._provider.derive_ed25519_keypair(path, subject, agent_id) - - async def raw_derive_key(self, path: str, subject: str) -> DeriveKeyResult: - return await self._provider.raw_derive_key(path, subject) diff --git a/python/elizaos_plugin_tee/types.py b/python/elizaos_plugin_tee/types.py deleted file mode 100644 index ab8caaf..0000000 --- a/python/elizaos_plugin_tee/types.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Core types for the TEE plugin.""" - -from __future__ import annotations - -from enum import Enum -from typing import TypeAlias - -from pydantic import BaseModel, ConfigDict, Field - - -class TeeMode(str, Enum): - LOCAL = "LOCAL" - DOCKER = "DOCKER" - PRODUCTION = "PRODUCTION" - - -class TeeVendor(str, Enum): - PHALA = "phala" - - -class TeeType(str, Enum): - SGX_GRAMINE = "sgx_gramine" - TDX_DSTACK = "tdx_dstack" - - -class TdxQuoteHashAlgorithm(str, Enum): - SHA256 = "sha256" - SHA384 = "sha384" - SHA512 = "sha512" - RAW = "raw" - - -class RemoteAttestationQuote(BaseModel): - quote: str - timestamp: int - - -class DeriveKeyAttestationData(BaseModel): - model_config = ConfigDict(populate_by_name=True) - - agent_id: str = Field(alias="agentId") - public_key: str = Field(alias="publicKey") - subject: str | None = None - - -class RemoteAttestationMessageContent(BaseModel): - model_config = ConfigDict(populate_by_name=True) - - entity_id: str = Field(alias="entityId") - room_id: str = Field(alias="roomId") - content: str - - -class RemoteAttestationMessage(BaseModel): - model_config = ConfigDict(populate_by_name=True) - - agent_id: str = Field(alias="agentId") - timestamp: int - message: RemoteAttestationMessageContent - - -class DeriveKeyResult(BaseModel): - key: bytes - certificate_chain: list[str] - - -class Ed25519KeypairResult(BaseModel): - public_key: str - secret_key: bytes - attestation: RemoteAttestationQuote - - -class EcdsaKeypairResult(BaseModel): - address: str - private_key: bytes - attestation: RemoteAttestationQuote - - -class TeeServiceConfig(BaseModel): - mode: TeeMode - vendor: TeeVendor = TeeVendor.PHALA - secret_salt: str | None = None - - -class TeeProviderResult(BaseModel): - data: dict[str, str] | None = None - values: dict[str, str] = Field(default_factory=dict) - text: str - - -# Type alias for UUID strings -UUID: TypeAlias = str - - -def parse_tee_mode(mode: str) -> TeeMode: - """ - Validate TEE mode string. - - Args: - mode: The mode string to parse. - - Returns: - The validated TeeMode enum value. - - Raises: - ValueError: If the mode is invalid. - """ - mode_upper = mode.upper() - if mode_upper == "LOCAL": - return TeeMode.LOCAL - if mode_upper == "DOCKER": - return TeeMode.DOCKER - if mode_upper == "PRODUCTION": - return TeeMode.PRODUCTION - raise ValueError(f"Invalid TEE_MODE: {mode}. Must be one of: LOCAL, DOCKER, PRODUCTION") - - -def parse_tee_vendor(vendor: str) -> TeeVendor: - vendor_lower = vendor.lower() - if vendor_lower == "phala": - return TeeVendor.PHALA - raise ValueError(f"Invalid TEE_VENDOR: {vendor}. Must be one of: phala") diff --git a/python/elizaos_plugin_tee/utils.py b/python/elizaos_plugin_tee/utils.py deleted file mode 100644 index 0311f62..0000000 --- a/python/elizaos_plugin_tee/utils.py +++ /dev/null @@ -1,81 +0,0 @@ -from __future__ import annotations - -import hashlib - -import httpx - - -def hex_to_bytes(hex_str: str) -> bytes: - hex_string = hex_str.strip().removeprefix("0x") - if not hex_string: - raise ValueError("Invalid hex string: empty after stripping prefix") - if len(hex_string) % 2 != 0: - raise ValueError("Invalid hex string: odd number of characters") - - try: - return bytes.fromhex(hex_string) - except ValueError as e: - raise ValueError(f"Invalid hex string: {e}") from e - - -def bytes_to_hex(data: bytes) -> str: - return data.hex() - - -def calculate_sha256(data: str | bytes) -> bytes: - if isinstance(data, str): - data = data.encode("utf-8") - return hashlib.sha256(data).digest() - - -def get_tee_endpoint(mode: str) -> str | None: - mode_upper = mode.upper() - if mode_upper == "LOCAL": - return "http://localhost:8090" - if mode_upper == "DOCKER": - return "http://host.docker.internal:8090" - if mode_upper == "PRODUCTION": - return None - raise ValueError(f"Invalid TEE_MODE: {mode}. Must be one of: LOCAL, DOCKER, PRODUCTION") - - -async def upload_attestation_quote(data: bytes) -> dict[str, str]: - async with httpx.AsyncClient() as client: - files = {"file": ("quote.bin", data, "application/octet-stream")} - response = await client.post("https://proof.t16z.com/api/upload", files=files) - response.raise_for_status() - return response.json() - - -class TeeClient: - def __init__(self, endpoint: str | None = None) -> None: - self.endpoint = endpoint or "https://api.phala.network/tee" - self._client = httpx.AsyncClient(timeout=30.0) - - async def derive_key(self, path: str, subject: str) -> bytes: - response = await self._client.post( - f"{self.endpoint}/derive-key", - json={"path": path, "subject": subject}, - ) - response.raise_for_status() - result = response.json() - return bytes.fromhex(result["key"]) - - async def tdx_quote( - self, - report_data: str, - hash_algorithm: str | None = None, - ) -> dict[str, str | list[str]]: - payload: dict[str, str] = {"reportData": report_data} - if hash_algorithm: - payload["hashAlgorithm"] = hash_algorithm - - response = await self._client.post( - f"{self.endpoint}/tdx-quote", - json=payload, - ) - response.raise_for_status() - return response.json() - - async def close(self) -> None: - await self._client.aclose() diff --git a/python/elizaos_plugin_tee/vendors/__init__.py b/python/elizaos_plugin_tee/vendors/__init__.py deleted file mode 100644 index f1f4b07..0000000 --- a/python/elizaos_plugin_tee/vendors/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from elizaos_plugin_tee.vendors.phala import PhalaVendor -from elizaos_plugin_tee.vendors.types import TeeVendorInterface, TeeVendorNames - -_vendors: dict[str, TeeVendorInterface] = { - TeeVendorNames.PHALA: PhalaVendor(), -} - - -def get_vendor(vendor_type: str) -> TeeVendorInterface: - vendor = _vendors.get(vendor_type) - if not vendor: - raise ValueError(f"Unsupported TEE vendor: {vendor_type}") - return vendor - - -__all__ = [ - "TeeVendorNames", - "TeeVendorInterface", - "PhalaVendor", - "get_vendor", -] diff --git a/python/elizaos_plugin_tee/vendors/phala.py b/python/elizaos_plugin_tee/vendors/phala.py deleted file mode 100644 index 93d551f..0000000 --- a/python/elizaos_plugin_tee/vendors/phala.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import annotations - -from elizaos_plugin_tee.actions.remote_attestation import REMOTE_ATTESTATION_ACTION -from elizaos_plugin_tee.providers import ( - get_derived_keys, - get_remote_attestation, -) -from elizaos_plugin_tee.vendors.types import TeeVendorInterface, TeeVendorNames - - -class PhalaVendor(TeeVendorInterface): - @property - def type(self) -> str: - return TeeVendorNames.PHALA - - def get_actions(self) -> list[dict[str, object]]: - return [REMOTE_ATTESTATION_ACTION] - - def get_providers(self) -> list[object]: - return [get_derived_keys, get_remote_attestation] - - def get_name(self) -> str: - return "phala-tee-plugin" - - def get_description(self) -> str: - return "Phala Network TEE for secure agent execution" diff --git a/python/elizaos_plugin_tee/vendors/types.py b/python/elizaos_plugin_tee/vendors/types.py deleted file mode 100644 index 66a6a1a..0000000 --- a/python/elizaos_plugin_tee/vendors/types.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod - - -class TeeVendorNames: - PHALA = "phala" - - -class TeeVendorInterface(ABC): - @property - @abstractmethod - def type(self) -> str: - pass - - @abstractmethod - def get_actions(self) -> list[dict[str, object]]: - pass - - @abstractmethod - def get_providers(self) -> list[object]: - pass - - @abstractmethod - def get_name(self) -> str: - pass - - @abstractmethod - def get_description(self) -> str: - pass diff --git a/python/pyproject.toml b/python/pyproject.toml deleted file mode 100644 index d1a82ec..0000000 --- a/python/pyproject.toml +++ /dev/null @@ -1,114 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "elizaos-plugin-tee" -version = "2.0.0a4" -description = "elizaOS TEE Plugin - Trusted Execution Environment integration for secure key management and remote attestation" -readme = "README.md" -license = "MIT" -requires-python = ">=3.11" -authors = [ - { name = "elizaOS Contributors" } -] -keywords = ["ai", "agents", "tee", "attestation", "security", "phala", "elizaos"] -classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Security :: Cryptography", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Typing :: Typed", -] -dependencies = [ - "httpx>=0.27.0", - "pydantic>=2.10.0", - "cryptography>=43.0.0", - "pynacl>=1.5.0", - "web3>=7.0.0", - "eth-account>=0.13.0", - "solders>=0.21.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0.0", - "pytest-asyncio>=0.24.0", - "pytest-cov>=6.0.0", - "pytest-xprocess>=1.0.2", - "mypy>=1.19.0", - "ruff>=0.14.0", - "python-dotenv>=1.0.0", -] - -[project.urls] -Homepage = "https://github.com/elizaos/eliza" -Documentation = "https://elizaos.ai/docs" -Repository = "https://github.com/elizaos/eliza" - -[tool.hatch.build.targets.wheel] -packages = ["elizaos_plugin_tee"] - -[tool.hatch.build.targets.sdist] -include = [ - "/elizaos_plugin_tee", - "/tests", - "/README.md", -] - -[tool.pytest.ini_options] -testpaths = ["tests"] -python_files = "test_*.py" -python_functions = "test_*" -addopts = "-v -p no:anchorpy" -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "function" - -[tool.mypy] -python_version = "3.11" -strict = true -warn_return_any = true -warn_unused_ignores = true -disallow_untyped_defs = true -disallow_incomplete_defs = true -check_untyped_defs = true -no_implicit_optional = true -disallow_any_generics = true -disallow_subclassing_any = true - -[tool.ruff] -target-version = "py311" -line-length = 100 - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # Pyflakes - "I", # isort - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "UP", # pyupgrade - "ANN", # flake8-annotations - "S", # flake8-bandit - "T20", # flake8-print -] -ignore = [ - "E501", # line too long (handled by formatter) - "ANN101", # missing type for self - "ANN102", # missing type for cls - "S101", # use of assert (needed for tests) -] - -[tool.ruff.lint.isort] -known-first-party = ["elizaos_plugin_tee"] - - - - - - - diff --git a/python/tests/__init__.py b/python/tests/__init__.py deleted file mode 100644 index 63d5cb2..0000000 --- a/python/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for elizaos-plugin-tee.""" diff --git a/python/tests/conftest.py b/python/tests/conftest.py deleted file mode 100644 index 1e5921c..0000000 --- a/python/tests/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import annotations - -import pytest - - -@pytest.fixture -def tee_mode() -> str: - return "LOCAL" - - -@pytest.fixture -def agent_id() -> str: - return "test-agent-id-12345" - - -@pytest.fixture -def secret_salt() -> str: - return "test-secret-salt" diff --git a/python/tests/test_actions.py b/python/tests/test_actions.py deleted file mode 100644 index 38c3841..0000000 --- a/python/tests/test_actions.py +++ /dev/null @@ -1,205 +0,0 @@ -"""Tests for TEE remote attestation action. - -These tests validate the action metadata, handler error paths, -and successful attestation generation using mocks. -""" - -from __future__ import annotations - -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest - -from elizaos_plugin_tee.actions.remote_attestation import ( - REMOTE_ATTESTATION_ACTION, - handle_remote_attestation, -) -from elizaos_plugin_tee.types import RemoteAttestationQuote - - -# --------------------------------------------------------------------------- -# Action metadata tests -# --------------------------------------------------------------------------- - - -class TestRemoteAttestationActionMetadata: - def test_action_name(self) -> None: - assert REMOTE_ATTESTATION_ACTION["name"] == "REMOTE_ATTESTATION" - - def test_action_has_similes(self) -> None: - similes = REMOTE_ATTESTATION_ACTION["similes"] - assert isinstance(similes, list) - assert len(similes) > 0 - assert "REMOTE_ATTESTATION" in similes - assert "TEE_ATTESTATION" in similes - assert "PROVE_TEE" in similes - - def test_action_description_mentions_tee(self) -> None: - desc = REMOTE_ATTESTATION_ACTION["description"] - assert "TEE" in desc - assert "attestation" in desc.lower() - - def test_action_has_examples(self) -> None: - examples = REMOTE_ATTESTATION_ACTION["examples"] - assert isinstance(examples, list) - assert len(examples) >= 2 - # Each example should be a list of message pairs - for example in examples: - assert isinstance(example, list) - assert len(example) == 2 - - def test_action_examples_contain_action_name(self) -> None: - """At least one example response should reference REMOTE_ATTESTATION.""" - examples = REMOTE_ATTESTATION_ACTION["examples"] - found = False - for example in examples: - for msg in example: - actions = msg.get("content", {}).get("actions", []) - if "REMOTE_ATTESTATION" in actions: - found = True - break - assert found, "No example references the REMOTE_ATTESTATION action" - - -# --------------------------------------------------------------------------- -# Handler error-path tests -# --------------------------------------------------------------------------- - - -class TestHandleRemoteAttestationErrors: - @pytest.mark.asyncio - async def test_returns_failure_when_tee_mode_empty(self) -> None: - result = await handle_remote_attestation( - tee_mode="", - agent_id="agent-1", - entity_id="entity-1", - room_id="room-1", - content="hello", - ) - assert result["success"] is False - assert "TEE_MODE" in result["text"] - - @pytest.mark.asyncio - async def test_returns_failure_when_tee_mode_none(self) -> None: - result = await handle_remote_attestation( - tee_mode="", # falsy - agent_id="agent-1", - entity_id="entity-1", - room_id="room-1", - content="test", - ) - assert result["success"] is False - - @pytest.mark.asyncio - async def test_returns_failure_on_provider_error(self) -> None: - """When the provider raises, the handler catches and returns failure.""" - with patch( - "elizaos_plugin_tee.actions.remote_attestation.PhalaRemoteAttestationProvider" - ) as MockProvider: - instance = AsyncMock() - instance.generate_attestation.side_effect = RuntimeError("connection failed") - instance.close = AsyncMock() - MockProvider.return_value = instance - - result = await handle_remote_attestation( - tee_mode="LOCAL", - agent_id="agent-1", - entity_id="entity-1", - room_id="room-1", - content="test", - ) - - assert result["success"] is False - assert "connection failed" in result["text"] - - -# --------------------------------------------------------------------------- -# Handler success-path tests -# --------------------------------------------------------------------------- - - -class TestHandleRemoteAttestationSuccess: - @pytest.mark.asyncio - async def test_successful_attestation_returns_proof_url(self) -> None: - mock_attestation = RemoteAttestationQuote( - quote="aabbccdd", - timestamp=1700000000000, - ) - - with ( - patch( - "elizaos_plugin_tee.actions.remote_attestation.PhalaRemoteAttestationProvider" - ) as MockProvider, - patch( - "elizaos_plugin_tee.actions.remote_attestation.upload_attestation_quote" - ) as mock_upload, - ): - provider_instance = AsyncMock() - provider_instance.generate_attestation.return_value = mock_attestation - provider_instance.close = AsyncMock() - MockProvider.return_value = provider_instance - - mock_upload.return_value = {"checksum": "abc123checksum"} - - result = await handle_remote_attestation( - tee_mode="LOCAL", - agent_id="agent-1", - entity_id="entity-1", - room_id="room-1", - content="prove it", - ) - - assert result["success"] is True - assert "https://proof.t16z.com/reports/abc123checksum" in result["text"] - - @pytest.mark.asyncio - async def test_provider_is_closed_after_success(self) -> None: - mock_attestation = RemoteAttestationQuote( - quote="aabbccdd", - timestamp=1700000000000, - ) - - with ( - patch( - "elizaos_plugin_tee.actions.remote_attestation.PhalaRemoteAttestationProvider" - ) as MockProvider, - patch( - "elizaos_plugin_tee.actions.remote_attestation.upload_attestation_quote" - ) as mock_upload, - ): - provider_instance = AsyncMock() - provider_instance.generate_attestation.return_value = mock_attestation - provider_instance.close = AsyncMock() - MockProvider.return_value = provider_instance - - mock_upload.return_value = {"checksum": "xyz"} - - await handle_remote_attestation( - tee_mode="LOCAL", - agent_id="agent-1", - entity_id="entity-1", - room_id="room-1", - content="test", - ) - - provider_instance.close.assert_awaited_once() - - @pytest.mark.asyncio - async def test_provider_is_closed_even_on_error(self) -> None: - with patch( - "elizaos_plugin_tee.actions.remote_attestation.PhalaRemoteAttestationProvider" - ) as MockProvider: - provider_instance = AsyncMock() - provider_instance.generate_attestation.side_effect = RuntimeError("boom") - provider_instance.close = AsyncMock() - MockProvider.return_value = provider_instance - - await handle_remote_attestation( - tee_mode="LOCAL", - agent_id="agent-1", - entity_id="entity-1", - room_id="room-1", - content="test", - ) - - provider_instance.close.assert_awaited_once() diff --git a/python/tests/test_providers.py b/python/tests/test_providers.py deleted file mode 100644 index 8d59181..0000000 --- a/python/tests/test_providers.py +++ /dev/null @@ -1,382 +0,0 @@ -"""Tests for TEE providers (DeriveKey and RemoteAttestation). - -These tests validate provider metadata, error paths, and successful -operation using mocked HTTP clients. -""" - -from __future__ import annotations - -from unittest.mock import AsyncMock, patch - -import pytest - -from elizaos_plugin_tee.errors import AttestationError, KeyDerivationError -from elizaos_plugin_tee.providers.derive_key import ( - PhalaDeriveKeyProvider, - get_derived_keys, -) -from elizaos_plugin_tee.providers.remote_attestation import ( - PhalaRemoteAttestationProvider, - get_remote_attestation, -) -from elizaos_plugin_tee.types import RemoteAttestationQuote - - -# =========================================================================== -# PhalaRemoteAttestationProvider tests -# =========================================================================== - - -class TestPhalaRemoteAttestationProvider: - def test_provider_creates_with_local_mode(self) -> None: - provider = PhalaRemoteAttestationProvider("LOCAL") - assert provider._client.endpoint == "http://localhost:8090" - - def test_provider_creates_with_docker_mode(self) -> None: - provider = PhalaRemoteAttestationProvider("DOCKER") - assert provider._client.endpoint == "http://host.docker.internal:8090" - - def test_provider_creates_with_production_mode(self) -> None: - provider = PhalaRemoteAttestationProvider("PRODUCTION") - # Production uses the default endpoint - assert provider._client.endpoint == "https://api.phala.network/tee" - - def test_provider_raises_on_invalid_mode(self) -> None: - with pytest.raises(ValueError, match="Invalid TEE_MODE"): - PhalaRemoteAttestationProvider("INVALID") - - @pytest.mark.asyncio - async def test_generate_attestation_calls_tdx_quote(self) -> None: - provider = PhalaRemoteAttestationProvider("LOCAL") - provider._client = AsyncMock() - provider._client.tdx_quote.return_value = {"quote": "deadbeef"} - - quote = await provider.generate_attestation("test-report-data") - - provider._client.tdx_quote.assert_awaited_once_with("test-report-data", None) - assert isinstance(quote, RemoteAttestationQuote) - assert quote.quote == "deadbeef" - assert quote.timestamp > 0 - - @pytest.mark.asyncio - async def test_generate_attestation_with_hash_algorithm(self) -> None: - from elizaos_plugin_tee.types import TdxQuoteHashAlgorithm - - provider = PhalaRemoteAttestationProvider("LOCAL") - provider._client = AsyncMock() - provider._client.tdx_quote.return_value = {"quote": "cafe"} - - quote = await provider.generate_attestation( - "data", hash_algorithm=TdxQuoteHashAlgorithm.SHA256 - ) - - provider._client.tdx_quote.assert_awaited_once_with("data", "sha256") - assert quote.quote == "cafe" - - @pytest.mark.asyncio - async def test_generate_attestation_raises_attestation_error(self) -> None: - provider = PhalaRemoteAttestationProvider("LOCAL") - provider._client = AsyncMock() - provider._client.tdx_quote.side_effect = RuntimeError("network timeout") - - with pytest.raises(AttestationError, match="network timeout"): - await provider.generate_attestation("data") - - @pytest.mark.asyncio - async def test_close_delegates_to_client(self) -> None: - provider = PhalaRemoteAttestationProvider("LOCAL") - provider._client = AsyncMock() - - await provider.close() - - provider._client.close.assert_awaited_once() - - -# =========================================================================== -# get_remote_attestation provider-function tests -# =========================================================================== - - -class TestGetRemoteAttestation: - @pytest.mark.asyncio - async def test_returns_not_configured_when_tee_mode_empty(self) -> None: - result = await get_remote_attestation( - tee_mode="", - agent_id="a1", - entity_id="e1", - room_id="r1", - content="hi", - ) - assert result["data"] is None - assert "TEE_MODE" in result["text"] - - @pytest.mark.asyncio - async def test_returns_attestation_data_on_success(self) -> None: - mock_quote = RemoteAttestationQuote( - quote="aabbccdd" * 8, # long enough to slice - timestamp=1700000000000, - ) - - with patch( - "elizaos_plugin_tee.providers.remote_attestation.PhalaRemoteAttestationProvider" - ) as MockProvider: - instance = AsyncMock() - instance.generate_attestation.return_value = mock_quote - instance.close = AsyncMock() - MockProvider.return_value = instance - - result = await get_remote_attestation( - tee_mode="LOCAL", - agent_id="a1", - entity_id="e1", - room_id="r1", - content="hello", - ) - - assert result["data"] is not None - assert result["data"]["quote"] == mock_quote.quote - assert result["data"]["timestamp"] == str(mock_quote.timestamp) - assert result["values"]["quote"] == mock_quote.quote - assert "Remote attestation:" in result["text"] - - @pytest.mark.asyncio - async def test_raises_attestation_error_on_failure(self) -> None: - with patch( - "elizaos_plugin_tee.providers.remote_attestation.PhalaRemoteAttestationProvider" - ) as MockProvider: - instance = AsyncMock() - instance.generate_attestation.side_effect = RuntimeError("fail") - instance.close = AsyncMock() - MockProvider.return_value = instance - - with pytest.raises(AttestationError): - await get_remote_attestation( - tee_mode="LOCAL", - agent_id="a1", - entity_id="e1", - room_id="r1", - content="hello", - ) - - -# =========================================================================== -# PhalaDeriveKeyProvider tests -# =========================================================================== - - -class TestPhalaDeriveKeyProvider: - def test_creates_with_local_mode(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - assert provider._client.endpoint == "http://localhost:8090" - - def test_creates_with_docker_mode(self) -> None: - provider = PhalaDeriveKeyProvider("DOCKER") - assert provider._client.endpoint == "http://host.docker.internal:8090" - - def test_raises_on_invalid_mode(self) -> None: - with pytest.raises(ValueError, match="Invalid TEE_MODE"): - PhalaDeriveKeyProvider("BOGUS") - - @pytest.mark.asyncio - async def test_raw_derive_key_rejects_empty_path(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - with pytest.raises(KeyDerivationError): - await provider.raw_derive_key("", "subject") - - @pytest.mark.asyncio - async def test_raw_derive_key_rejects_empty_subject(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - with pytest.raises(KeyDerivationError): - await provider.raw_derive_key("/path", "") - - @pytest.mark.asyncio - async def test_derive_ed25519_rejects_empty_path(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - with pytest.raises(KeyDerivationError): - await provider.derive_ed25519_keypair("", "subject", "agent-1") - - @pytest.mark.asyncio - async def test_derive_ed25519_rejects_empty_subject(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - with pytest.raises(KeyDerivationError): - await provider.derive_ed25519_keypair("/path", "", "agent-1") - - @pytest.mark.asyncio - async def test_derive_ecdsa_rejects_empty_path(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - with pytest.raises(KeyDerivationError): - await provider.derive_ecdsa_keypair("", "subject", "agent-1") - - @pytest.mark.asyncio - async def test_derive_ecdsa_rejects_empty_subject(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - with pytest.raises(KeyDerivationError): - await provider.derive_ecdsa_keypair("/path", "", "agent-1") - - @pytest.mark.asyncio - async def test_raw_derive_key_success(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - provider._client = AsyncMock() - provider._client.derive_key.return_value = b"\x01\x02\x03\x04" * 8 - - result = await provider.raw_derive_key("/my/path", "my-subject") - - provider._client.derive_key.assert_awaited_once_with("/my/path", "my-subject") - assert result.key == b"\x01\x02\x03\x04" * 8 - assert result.certificate_chain == [] - - @pytest.mark.asyncio - async def test_raw_derive_key_wraps_client_error(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - provider._client = AsyncMock() - provider._client.derive_key.side_effect = RuntimeError("http 500") - - with pytest.raises(KeyDerivationError, match="http 500"): - await provider.raw_derive_key("/path", "subject") - - @pytest.mark.asyncio - async def test_derive_ed25519_returns_keypair_result(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - provider._client = AsyncMock() - # Return 32 bytes of key material - provider._client.derive_key.return_value = b"\xab" * 32 - - mock_quote = RemoteAttestationQuote(quote="abcdef", timestamp=100) - provider._ra_provider = AsyncMock() - provider._ra_provider.generate_attestation.return_value = mock_quote - - result = await provider.derive_ed25519_keypair("/salt", "solana", "agent-1") - - assert result.public_key # non-empty base58 public key - assert len(result.secret_key) > 0 - assert result.attestation.quote == "abcdef" - - @pytest.mark.asyncio - async def test_derive_ecdsa_returns_keypair_result(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - provider._client = AsyncMock() - provider._client.derive_key.return_value = b"\xcd" * 32 - - mock_quote = RemoteAttestationQuote(quote="deadbeef", timestamp=200) - provider._ra_provider = AsyncMock() - provider._ra_provider.generate_attestation.return_value = mock_quote - - result = await provider.derive_ecdsa_keypair("/salt", "evm", "agent-1") - - assert result.address.startswith("0x") - assert len(result.private_key) == 32 - assert result.attestation.quote == "deadbeef" - - @pytest.mark.asyncio - async def test_close_closes_both_clients(self) -> None: - provider = PhalaDeriveKeyProvider("LOCAL") - provider._client = AsyncMock() - provider._ra_provider = AsyncMock() - - await provider.close() - - provider._client.close.assert_awaited_once() - provider._ra_provider.close.assert_awaited_once() - - -# =========================================================================== -# get_derived_keys provider-function tests -# =========================================================================== - - -class TestGetDerivedKeys: - @pytest.mark.asyncio - async def test_returns_not_configured_when_tee_mode_empty(self) -> None: - result = await get_derived_keys(tee_mode="", secret_salt="salt", agent_id="a1") - assert result["data"] is None - assert "TEE_MODE" in result["text"] - - @pytest.mark.asyncio - async def test_returns_not_configured_when_salt_empty(self) -> None: - result = await get_derived_keys(tee_mode="LOCAL", secret_salt="", agent_id="a1") - assert result["data"] is None - assert "WALLET_SECRET_SALT" in result["text"] - - @pytest.mark.asyncio - async def test_returns_wallet_data_on_success(self) -> None: - mock_quote = RemoteAttestationQuote(quote="ff", timestamp=0) - - with patch( - "elizaos_plugin_tee.providers.derive_key.PhalaDeriveKeyProvider" - ) as MockProvider: - instance = AsyncMock() - - # Mock Ed25519 keypair result - ed25519_result = AsyncMock() - ed25519_result.public_key = "SolPubKey123" - - # Mock ECDSA keypair result - ecdsa_result = AsyncMock() - ecdsa_result.address = "0xEVMAddress456" - - instance.derive_ed25519_keypair.return_value = ed25519_result - instance.derive_ecdsa_keypair.return_value = ecdsa_result - instance.close = AsyncMock() - MockProvider.return_value = instance - - result = await get_derived_keys( - tee_mode="LOCAL", secret_salt="my-salt", agent_id="agent-1" - ) - - assert result["data"] is not None - assert result["data"]["solana"] == "SolPubKey123" - assert result["data"]["evm"] == "0xEVMAddress456" - assert result["values"]["solana_public_key"] == "SolPubKey123" - assert result["values"]["evm_address"] == "0xEVMAddress456" - assert "SolPubKey123" in result["text"] - assert "0xEVMAddress456" in result["text"] - - @pytest.mark.asyncio - async def test_returns_failure_text_on_derivation_error(self) -> None: - with patch( - "elizaos_plugin_tee.providers.derive_key.PhalaDeriveKeyProvider" - ) as MockProvider: - instance = AsyncMock() - instance.derive_ed25519_keypair.side_effect = RuntimeError("derive failed") - instance.close = AsyncMock() - MockProvider.return_value = instance - - result = await get_derived_keys( - tee_mode="LOCAL", secret_salt="salt", agent_id="agent-1" - ) - - assert result["data"] is None - assert "Failed to derive keys" in result["text"] - - @pytest.mark.asyncio - async def test_provider_is_closed_on_success(self) -> None: - with patch( - "elizaos_plugin_tee.providers.derive_key.PhalaDeriveKeyProvider" - ) as MockProvider: - instance = AsyncMock() - ed_result = AsyncMock() - ed_result.public_key = "pk" - ec_result = AsyncMock() - ec_result.address = "0x123" - instance.derive_ed25519_keypair.return_value = ed_result - instance.derive_ecdsa_keypair.return_value = ec_result - instance.close = AsyncMock() - MockProvider.return_value = instance - - await get_derived_keys(tee_mode="LOCAL", secret_salt="s", agent_id="a") - - instance.close.assert_awaited_once() - - @pytest.mark.asyncio - async def test_provider_is_closed_on_error(self) -> None: - with patch( - "elizaos_plugin_tee.providers.derive_key.PhalaDeriveKeyProvider" - ) as MockProvider: - instance = AsyncMock() - instance.derive_ed25519_keypair.side_effect = RuntimeError("boom") - instance.close = AsyncMock() - MockProvider.return_value = instance - - await get_derived_keys(tee_mode="LOCAL", secret_salt="s", agent_id="a") - - instance.close.assert_awaited_once() diff --git a/python/tests/test_types.py b/python/tests/test_types.py deleted file mode 100644 index a263de9..0000000 --- a/python/tests/test_types.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -import pytest - -from elizaos_plugin_tee import ( - TeeMode, - TeeVendor, - parse_tee_mode, - parse_tee_vendor, -) - - -class TestParseTeeMode: - def test_parses_local(self) -> None: - assert parse_tee_mode("LOCAL") == TeeMode.LOCAL - assert parse_tee_mode("local") == TeeMode.LOCAL - - def test_parses_docker(self) -> None: - assert parse_tee_mode("DOCKER") == TeeMode.DOCKER - assert parse_tee_mode("docker") == TeeMode.DOCKER - - def test_parses_production(self) -> None: - assert parse_tee_mode("PRODUCTION") == TeeMode.PRODUCTION - assert parse_tee_mode("production") == TeeMode.PRODUCTION - - def test_raises_for_invalid_mode(self) -> None: - with pytest.raises(ValueError, match="Invalid TEE_MODE"): - parse_tee_mode("INVALID") - - -class TestParseTeeVendor: - def test_parses_phala(self) -> None: - assert parse_tee_vendor("phala") == TeeVendor.PHALA - assert parse_tee_vendor("PHALA") == TeeVendor.PHALA - - def test_raises_for_invalid_vendor(self) -> None: - with pytest.raises(ValueError, match="Invalid TEE_VENDOR"): - parse_tee_vendor("invalid") diff --git a/python/tests/test_utils.py b/python/tests/test_utils.py deleted file mode 100644 index 4cd791f..0000000 --- a/python/tests/test_utils.py +++ /dev/null @@ -1,92 +0,0 @@ -from __future__ import annotations - -import pytest - -from elizaos_plugin_tee import ( - bytes_to_hex, - calculate_sha256, - get_tee_endpoint, - hex_to_bytes, -) - - -class TestHexToBytes: - def test_converts_valid_hex_string(self) -> None: - result = hex_to_bytes("0102030405") - assert result == bytes([1, 2, 3, 4, 5]) - - def test_handles_0x_prefix(self) -> None: - result = hex_to_bytes("0x0102030405") - assert result == bytes([1, 2, 3, 4, 5]) - - def test_raises_for_empty_string(self) -> None: - with pytest.raises(ValueError, match="Invalid hex string"): - hex_to_bytes("") - - def test_raises_for_0x_only(self) -> None: - with pytest.raises(ValueError, match="Invalid hex string"): - hex_to_bytes("0x") - - def test_raises_for_odd_length(self) -> None: - with pytest.raises(ValueError, match="Invalid hex string"): - hex_to_bytes("0x123") - - def test_raises_for_invalid_characters(self) -> None: - with pytest.raises(ValueError, match="Invalid hex string"): - hex_to_bytes("0xGG") - - -class TestBytesToHex: - def test_converts_bytes_to_hex(self) -> None: - result = bytes_to_hex(bytes([1, 2, 3, 4, 5])) - assert result == "0102030405" - - def test_handles_empty_bytes(self) -> None: - result = bytes_to_hex(bytes([])) - assert result == "" - - def test_pads_single_digit_bytes(self) -> None: - result = bytes_to_hex(bytes([0, 1, 15, 16])) - assert result == "00010f10" - - -class TestCalculateSha256: - def test_calculates_hash_of_string(self) -> None: - result = calculate_sha256("hello") - assert isinstance(result, bytes) - assert len(result) == 32 - - def test_calculates_hash_of_bytes(self) -> None: - result = calculate_sha256(b"hello") - assert isinstance(result, bytes) - assert len(result) == 32 - - def test_produces_consistent_results(self) -> None: - result1 = calculate_sha256("test") - result2 = calculate_sha256("test") - assert result1 == result2 - - def test_produces_different_results_for_different_inputs(self) -> None: - result1 = calculate_sha256("hello") - result2 = calculate_sha256("world") - assert result1 != result2 - - -class TestGetTeeEndpoint: - def test_returns_localhost_for_local(self) -> None: - assert get_tee_endpoint("LOCAL") == "http://localhost:8090" - - def test_returns_docker_internal_for_docker(self) -> None: - assert get_tee_endpoint("DOCKER") == "http://host.docker.internal:8090" - - def test_returns_none_for_production(self) -> None: - assert get_tee_endpoint("PRODUCTION") is None - - def test_handles_case_insensitivity(self) -> None: - assert get_tee_endpoint("local") == "http://localhost:8090" - assert get_tee_endpoint("docker") == "http://host.docker.internal:8090" - assert get_tee_endpoint("production") is None - - def test_raises_for_invalid_mode(self) -> None: - with pytest.raises(ValueError, match="Invalid TEE_MODE"): - get_tee_endpoint("INVALID") diff --git a/rust/.gitignore b/rust/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/rust/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/rust/Cargo.lock b/rust/Cargo.lock deleted file mode 100644 index 275e870..0000000 --- a/rust/Cargo.lock +++ /dev/null @@ -1,2210 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" - -[[package]] -name = "cc" -version = "1.2.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "elizaos-plugin-tee" -version = "2.0.0" -dependencies = [ - "async-trait", - "bs58", - "console_error_panic_hook", - "dotenvy", - "ed25519-dalek", - "futures", - "getrandom 0.2.17", - "hex", - "js-sys", - "k256", - "pretty_assertions", - "rand 0.8.5", - "reqwest", - "serde", - "serde_json", - "sha2", - "sha3", - "thiserror", - "tokio", - "tracing", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "find-msvc-tools" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro2" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime_guess", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" diff --git a/rust/Cargo.toml b/rust/Cargo.toml deleted file mode 100644 index f2e2673..0000000 --- a/rust/Cargo.toml +++ /dev/null @@ -1,75 +0,0 @@ -[package] -name = "elizaos-plugin-tee" -version = "2.0.0" -edition = "2021" -authors = ["elizaOS Contributors"] -description = "elizaOS TEE Plugin - Trusted Execution Environment integration for secure key management and remote attestation" -license = "MIT" -repository = "https://github.com/elizaos/eliza" -keywords = ["ai", "agent", "tee", "attestation", "security", "phala"] -categories = ["cryptography", "api-bindings", "web-programming"] -readme = "README.md" - -include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = ["native"] -native = ["tokio/full", "reqwest/default-tls"] -wasm = ["wasm-bindgen", "wasm-bindgen-futures", "js-sys", "web-sys", "console_error_panic_hook", "getrandom/js", "reqwest/rustls-tls"] - -[dependencies] -# HTTP client -reqwest = { version = "0.12", default-features = false, features = ["json", "multipart"] } - -# Serialization -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -# Async runtime -async-trait = "0.1" -futures = "0.3" - -# Error handling -thiserror = "2.0" - -# Logging -tracing = "0.1" - -# UUID handling -uuid = { version = "1.19", features = ["v4", "serde"] } - -# Cryptography -sha2 = "0.10" -sha3 = "0.10" -ed25519-dalek = { version = "2.1", features = ["rand_core"] } -k256 = { version = "0.13", features = ["ecdsa", "sha256"] } -rand = "0.8" -hex = "0.4" -bs58 = "0.5" - -# Native-only dependencies -tokio = { version = "1.0", features = ["rt-multi-thread", "macros"], optional = true } - -# WASM-only dependencies -wasm-bindgen = { version = "0.2", optional = true } -wasm-bindgen-futures = { version = "0.4", optional = true } -js-sys = { version = "0.3", optional = true } -web-sys = { version = "0.3", features = ["console", "FormData", "Blob"], optional = true } -console_error_panic_hook = { version = "0.1", optional = true } -getrandom = { version = "0.2", optional = true } - -[dev-dependencies] -tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } -pretty_assertions = "1.0" -dotenvy = "0.15" - -[profile.release] -lto = true -opt-level = "z" - - - - diff --git a/rust/LICENSE b/rust/LICENSE deleted file mode 100644 index 6fa975f..0000000 --- a/rust/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -MIT License - -Copyright (c) 2024 elizaOS Team - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - - - - - diff --git a/rust/README.md b/rust/README.md deleted file mode 100644 index e3a4221..0000000 --- a/rust/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# elizaos-plugin-tee (Rust) - -Rust implementation of the elizaOS TEE Plugin for Trusted Execution Environment integration. - -## Features - -- 🔐 **Remote Attestation** - Prove agent execution in TEE -- 🔑 **Key Derivation** - Secure Ed25519 and ECDSA key derivation -- 🛡️ **Vendor Support** - Extensible vendor system (Phala Network) -- ⚡ **Async** - Full async/await support with Tokio -- 🔒 **Type Safe** - Strong Rust types with no unsafe code -- 🌐 **WASM** - Optional WebAssembly support - -## Installation - -Add to your `Cargo.toml`: - -```toml -[dependencies] -elizaos-plugin-tee = "1.0" -``` - -## Quick Start - -```rust -use elizaos_plugin_tee::{TEEService, TeeMode}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Start the service - let service = TEEService::start(Some("LOCAL"), None)?; - - // Derive Ed25519 keypair (for Solana) - let solana_result = service - .derive_ed25519_keypair("my-secret-salt", "solana", "agent-123") - .await?; - println!("Solana Public Key: {}", solana_result.public_key); - - // Derive ECDSA keypair (for EVM) - let evm_result = service - .derive_ecdsa_keypair("my-secret-salt", "evm", "agent-123") - .await?; - println!("EVM Address: {}", evm_result.address); - - // Stop the service - service.stop(); - - Ok(()) -} -``` - -## Configuration - -| Variable | Description | Required | -| -------------------- | ----------------------------------------------- | -------- | -| `TEE_MODE` | Operation mode: `LOCAL`, `DOCKER`, `PRODUCTION` | Yes | -| `WALLET_SECRET_SALT` | Secret for key derivation | Yes | -| `TEE_VENDOR` | Vendor name (default: `phala`) | No | - -## API Reference - -### TEEService - -Main service for TEE operations. - -```rust -// Initialize -let service = TEEService::start(Some("LOCAL"), None)?; - -// Derive keys -let ed25519 = service.derive_ed25519_keypair(path, subject, agent_id).await?; -let ecdsa = service.derive_ecdsa_keypair(path, subject, agent_id).await?; -let raw = service.raw_derive_key(path, subject).await?; - -// Cleanup -service.stop(); -``` - -### Remote Attestation - -```rust -use elizaos_plugin_tee::{PhalaRemoteAttestationProvider, RemoteAttestationProvider}; - -// Using provider directly -let provider = PhalaRemoteAttestationProvider::new("LOCAL")?; -let quote = provider.generate_attestation(report_data, None).await?; -``` - -### Types - -```rust -use elizaos_plugin_tee::{ - TeeMode, - TeeVendor, - RemoteAttestationQuote, - Ed25519KeypairResult, - EcdsaKeypairResult, -}; -``` - -## Features - -- `native` (default): Full async support with Tokio -- `wasm`: WebAssembly support for browser environments - -```toml -# For WASM -elizaos-plugin-tee = { version = "1.0", default-features = false, features = ["wasm"] } -``` - -## Development - -```bash -# Build -cargo build --release - -# Test -cargo test - -# Build WASM -wasm-pack build --target web - -# Lint -cargo clippy --all-targets -- -D warnings -``` - -## License - -MIT - - - diff --git a/rust/src/actions/mod.rs b/rust/src/actions/mod.rs deleted file mode 100644 index 3ff8d22..0000000 --- a/rust/src/actions/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![allow(missing_docs)] - -pub mod remote_attestation; - -pub use remote_attestation::{RemoteAttestationAction, REMOTE_ATTESTATION_EXAMPLES}; diff --git a/rust/src/actions/remote_attestation.rs b/rust/src/actions/remote_attestation.rs deleted file mode 100644 index 42f6010..0000000 --- a/rust/src/actions/remote_attestation.rs +++ /dev/null @@ -1,154 +0,0 @@ -#![allow(missing_docs)] - -use tracing::{error, info}; - -use crate::client::upload_attestation_quote; -use crate::providers::base::RemoteAttestationProvider; -use crate::providers::PhalaRemoteAttestationProvider; -use crate::types::{RemoteAttestationMessage, RemoteAttestationMessageContent}; -use crate::utils::{current_timestamp_ms, hex_to_bytes}; - -pub struct RemoteAttestationAction; - -pub struct RemoteAttestationResult { - pub success: bool, - pub text: String, -} - -impl RemoteAttestationAction { - pub const NAME: &'static str = "REMOTE_ATTESTATION"; - - pub const DESCRIPTION: &'static str = - "Generate a remote attestation to prove that the agent is running in a TEE (Trusted Execution Environment)"; - - pub const SIMILES: &'static [&'static str] = &[ - "REMOTE_ATTESTATION", - "TEE_REMOTE_ATTESTATION", - "TEE_ATTESTATION", - "TEE_QUOTE", - "ATTESTATION", - "TEE_ATTESTATION_QUOTE", - "PROVE_TEE", - "VERIFY_TEE", - ]; - - pub async fn handle( - tee_mode: Option<&str>, - agent_id: &str, - entity_id: &str, - room_id: &str, - content: &str, - ) -> RemoteAttestationResult { - let Some(mode) = tee_mode else { - error!("TEE_MODE is not configured"); - return RemoteAttestationResult { - success: false, - text: "TEE_MODE is not configured. Cannot generate attestation.".to_string(), - }; - }; - - let attestation_message = RemoteAttestationMessage { - agent_id: agent_id.to_string(), - timestamp: current_timestamp_ms(), - message: RemoteAttestationMessageContent { - entity_id: entity_id.to_string(), - room_id: room_id.to_string(), - content: content.to_string(), - }, - }; - - let provider = match PhalaRemoteAttestationProvider::new(mode) { - Ok(p) => p, - Err(e) => { - error!("Failed to create attestation provider: {}", e); - return RemoteAttestationResult { - success: false, - text: format!("Failed to create attestation provider: {}", e), - }; - } - }; - - let report_data = match serde_json::to_string(&attestation_message) { - Ok(s) => s, - Err(e) => { - error!("Failed to serialize attestation message: {}", e); - return RemoteAttestationResult { - success: false, - text: format!("Failed to serialize attestation message: {}", e), - }; - } - }; - - let attestation = match provider.generate_attestation(&report_data, None).await { - Ok(a) => a, - Err(e) => { - error!("Failed to generate attestation: {}", e); - return RemoteAttestationResult { - success: false, - text: format!("Failed to generate attestation: {}", e), - }; - } - }; - - let attestation_data = match hex_to_bytes(&attestation.quote) { - Ok(d) => d, - Err(e) => { - error!("Failed to decode attestation quote: {}", e); - return RemoteAttestationResult { - success: false, - text: format!("Failed to decode attestation quote: {}", e), - }; - } - }; - - let upload_result = match upload_attestation_quote(&attestation_data).await { - Ok(r) => r, - Err(e) => { - error!("Failed to upload attestation: {}", e); - return RemoteAttestationResult { - success: false, - text: format!("Failed to upload attestation: {}", e), - }; - } - }; - - let proof_url = format!("https://proof.t16z.com/reports/{}", upload_result.checksum); - - info!("Attestation uploaded: {}", proof_url); - - RemoteAttestationResult { - success: true, - text: format!("Remote attestation quote: {}", proof_url), - } - } -} - -pub const REMOTE_ATTESTATION_EXAMPLES: &[&[(&str, &str)]] = &[ - &[ - ( - "{{name1}}", - "If you are running in a TEE, generate a remote attestation", - ), - ("{{agentName}}", "Of course, one second..."), - ], - &[ - ( - "{{name1}}", - "Can you prove you're running in a trusted execution environment?", - ), - ( - "{{agentName}}", - "Absolutely! Let me generate a TEE attestation quote for you.", - ), - ], - &[ - ( - "{{name1}}", - "I need verification that this conversation is happening in a secure enclave", - ), - ( - "{{agentName}}", - "I'll generate a remote attestation to prove I'm running in a TEE.", - ), - ], -]; diff --git a/rust/src/client.rs b/rust/src/client.rs deleted file mode 100644 index abbb1a7..0000000 --- a/rust/src/client.rs +++ /dev/null @@ -1,139 +0,0 @@ -#![allow(missing_docs)] - -use crate::error::{Result, TeeError}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone)] -pub struct TeeClient { - client: Client, - endpoint: String, -} - -#[derive(Debug, Deserialize)] -pub struct DeriveKeyResponse { - pub key: String, -} - -#[derive(Debug, Deserialize)] -pub struct TdxQuoteResponse { - pub quote: String, - #[serde(default)] - pub rtmrs: Vec, -} - -#[derive(Debug, Serialize)] -pub struct DeriveKeyRequest { - pub path: String, - pub subject: String, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TdxQuoteRequest { - pub report_data: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub hash_algorithm: Option, -} - -impl TeeClient { - pub fn new(endpoint: Option) -> Self { - let endpoint = endpoint.unwrap_or_else(|| "https://api.phala.network/tee".to_string()); - let client = Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .build() - .expect("Failed to create HTTP client"); - - Self { client, endpoint } - } - - pub async fn derive_key(&self, path: &str, subject: &str) -> Result> { - let request = DeriveKeyRequest { - path: path.to_string(), - subject: subject.to_string(), - }; - - let response = self - .client - .post(format!("{}/derive-key", self.endpoint)) - .json(&request) - .send() - .await?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(TeeError::network(format!( - "Key derivation failed: {} - {}", - status, body - ))); - } - - let result: DeriveKeyResponse = response.json().await?; - hex::decode(&result.key).map_err(TeeError::from) - } - - pub async fn tdx_quote( - &self, - report_data: &str, - hash_algorithm: Option<&str>, - ) -> Result { - let request = TdxQuoteRequest { - report_data: report_data.to_string(), - hash_algorithm: hash_algorithm.map(|s| s.to_string()), - }; - - let response = self - .client - .post(format!("{}/tdx-quote", self.endpoint)) - .json(&request) - .send() - .await?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(TeeError::attestation(format!( - "TDX quote generation failed: {} - {}", - status, body - ))); - } - - let result: TdxQuoteResponse = response.json().await?; - Ok(result) - } -} - -pub async fn upload_attestation_quote(data: &[u8]) -> Result { - let client = Client::new(); - - let part = reqwest::multipart::Part::bytes(data.to_vec()) - .file_name("quote.bin") - .mime_str("application/octet-stream") - .map_err(|e| TeeError::network(e.to_string()))?; - - let form = reqwest::multipart::Form::new().part("file", part); - - let response = client - .post("https://proof.t16z.com/api/upload") - .multipart(form) - .send() - .await?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(TeeError::network(format!( - "Upload failed: {} - {}", - status, body - ))); - } - - let result: UploadResponse = response.json().await?; - Ok(result) -} - -#[derive(Debug, Deserialize)] -pub struct UploadResponse { - pub checksum: String, -} diff --git a/rust/src/error.rs b/rust/src/error.rs deleted file mode 100644 index a9efe1a..0000000 --- a/rust/src/error.rs +++ /dev/null @@ -1,60 +0,0 @@ -#![allow(missing_docs)] - -use thiserror::Error; - -pub type Result = std::result::Result; - -#[derive(Debug, Error)] -pub enum TeeError { - #[error("Configuration error: {0}")] - Config(String), - - #[error("Invalid TEE_MODE: {0}. Must be one of: LOCAL, DOCKER, PRODUCTION")] - InvalidMode(String), - - #[error("Invalid TEE_VENDOR: {0}. Must be one of: phala")] - InvalidVendor(String), - - #[error("Failed to generate attestation: {0}")] - Attestation(String), - - #[error("Failed to derive key: {0}")] - KeyDerivation(String), - - #[error("Network error: {0}")] - Network(String), - - #[error("HTTP error: {0}")] - Http(#[from] reqwest::Error), - - #[error("JSON error: {0}")] - Json(#[from] serde_json::Error), - - #[error("Hex decoding error: {0}")] - HexDecode(#[from] hex::FromHexError), - - #[error("Cryptographic error: {0}")] - Crypto(String), -} - -impl TeeError { - pub fn config>(msg: S) -> Self { - Self::Config(msg.into()) - } - - pub fn attestation>(msg: S) -> Self { - Self::Attestation(msg.into()) - } - - pub fn key_derivation>(msg: S) -> Self { - Self::KeyDerivation(msg.into()) - } - - pub fn network>(msg: S) -> Self { - Self::Network(msg.into()) - } - - pub fn crypto>(msg: S) -> Self { - Self::Crypto(msg.into()) - } -} diff --git a/rust/src/generated/specs/mod.rs b/rust/src/generated/specs/mod.rs deleted file mode 100644 index 27004b1..0000000 --- a/rust/src/generated/specs/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Auto-generated specs module. - -pub mod specs; diff --git a/rust/src/generated/specs/specs.rs b/rust/src/generated/specs/specs.rs deleted file mode 100644 index b52e6f1..0000000 --- a/rust/src/generated/specs/specs.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Auto-generated canonical action/provider/evaluator docs for plugin-tee. -//! DO NOT EDIT - Generated from prompts/specs/**. - -pub const CORE_ACTION_DOCS_JSON: &str = r#"{ - "version": "1.0.0", - "actions": [] -}"#; -pub const ALL_ACTION_DOCS_JSON: &str = r#"{ - "version": "1.0.0", - "actions": [] -}"#; -pub const CORE_PROVIDER_DOCS_JSON: &str = r#"{ - "version": "1.0.0", - "providers": [] -}"#; -pub const ALL_PROVIDER_DOCS_JSON: &str = r#"{ - "version": "1.0.0", - "providers": [] -}"#; -pub const CORE_EVALUATOR_DOCS_JSON: &str = r#"{ - "version": "1.0.0", - "evaluators": [] -}"#; -pub const ALL_EVALUATOR_DOCS_JSON: &str = r#"{ - "version": "1.0.0", - "evaluators": [] -}"#; diff --git a/rust/src/lib.rs b/rust/src/lib.rs deleted file mode 100644 index 3f5b870..0000000 --- a/rust/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![allow(missing_docs)] -#![deny(unsafe_code)] - -pub mod actions; -pub mod client; -pub mod error; -pub mod providers; -pub mod services; -pub mod types; -pub mod utils; -pub mod vendors; - -pub use types::{TeeMode, TeeVendor}; -pub use utils::{bytes_to_hex, calculate_sha256, get_tee_endpoint, hex_to_bytes}; - -pub const PLUGIN_NAME: &str = "tee"; -pub const PLUGIN_DESCRIPTION: &str = - "TEE integration plugin for secure key management and remote attestation"; -pub const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/rust/src/providers/base.rs b/rust/src/providers/base.rs deleted file mode 100644 index c053df8..0000000 --- a/rust/src/providers/base.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(missing_docs)] - -use async_trait::async_trait; - -use crate::error::Result; -use crate::types::{DeriveKeyResult, RemoteAttestationQuote, TdxQuoteHashAlgorithm}; - -#[async_trait] -pub trait DeriveKeyProvider: Send + Sync { - async fn raw_derive_key(&self, path: &str, subject: &str) -> Result; -} - -#[async_trait] -pub trait RemoteAttestationProvider: Send + Sync { - async fn generate_attestation( - &self, - report_data: &str, - hash_algorithm: Option, - ) -> Result; -} diff --git a/rust/src/providers/derive_key.rs b/rust/src/providers/derive_key.rs deleted file mode 100644 index 4743937..0000000 --- a/rust/src/providers/derive_key.rs +++ /dev/null @@ -1,148 +0,0 @@ -#![allow(missing_docs)] - -use async_trait::async_trait; -use bs58; -use ed25519_dalek::{SigningKey, VerifyingKey}; -use k256::ecdsa::SigningKey as K256SigningKey; -use tracing::info; - -use crate::client::TeeClient; -use crate::error::{Result, TeeError}; -use crate::providers::base::{DeriveKeyProvider, RemoteAttestationProvider}; -use crate::providers::remote_attestation::PhalaRemoteAttestationProvider; -use crate::types::{ - DeriveKeyAttestationData, DeriveKeyResult, EcdsaKeypairResult, Ed25519KeypairResult, - RemoteAttestationQuote, -}; -use crate::utils::{calculate_keccak256, calculate_sha256, get_tee_endpoint}; - -pub struct PhalaDeriveKeyProvider { - client: TeeClient, - ra_provider: PhalaRemoteAttestationProvider, -} - -impl PhalaDeriveKeyProvider { - pub fn new(tee_mode: &str) -> Result { - let endpoint = get_tee_endpoint(tee_mode)?; - - if let Some(ref ep) = endpoint { - info!("TEE: Connecting to key derivation service at {}", ep); - } else { - info!("TEE: Running key derivation in production mode"); - } - - Ok(Self { - client: TeeClient::new(endpoint), - ra_provider: PhalaRemoteAttestationProvider::new(tee_mode)?, - }) - } - - async fn generate_derive_key_attestation( - &self, - agent_id: &str, - public_key: &str, - subject: Option<&str>, - ) -> Result { - let derive_key_data = DeriveKeyAttestationData { - agent_id: agent_id.to_string(), - public_key: public_key.to_string(), - subject: subject.map(|s| s.to_string()), - }; - - let report_data = serde_json::to_string(&derive_key_data)?; - self.ra_provider - .generate_attestation(&report_data, None) - .await - } - - pub async fn derive_ed25519_keypair( - &self, - path: &str, - subject: &str, - agent_id: &str, - ) -> Result { - if path.is_empty() || subject.is_empty() { - return Err(TeeError::key_derivation( - "Path and subject are required for key derivation", - )); - } - - let derived_key = self.client.derive_key(path, subject).await?; - - let seed = calculate_sha256(&derived_key); - let seed_array: [u8; 32] = seed[..32] - .try_into() - .map_err(|_| TeeError::crypto("Failed to create seed array"))?; - - let signing_key = SigningKey::from_bytes(&seed_array); - let verifying_key: VerifyingKey = (&signing_key).into(); - let public_key = bs58::encode(verifying_key.as_bytes()).into_string(); - - let attestation = self - .generate_derive_key_attestation(agent_id, &public_key, Some(subject)) - .await?; - - Ok(Ed25519KeypairResult { - public_key, - secret_key: signing_key.to_bytes().to_vec(), - attestation, - }) - } - - pub async fn derive_ecdsa_keypair( - &self, - path: &str, - subject: &str, - agent_id: &str, - ) -> Result { - if path.is_empty() || subject.is_empty() { - return Err(TeeError::key_derivation( - "Path and subject are required for key derivation", - )); - } - - let derived_key = self.client.derive_key(path, subject).await?; - - let private_key_bytes = calculate_keccak256(&derived_key); - let private_key_array: [u8; 32] = private_key_bytes[..32] - .try_into() - .map_err(|_| TeeError::crypto("Failed to create private key array"))?; - - let signing_key = K256SigningKey::from_bytes(&private_key_array.into()) - .map_err(|e| TeeError::crypto(e.to_string()))?; - - let public_key = signing_key.verifying_key(); - let public_key_bytes = public_key.to_encoded_point(false); - let public_key_hash = calculate_keccak256(&public_key_bytes.as_bytes()[1..]); - let address_bytes = &public_key_hash[12..]; - let address = format!("0x{}", hex::encode(address_bytes)); - - let attestation = self - .generate_derive_key_attestation(agent_id, &address, Some(subject)) - .await?; - - Ok(EcdsaKeypairResult { - address, - private_key: private_key_bytes, - attestation, - }) - } -} - -#[async_trait] -impl DeriveKeyProvider for PhalaDeriveKeyProvider { - async fn raw_derive_key(&self, path: &str, subject: &str) -> Result { - if path.is_empty() || subject.is_empty() { - return Err(TeeError::key_derivation( - "Path and subject are required for key derivation", - )); - } - - let key = self.client.derive_key(path, subject).await?; - - Ok(DeriveKeyResult { - key, - certificate_chain: vec![], - }) - } -} diff --git a/rust/src/providers/mod.rs b/rust/src/providers/mod.rs deleted file mode 100644 index 458ba9a..0000000 --- a/rust/src/providers/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![allow(missing_docs)] - -pub mod base; -pub mod derive_key; -pub mod remote_attestation; - -pub use base::{DeriveKeyProvider, RemoteAttestationProvider}; -pub use derive_key::PhalaDeriveKeyProvider; -pub use remote_attestation::PhalaRemoteAttestationProvider; diff --git a/rust/src/providers/remote_attestation.rs b/rust/src/providers/remote_attestation.rs deleted file mode 100644 index 2d8a7b5..0000000 --- a/rust/src/providers/remote_attestation.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![allow(missing_docs)] - -use async_trait::async_trait; -use tracing::info; - -use crate::client::TeeClient; -use crate::error::{Result, TeeError}; -use crate::providers::base::RemoteAttestationProvider; -use crate::types::{RemoteAttestationQuote, TdxQuoteHashAlgorithm}; -use crate::utils::{current_timestamp_ms, get_tee_endpoint}; - -pub struct PhalaRemoteAttestationProvider { - client: TeeClient, -} - -impl PhalaRemoteAttestationProvider { - pub fn new(tee_mode: &str) -> Result { - let endpoint = get_tee_endpoint(tee_mode)?; - - if let Some(ref ep) = endpoint { - info!("TEE: Connecting to simulator at {}", ep); - } else { - info!("TEE: Running in production mode without simulator"); - } - - Ok(Self { - client: TeeClient::new(endpoint), - }) - } -} - -#[async_trait] -impl RemoteAttestationProvider for PhalaRemoteAttestationProvider { - async fn generate_attestation( - &self, - report_data: &str, - hash_algorithm: Option, - ) -> Result { - let hash_algo = hash_algorithm.map(|a| match a { - TdxQuoteHashAlgorithm::Sha256 => "sha256", - TdxQuoteHashAlgorithm::Sha384 => "sha384", - TdxQuoteHashAlgorithm::Sha512 => "sha512", - TdxQuoteHashAlgorithm::Raw => "raw", - }); - - let result = self - .client - .tdx_quote(report_data, hash_algo) - .await - .map_err(|e| TeeError::attestation(e.to_string()))?; - - Ok(RemoteAttestationQuote { - quote: result.quote, - timestamp: current_timestamp_ms(), - }) - } -} diff --git a/rust/src/services/mod.rs b/rust/src/services/mod.rs deleted file mode 100644 index 06e71c8..0000000 --- a/rust/src/services/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![allow(missing_docs)] - -pub mod tee; - -pub use tee::TEEService; diff --git a/rust/src/services/tee.rs b/rust/src/services/tee.rs deleted file mode 100644 index 4f418ea..0000000 --- a/rust/src/services/tee.rs +++ /dev/null @@ -1,80 +0,0 @@ -#![allow(missing_docs)] - -use tracing::info; - -use crate::error::Result; -use crate::providers::derive_key::PhalaDeriveKeyProvider; -use crate::providers::DeriveKeyProvider; -use crate::types::{ - DeriveKeyResult, EcdsaKeypairResult, Ed25519KeypairResult, TeeMode, TeeServiceConfig, TeeVendor, -}; - -pub struct TEEService { - provider: PhalaDeriveKeyProvider, - pub config: TeeServiceConfig, -} - -impl TEEService { - pub fn new(config: TeeServiceConfig) -> Result { - let provider = PhalaDeriveKeyProvider::new(config.mode.as_str())?; - - info!( - "TEE service initialized with mode: {}, vendor: {}", - config.mode.as_str(), - config.vendor.as_str() - ); - - Ok(Self { provider, config }) - } - - pub fn start(tee_mode: Option<&str>, secret_salt: Option) -> Result { - let mode = match tee_mode { - Some(m) => TeeMode::parse(m)?, - None => TeeMode::Local, - }; - - info!("Starting TEE service with mode: {}", mode.as_str()); - - let config = TeeServiceConfig { - mode, - vendor: TeeVendor::Phala, - secret_salt, - }; - - Self::new(config) - } - - pub fn stop(&self) { - info!("Stopping TEE service"); - } - - pub const SERVICE_TYPE: &'static str = "tee"; - pub const CAPABILITY_DESCRIPTION: &'static str = - "Trusted Execution Environment for secure key management"; - - pub async fn derive_ecdsa_keypair( - &self, - path: &str, - subject: &str, - agent_id: &str, - ) -> Result { - self.provider - .derive_ecdsa_keypair(path, subject, agent_id) - .await - } - - pub async fn derive_ed25519_keypair( - &self, - path: &str, - subject: &str, - agent_id: &str, - ) -> Result { - self.provider - .derive_ed25519_keypair(path, subject, agent_id) - .await - } - - pub async fn raw_derive_key(&self, path: &str, subject: &str) -> Result { - self.provider.raw_derive_key(path, subject).await - } -} diff --git a/rust/src/types.rs b/rust/src/types.rs deleted file mode 100644 index 62bd2cc..0000000 --- a/rust/src/types.rs +++ /dev/null @@ -1,149 +0,0 @@ -#![allow(missing_docs)] - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -#[derive(Default)] -pub enum TeeMode { - #[default] - Local, - Docker, - Production, -} - -impl TeeMode { - pub fn as_str(&self) -> &'static str { - match self { - Self::Local => "LOCAL", - Self::Docker => "DOCKER", - Self::Production => "PRODUCTION", - } - } - - pub fn parse(s: &str) -> Result { - match s.to_uppercase().as_str() { - "LOCAL" => Ok(Self::Local), - "DOCKER" => Ok(Self::Docker), - "PRODUCTION" => Ok(Self::Production), - _ => Err(TeeError::InvalidMode(s.to_string())), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[derive(Default)] -pub enum TeeVendor { - #[default] - Phala, -} - -impl TeeVendor { - pub fn as_str(&self) -> &'static str { - match self { - Self::Phala => "phala", - } - } - - pub fn parse(s: &str) -> Result { - match s.to_lowercase().as_str() { - "phala" => Ok(Self::Phala), - _ => Err(TeeError::InvalidVendor(s.to_string())), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum TeeType { - SgxGramine, - TdxDstack, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum TdxQuoteHashAlgorithm { - Sha256, - Sha384, - Sha512, - Raw, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RemoteAttestationQuote { - pub quote: String, - pub timestamp: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DeriveKeyAttestationData { - pub agent_id: String, - pub public_key: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub subject: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RemoteAttestationMessageContent { - pub entity_id: String, - pub room_id: String, - pub content: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RemoteAttestationMessage { - pub agent_id: String, - pub timestamp: u64, - pub message: RemoteAttestationMessageContent, -} - -#[derive(Debug, Clone)] -pub struct DeriveKeyResult { - pub key: Vec, - pub certificate_chain: Vec, -} - -#[derive(Debug, Clone)] -pub struct Ed25519KeypairResult { - pub public_key: String, - pub secret_key: Vec, - pub attestation: RemoteAttestationQuote, -} - -#[derive(Debug, Clone)] -pub struct EcdsaKeypairResult { - pub address: String, - pub private_key: Vec, - pub attestation: RemoteAttestationQuote, -} - -#[derive(Debug, Clone)] -pub struct TeeServiceConfig { - pub mode: TeeMode, - pub vendor: TeeVendor, - pub secret_salt: Option, -} - -impl Default for TeeServiceConfig { - fn default() -> Self { - Self { - mode: TeeMode::Local, - vendor: TeeVendor::Phala, - secret_salt: None, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TeeProviderResult { - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option>, - pub values: std::collections::HashMap, - pub text: String, -} - -use crate::error::TeeError; diff --git a/rust/src/utils.rs b/rust/src/utils.rs deleted file mode 100644 index 935c0d8..0000000 --- a/rust/src/utils.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![allow(missing_docs)] - -use crate::error::{Result, TeeError}; -use sha2::{Digest, Sha256}; - -pub fn hex_to_bytes(hex_str: &str) -> Result> { - let hex_str = hex_str.trim().trim_start_matches("0x"); - if hex_str.is_empty() { - return Err(TeeError::config( - "Invalid hex string: empty after stripping prefix", - )); - } - if !hex_str.len().is_multiple_of(2) { - return Err(TeeError::config( - "Invalid hex string: odd number of characters", - )); - } - hex::decode(hex_str).map_err(TeeError::from) -} - -pub fn bytes_to_hex(bytes: &[u8]) -> String { - hex::encode(bytes) -} - -pub fn calculate_sha256(data: &[u8]) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(data); - hasher.finalize().to_vec() -} - -pub fn calculate_keccak256(data: &[u8]) -> Vec { - use sha3::Keccak256; - let mut hasher = Keccak256::new(); - hasher.update(data); - hasher.finalize().to_vec() -} - -pub fn get_tee_endpoint(mode: &str) -> Result> { - match mode.to_uppercase().as_str() { - "LOCAL" => Ok(Some("http://localhost:8090".to_string())), - "DOCKER" => Ok(Some("http://host.docker.internal:8090".to_string())), - "PRODUCTION" => Ok(None), - _ => Err(TeeError::InvalidMode(mode.to_string())), - } -} - -pub fn current_timestamp_ms() -> u64 { - use std::time::{SystemTime, UNIX_EPOCH}; - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_millis() as u64 -} - -pub fn format_evm_address(bytes: &[u8]) -> String { - format!("0x{}", bytes_to_hex(bytes)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_hex_to_bytes() { - let result = hex_to_bytes("0102030405").unwrap(); - assert_eq!(result, vec![1, 2, 3, 4, 5]); - } - - #[test] - fn test_hex_to_bytes_with_prefix() { - let result = hex_to_bytes("0x0102030405").unwrap(); - assert_eq!(result, vec![1, 2, 3, 4, 5]); - } - - #[test] - fn test_hex_to_bytes_empty() { - assert!(hex_to_bytes("").is_err()); - assert!(hex_to_bytes("0x").is_err()); - } - - #[test] - fn test_hex_to_bytes_odd_length() { - assert!(hex_to_bytes("0x123").is_err()); - } - - #[test] - fn test_bytes_to_hex() { - let result = bytes_to_hex(&[1, 2, 3, 4, 5]); - assert_eq!(result, "0102030405"); - } - - #[test] - fn test_calculate_sha256() { - let result = calculate_sha256(b"hello"); - assert_eq!(result.len(), 32); - } - - #[test] - fn test_get_tee_endpoint() { - assert_eq!( - get_tee_endpoint("LOCAL").unwrap(), - Some("http://localhost:8090".to_string()) - ); - assert_eq!( - get_tee_endpoint("DOCKER").unwrap(), - Some("http://host.docker.internal:8090".to_string()) - ); - assert_eq!(get_tee_endpoint("PRODUCTION").unwrap(), None); - assert!(get_tee_endpoint("INVALID").is_err()); - } -} diff --git a/rust/src/vendors/mod.rs b/rust/src/vendors/mod.rs deleted file mode 100644 index fdfc90b..0000000 --- a/rust/src/vendors/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![allow(missing_docs)] - -pub mod phala; -pub mod types; - -pub use phala::PhalaVendor; -pub use types::{TeeVendorInterface, TeeVendorNames}; - -use crate::error::{Result, TeeError}; - -pub fn get_vendor(vendor_type: &str) -> Result> { - match vendor_type.to_lowercase().as_str() { - "phala" => Ok(Box::new(PhalaVendor::new())), - _ => Err(TeeError::InvalidVendor(vendor_type.to_string())), - } -} diff --git a/rust/src/vendors/phala.rs b/rust/src/vendors/phala.rs deleted file mode 100644 index c90e3dd..0000000 --- a/rust/src/vendors/phala.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![allow(missing_docs)] - -use crate::vendors::types::{TeeVendorInterface, TeeVendorNames}; - -pub struct PhalaVendor; - -impl PhalaVendor { - pub fn new() -> Self { - Self - } -} - -impl Default for PhalaVendor { - fn default() -> Self { - Self::new() - } -} - -impl TeeVendorInterface for PhalaVendor { - fn vendor_type(&self) -> &'static str { - TeeVendorNames::PHALA - } - - fn name(&self) -> &'static str { - "phala-tee-plugin" - } - - fn description(&self) -> &'static str { - "Phala Network TEE for secure agent execution" - } - - fn action_names(&self) -> Vec<&'static str> { - vec!["REMOTE_ATTESTATION"] - } - - fn provider_names(&self) -> Vec<&'static str> { - vec!["phala-derive-key", "phala-remote-attestation"] - } -} diff --git a/rust/src/vendors/types.rs b/rust/src/vendors/types.rs deleted file mode 100644 index aafac6c..0000000 --- a/rust/src/vendors/types.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![allow(missing_docs)] - -pub struct TeeVendorNames; - -impl TeeVendorNames { - pub const PHALA: &'static str = "phala"; -} - -pub trait TeeVendorInterface: Send + Sync { - fn vendor_type(&self) -> &'static str; - fn name(&self) -> &'static str; - fn description(&self) -> &'static str; - fn action_names(&self) -> Vec<&'static str>; - fn provider_names(&self) -> Vec<&'static str>; -} diff --git a/rust/tests/integration_tests.rs b/rust/tests/integration_tests.rs deleted file mode 100644 index 5950ec4..0000000 --- a/rust/tests/integration_tests.rs +++ /dev/null @@ -1,359 +0,0 @@ -use elizaos_plugin_tee::{ - bytes_to_hex, calculate_sha256, get_tee_endpoint, hex_to_bytes, TeeMode, TeeVendor, -}; -use elizaos_plugin_tee::actions::remote_attestation::{ - RemoteAttestationAction, REMOTE_ATTESTATION_EXAMPLES, -}; -use elizaos_plugin_tee::providers::{ - DeriveKeyProvider, PhalaDeriveKeyProvider, PhalaRemoteAttestationProvider, -}; -use elizaos_plugin_tee::types::{ - DeriveKeyAttestationData, RemoteAttestationMessage, RemoteAttestationMessageContent, - RemoteAttestationQuote, TdxQuoteHashAlgorithm, TeeServiceConfig, -}; - -// =========================================================================== -// Original utility tests -// =========================================================================== - -#[test] -fn test_hex_to_bytes() { - let result = hex_to_bytes("0102030405").unwrap(); - assert_eq!(result, vec![1, 2, 3, 4, 5]); -} - -#[test] -fn test_hex_to_bytes_with_prefix() { - let result = hex_to_bytes("0x0102030405").unwrap(); - assert_eq!(result, vec![1, 2, 3, 4, 5]); -} - -#[test] -fn test_hex_to_bytes_empty() { - assert!(hex_to_bytes("").is_err()); - assert!(hex_to_bytes("0x").is_err()); -} - -#[test] -fn test_hex_to_bytes_odd_length() { - assert!(hex_to_bytes("0x123").is_err()); -} - -#[test] -fn test_bytes_to_hex() { - let result = bytes_to_hex(&[1, 2, 3, 4, 5]); - assert_eq!(result, "0102030405"); -} - -#[test] -fn test_calculate_sha256() { - let result = calculate_sha256(b"hello"); - assert_eq!(result.len(), 32); -} - -#[test] -fn test_get_tee_endpoint() { - assert_eq!( - get_tee_endpoint("LOCAL").unwrap(), - Some("http://localhost:8090".to_string()) - ); - assert_eq!( - get_tee_endpoint("DOCKER").unwrap(), - Some("http://host.docker.internal:8090".to_string()) - ); - assert_eq!(get_tee_endpoint("PRODUCTION").unwrap(), None); - assert!(get_tee_endpoint("INVALID").is_err()); -} - -#[test] -fn test_tee_mode_parse() { - assert_eq!(TeeMode::parse("LOCAL").unwrap(), TeeMode::Local); - assert_eq!(TeeMode::parse("local").unwrap(), TeeMode::Local); - assert_eq!(TeeMode::parse("DOCKER").unwrap(), TeeMode::Docker); - assert_eq!(TeeMode::parse("PRODUCTION").unwrap(), TeeMode::Production); - assert!(TeeMode::parse("INVALID").is_err()); -} - -#[test] -fn test_tee_vendor_parse() { - assert_eq!(TeeVendor::parse("phala").unwrap(), TeeVendor::Phala); - assert_eq!(TeeVendor::parse("PHALA").unwrap(), TeeVendor::Phala); - assert!(TeeVendor::parse("invalid").is_err()); -} - -// =========================================================================== -// RemoteAttestationAction metadata tests -// =========================================================================== - -#[test] -fn test_remote_attestation_action_name() { - assert_eq!(RemoteAttestationAction::NAME, "REMOTE_ATTESTATION"); -} - -#[test] -fn test_remote_attestation_action_description_mentions_tee() { - let desc = RemoteAttestationAction::DESCRIPTION; - assert!(desc.contains("TEE")); - assert!(desc.to_lowercase().contains("attestation")); -} - -#[test] -fn test_remote_attestation_action_similes_not_empty() { - let similes = RemoteAttestationAction::SIMILES; - assert!(!similes.is_empty()); - assert!(similes.contains(&"REMOTE_ATTESTATION")); - assert!(similes.contains(&"TEE_ATTESTATION")); - assert!(similes.contains(&"PROVE_TEE")); - assert!(similes.contains(&"VERIFY_TEE")); -} - -#[test] -fn test_remote_attestation_examples_not_empty() { - assert!(!REMOTE_ATTESTATION_EXAMPLES.is_empty()); - // Each example should be a pair of messages - for example in REMOTE_ATTESTATION_EXAMPLES { - assert_eq!(example.len(), 2); - } -} - -// =========================================================================== -// RemoteAttestationAction handler error paths -// =========================================================================== - -#[tokio::test] -async fn test_remote_attestation_handle_no_tee_mode() { - let result = RemoteAttestationAction::handle( - None, - "agent-1", - "entity-1", - "room-1", - "test content", - ) - .await; - - assert!(!result.success); - assert!(result.text.contains("TEE_MODE")); -} - -// =========================================================================== -// PhalaRemoteAttestationProvider tests -// =========================================================================== - -#[test] -fn test_ra_provider_creates_with_local_mode() { - let provider = PhalaRemoteAttestationProvider::new("LOCAL"); - assert!(provider.is_ok()); -} - -#[test] -fn test_ra_provider_creates_with_docker_mode() { - let provider = PhalaRemoteAttestationProvider::new("DOCKER"); - assert!(provider.is_ok()); -} - -#[test] -fn test_ra_provider_creates_with_production_mode() { - let provider = PhalaRemoteAttestationProvider::new("PRODUCTION"); - assert!(provider.is_ok()); -} - -#[test] -fn test_ra_provider_rejects_invalid_mode() { - let result = PhalaRemoteAttestationProvider::new("INVALID"); - assert!(result.is_err()); - match result { - Err(e) => assert!(e.to_string().contains("Invalid TEE_MODE")), - Ok(_) => panic!("Expected error for invalid mode"), - } -} - -// =========================================================================== -// PhalaDeriveKeyProvider tests -// =========================================================================== - -#[test] -fn test_derive_key_provider_creates_with_valid_modes() { - assert!(PhalaDeriveKeyProvider::new("LOCAL").is_ok()); - assert!(PhalaDeriveKeyProvider::new("DOCKER").is_ok()); - assert!(PhalaDeriveKeyProvider::new("PRODUCTION").is_ok()); -} - -#[test] -fn test_derive_key_provider_rejects_invalid_mode() { - let result = PhalaDeriveKeyProvider::new("BOGUS"); - assert!(result.is_err()); - match result { - Err(e) => assert!(e.to_string().contains("Invalid TEE_MODE")), - Ok(_) => panic!("Expected error for invalid mode"), - } -} - -#[tokio::test] -async fn test_derive_key_raw_rejects_empty_path() { - let provider = PhalaDeriveKeyProvider::new("LOCAL").unwrap(); - let result = provider.raw_derive_key("", "subject").await; - assert!(result.is_err()); - let err = result.unwrap_err(); - assert!(err.to_string().contains("Path and subject are required")); -} - -#[tokio::test] -async fn test_derive_key_raw_rejects_empty_subject() { - let provider = PhalaDeriveKeyProvider::new("LOCAL").unwrap(); - let result = provider.raw_derive_key("/path", "").await; - assert!(result.is_err()); - let err = result.unwrap_err(); - assert!(err.to_string().contains("Path and subject are required")); -} - -#[tokio::test] -async fn test_derive_ed25519_rejects_empty_path() { - let provider = PhalaDeriveKeyProvider::new("LOCAL").unwrap(); - let result = provider.derive_ed25519_keypair("", "subject", "agent-1").await; - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Path and subject are required")); -} - -#[tokio::test] -async fn test_derive_ed25519_rejects_empty_subject() { - let provider = PhalaDeriveKeyProvider::new("LOCAL").unwrap(); - let result = provider.derive_ed25519_keypair("/path", "", "agent-1").await; - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Path and subject are required")); -} - -#[tokio::test] -async fn test_derive_ecdsa_rejects_empty_path() { - let provider = PhalaDeriveKeyProvider::new("LOCAL").unwrap(); - let result = provider.derive_ecdsa_keypair("", "subject", "agent-1").await; - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Path and subject are required")); -} - -#[tokio::test] -async fn test_derive_ecdsa_rejects_empty_subject() { - let provider = PhalaDeriveKeyProvider::new("LOCAL").unwrap(); - let result = provider.derive_ecdsa_keypair("/path", "", "agent-1").await; - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Path and subject are required")); -} - -// =========================================================================== -// Attestation-related type structure tests -// =========================================================================== - -#[test] -fn test_remote_attestation_quote_structure() { - let quote = RemoteAttestationQuote { - quote: "deadbeef".to_string(), - timestamp: 1700000000000, - }; - assert_eq!(quote.quote, "deadbeef"); - assert_eq!(quote.timestamp, 1700000000000); - - // Verify JSON serialization - let json = serde_json::to_string("e).unwrap(); - assert!(json.contains("deadbeef")); - assert!(json.contains("1700000000000")); -} - -#[test] -fn test_remote_attestation_message_serialization() { - let msg = RemoteAttestationMessage { - agent_id: "agent-1".to_string(), - timestamp: 1234567890, - message: RemoteAttestationMessageContent { - entity_id: "entity-1".to_string(), - room_id: "room-1".to_string(), - content: "hello".to_string(), - }, - }; - - let json = serde_json::to_string(&msg).unwrap(); - // camelCase serialization - assert!(json.contains("agentId")); - assert!(json.contains("entityId")); - assert!(json.contains("roomId")); - assert!(json.contains("hello")); -} - -#[test] -fn test_derive_key_attestation_data_serialization() { - let data = DeriveKeyAttestationData { - agent_id: "agent-1".to_string(), - public_key: "pubkey123".to_string(), - subject: Some("solana".to_string()), - }; - - let json = serde_json::to_string(&data).unwrap(); - assert!(json.contains("agentId")); - assert!(json.contains("publicKey")); - assert!(json.contains("solana")); -} - -#[test] -fn test_derive_key_attestation_data_skips_none_subject() { - let data = DeriveKeyAttestationData { - agent_id: "agent-1".to_string(), - public_key: "pk".to_string(), - subject: None, - }; - - let json = serde_json::to_string(&data).unwrap(); - assert!(!json.contains("subject")); -} - -#[test] -fn test_tee_service_config_defaults() { - let config = TeeServiceConfig::default(); - assert_eq!(config.mode, TeeMode::Local); - assert_eq!(config.vendor, TeeVendor::Phala); - assert!(config.secret_salt.is_none()); -} - -#[test] -fn test_tdx_quote_hash_algorithms() { - let algos = [ - TdxQuoteHashAlgorithm::Sha256, - TdxQuoteHashAlgorithm::Sha384, - TdxQuoteHashAlgorithm::Sha512, - TdxQuoteHashAlgorithm::Raw, - ]; - - for algo in &algos { - let json = serde_json::to_string(algo).unwrap(); - let deserialized: TdxQuoteHashAlgorithm = serde_json::from_str(&json).unwrap(); - assert_eq!(*algo, deserialized); - } -} - -// =========================================================================== -// Plugin metadata tests -// =========================================================================== - -#[test] -fn test_plugin_name() { - assert_eq!(elizaos_plugin_tee::PLUGIN_NAME, "tee"); -} - -#[test] -fn test_plugin_description_mentions_tee() { - assert!(elizaos_plugin_tee::PLUGIN_DESCRIPTION.contains("TEE")); -} - -#[test] -fn test_plugin_version_not_empty() { - assert!(!elizaos_plugin_tee::PLUGIN_VERSION.is_empty()); -} diff --git a/src/__tests__/deriveKey.test.ts b/src/__tests__/deriveKey.test.ts new file mode 100644 index 0000000..2cdbb95 --- /dev/null +++ b/src/__tests__/deriveKey.test.ts @@ -0,0 +1,123 @@ +import { TEEMode } from "@elizaos/core"; +import { DstackClient } from "@phala/dstack-sdk"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { PhalaDeriveKeyProvider } from "../providers/deriveKeyProvider"; + +// Mock dependencies +vi.mock("@phala/dstack-sdk", () => ({ + DstackClient: vi.fn().mockImplementation(() => ({ + getKey: vi.fn().mockResolvedValue({ + asUint8Array: () => new Uint8Array([1, 2, 3, 4, 5]), + }), + getQuote: vi.fn().mockResolvedValue({ + quote: "mock-quote-data", + replayRtmrs: () => ["rtmr0", "rtmr1", "rtmr2", "rtmr3"], + }), + rawDeriveKey: vi.fn(), + })), +})); + +vi.mock("@solana/web3.js", () => ({ + Keypair: { + fromSeed: vi.fn().mockReturnValue({ + publicKey: { + toBase58: () => "mock-solana-public-key", + }, + }), + }, +})); + +vi.mock("viem/accounts", () => ({ + privateKeyToAccount: vi.fn().mockReturnValue({ + address: "mock-evm-address", + }), +})); + +describe("DeriveKeyProvider", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("constructor", () => { + it("should initialize with LOCAL mode", () => { + const _provider = new PhalaDeriveKeyProvider(TEEMode.LOCAL); + expect(DstackClient).toHaveBeenCalledWith("http://localhost:8090"); + }); + + it("should initialize with DOCKER mode", () => { + const _provider = new PhalaDeriveKeyProvider(TEEMode.DOCKER); + expect(DstackClient).toHaveBeenCalledWith( + "http://host.docker.internal:8090" + ); + }); + + it("should initialize with PRODUCTION mode", () => { + const _provider = new PhalaDeriveKeyProvider(TEEMode.PRODUCTION); + expect(DstackClient).toHaveBeenCalledWith(); + }); + + it("should throw error for invalid mode", () => { + expect(() => new PhalaDeriveKeyProvider("INVALID_MODE")).toThrow( + "Invalid TEE_MODE" + ); + }); + }); + + describe("rawDeriveKey", () => { + let _provider: PhalaDeriveKeyProvider; + + beforeEach(() => { + _provider = new PhalaDeriveKeyProvider(TEEMode.LOCAL); + }); + + it("should derive raw key successfully", async () => { + const path = "test-path"; + const subject = "test-subject"; + const result = await _provider.rawDeriveKey(path, subject); + + const client = vi.mocked(DstackClient).mock.results[0].value; + expect(client.getKey).toHaveBeenCalledWith(path, subject); + expect(result.key).toEqual(new Uint8Array([1, 2, 3, 4, 5])); + }); + + it("should handle errors during raw key derivation", async () => { + const mockError = new Error("Key derivation failed"); + vi.mocked(DstackClient).mockImplementationOnce(() => { + const instance = new DstackClient(); + instance.getKey = vi.fn().mockRejectedValueOnce(mockError); + instance.getQuote = vi.fn(); + instance.getKey = vi.fn(); + return instance; + }); + + const provider = new PhalaDeriveKeyProvider(TEEMode.LOCAL); + await expect(provider.rawDeriveKey("path", "subject")).rejects.toThrow( + mockError + ); + }); + }); + + describe("deriveEd25519Keypair", () => { + let provider: PhalaDeriveKeyProvider; + + beforeEach(() => { + provider = new PhalaDeriveKeyProvider(TEEMode.LOCAL); + }); + + it("should derive Ed25519 keypair successfully", async () => { + const path = "test-path"; + const subject = "test-subject"; + const result = await provider.deriveEd25519Keypair( + path, + subject, + "test-agent-id" + ); + + const client = vi.mocked(DstackClient).mock.results[0].value; + expect(client.getKey).toHaveBeenCalledWith(path, subject); + expect(result.keypair.publicKey.toBase58()).toEqual( + "mock-solana-public-key" + ); + }); + }); +}); diff --git a/src/__tests__/remoteAttestation.test.ts b/src/__tests__/remoteAttestation.test.ts new file mode 100644 index 0000000..fade125 --- /dev/null +++ b/src/__tests__/remoteAttestation.test.ts @@ -0,0 +1,92 @@ +import { TEEMode } from "@elizaos/core"; +import { DstackClient } from "@phala/dstack-sdk"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { PhalaRemoteAttestationProvider } from "../providers/remoteAttestationProvider"; + +// Mock DstackClient +vi.mock("@phala/dstack-sdk", () => ({ + DstackClient: vi.fn().mockImplementation(() => ({ + getQuote: vi.fn().mockResolvedValue({ + quote: "mock-quote-data", + replayRtmrs: () => ["rtmr0", "rtmr1", "rtmr2", "rtmr3"], + }), + getKey: vi.fn(), + endpoint: "http://localhost:8090", + info: vi.fn().mockResolvedValue({}), + })), +})); + +describe("PhalaRemoteAttestationProvider", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("constructor", () => { + it("should initialize with LOCAL mode", () => { + const _provider = new PhalaRemoteAttestationProvider(TEEMode.LOCAL); + expect(DstackClient).toHaveBeenCalledWith("http://localhost:8090"); + }); + + it("should initialize with DOCKER mode", () => { + const _provider = new PhalaRemoteAttestationProvider(TEEMode.DOCKER); + expect(DstackClient).toHaveBeenCalledWith( + "http://host.docker.internal:8090" + ); + }); + + it("should initialize with PRODUCTION mode", () => { + const _provider = new PhalaRemoteAttestationProvider(TEEMode.PRODUCTION); + expect(DstackClient).toHaveBeenCalledWith(); + }); + + it("should throw error for invalid mode", () => { + expect(() => new PhalaRemoteAttestationProvider("INVALID_MODE")).toThrow( + "Invalid TEE_MODE" + ); + }); + }); + + describe("generateAttestation", () => { + let provider: PhalaRemoteAttestationProvider; + + beforeEach(() => { + provider = new PhalaRemoteAttestationProvider(TEEMode.LOCAL); + }); + + it("should generate attestation successfully", async () => { + const reportData = "test-report-data"; + const quote = await provider.generateAttestation(reportData); + + expect(quote).toEqual({ + quote: "mock-quote-data", + timestamp: expect.any(Number), + }); + }); + + it("should handle errors during attestation generation", async () => { + const mockError = new Error("TDX Quote generation failed"); + const mockTdxQuote = vi.fn().mockRejectedValue(mockError); + vi.mocked(DstackClient).mockImplementationOnce( + () => + ({ + getQuote: mockTdxQuote, + getKey: vi.fn(), + }) as unknown as DstackClient + ); + + const provider = new PhalaRemoteAttestationProvider(TEEMode.LOCAL); + await expect(provider.generateAttestation("test-data")).rejects.toThrow( + "Failed to generate TDX Quote" + ); + }); + + it("should pass hash algorithm to tdxQuote when provided", async () => { + const reportData = "test-report-data"; + const hashAlgorithm = "raw"; + await provider.generateAttestation(reportData); + + const client = vi.mocked(DstackClient).mock.results[0].value; + expect(client.getQuote).toHaveBeenCalledWith(reportData, hashAlgorithm); + }); + }); +}); diff --git a/src/__tests__/timeout.test.ts b/src/__tests__/timeout.test.ts new file mode 100644 index 0000000..a837da0 --- /dev/null +++ b/src/__tests__/timeout.test.ts @@ -0,0 +1,142 @@ +import { TEEMode } from "@elizaos/core"; +import { DstackClient } from "@phala/dstack-sdk"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { PhalaDeriveKeyProvider } from "../providers/deriveKeyProvider"; +import { PhalaRemoteAttestationProvider } from "../providers/remoteAttestationProvider"; + +// Mock DstackClient +vi.mock("@phala/dstack-sdk", () => ({ + DstackClient: vi.fn().mockImplementation(() => ({ + getQuote: vi.fn(), + getKey: vi.fn(), + endpoint: "http://localhost:8090", // Add this + info: vi.fn().mockResolvedValue({}), // Add this + })), +})); + +describe("TEE Provider Timeout Tests", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("PhalaRemoteAttestationProvider", () => { + it("should handle API timeout during attestation generation", async () => { + const mockTdxQuote = vi + .fn() + .mockRejectedValueOnce(new Error("Request timed out")); + + vi.mocked(DstackClient).mockImplementation( + () => + ({ + getQuote: mockTdxQuote, + getKey: vi.fn(), + }) as unknown as DstackClient + ); + + const provider = new PhalaRemoteAttestationProvider(TEEMode.LOCAL); + await expect(() => + provider.generateAttestation("test-data") + ).rejects.toThrow("Failed to generate TDX Quote: Request timed out"); + + // Verify the call was made once + expect(mockTdxQuote).toHaveBeenCalledTimes(1); + expect(mockTdxQuote).toHaveBeenCalledWith("test-data", undefined); + }); + + it("should handle network errors during attestation generation", async () => { + const mockTdxQuote = vi + .fn() + .mockRejectedValueOnce(new Error("Network error")); + + vi.mocked(DstackClient).mockImplementation( + () => + ({ + getQuote: mockTdxQuote, + getKey: vi.fn(), + }) as unknown as DstackClient + ); + + const provider = new PhalaRemoteAttestationProvider(TEEMode.LOCAL); + await expect(() => + provider.generateAttestation("test-data") + ).rejects.toThrow("Failed to generate TDX Quote: Network error"); + + expect(mockTdxQuote).toHaveBeenCalledTimes(1); + }); + + it("should handle successful attestation generation", async () => { + const mockQuote = { + quote: "test-quote", + replayRtmrs: () => ["rtmr0", "rtmr1", "rtmr2", "rtmr3"], + }; + + const mockTdxQuote = vi.fn().mockResolvedValueOnce(mockQuote); + + vi.mocked(DstackClient).mockImplementation( + () => + ({ + getQuote: mockTdxQuote, + getKey: vi.fn(), + }) as unknown as DstackClient + ); + + const provider = new PhalaRemoteAttestationProvider(TEEMode.LOCAL); + const result = await provider.generateAttestation("test-data"); + + expect(mockTdxQuote).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + quote: "test-quote", + timestamp: expect.any(Number), + }); + }); + }); + + describe("PhalaDeriveKeyProvider", () => { + it("should handle API timeout during key derivation", async () => { + const mockDeriveKey = vi + .fn() + .mockRejectedValueOnce(new Error("Request timed out")); + + vi.mocked(DstackClient).mockImplementation( + () => + ({ + getQuote: vi.fn(), + getKey: vi.fn(), + }) as unknown as DstackClient + ); + + const provider = new PhalaDeriveKeyProvider(TEEMode.LOCAL); + await expect(() => + provider.rawDeriveKey("test-path", "test-subject") + ).rejects.toThrow("Request timed out"); + + expect(mockDeriveKey).toHaveBeenCalledTimes(1); + expect(mockDeriveKey).toHaveBeenCalledWith("test-path", "test-subject"); + }); + + it("should handle API timeout during Ed25519 key derivation", async () => { + const mockDeriveKey = vi + .fn() + .mockRejectedValueOnce(new Error("Request timed out")); + + vi.mocked(DstackClient).mockImplementation( + () => + ({ + getQuote: vi.fn(), + getKey: vi.fn(), + }) as unknown as DstackClient + ); + + const provider = new PhalaDeriveKeyProvider(TEEMode.LOCAL); + await expect(() => + provider.deriveEd25519Keypair( + "test-path", + "test-subject", + "test-agent-id" + ) + ).rejects.toThrow("Request timed out"); + + expect(mockDeriveKey).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/actions/remoteAttestationAction.ts b/src/actions/remoteAttestationAction.ts new file mode 100644 index 0000000..8f7bbc0 --- /dev/null +++ b/src/actions/remoteAttestationAction.ts @@ -0,0 +1,143 @@ +import type { + Action, + HandlerCallback, + IAgentRuntime, + Memory, + RemoteAttestationMessage, + State, +} from "@elizaos/core"; +import { logger } from "@elizaos/core"; +import { PhalaRemoteAttestationProvider as RemoteAttestationProvider } from "../providers/remoteAttestationProvider"; +import { hexToUint8Array } from "../utils"; + +/** + * Asynchronously uploads a Uint8Array as a binary file to a specified URL. + * + * @param {Uint8Array} data - The Uint8Array data to be uploaded as a binary file. + * @returns {Promise} A Promise that resolves once the upload is complete, returning a Response object. + */ +async function uploadUint8Array(data: Uint8Array) { + const blob = new Blob([data], { type: "application/octet-stream" }); + const formData = new FormData(); + formData.append("file", blob, "quote.bin"); + + return await fetch("https://proof.t16z.com/api/upload", { + method: "POST", + body: formData as BodyInit, + }); +} + +/** + * Represents an action for remote attestation. + * + * This action is used to generate a remote attestation to prove that the agent is running in a Trusted Execution Environment (TEE). + * + * @type {{name: string, similes: string[], description: string, handler: Function, validate: Function, examples: Array>}} + */ +export const phalaRemoteAttestationAction: Action = { + name: "REMOTE_ATTESTATION", + similes: [ + "REMOTE_ATTESTATION", + "TEE_REMOTE_ATTESTATION", + "TEE_ATTESTATION", + "TEE_QUOTE", + "ATTESTATION", + "TEE_ATTESTATION_QUOTE", + ], + description: + "Generate a remote attestation to prove that the agent is running in a TEE", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State | undefined, + _options?: { [key: string]: unknown } | undefined, + callback?: HandlerCallback | undefined + ) => { + try { + // Attestation will be generated based on the message info + const attestationMessage: RemoteAttestationMessage = { + agentId: runtime.agentId, + timestamp: Date.now(), + message: { + entityId: message.entityId, + roomId: message.roomId, + content: message.content.text || "", + }, + }; + // Get the remote attestation of the agentId + const teeMode = runtime.getSetting("TEE_MODE"); + logger.debug(`Tee mode: ${teeMode}`); + logger.debug( + `Attestation message: ${JSON.stringify(attestationMessage)}` + ); + const provider = new RemoteAttestationProvider(teeMode); + + const attestation = await provider.generateAttestation( + JSON.stringify(attestationMessage) + ); + const attestationData = hexToUint8Array(attestation.quote); + const response = await uploadUint8Array(attestationData); + const data = await response.json(); + + callback?.({ + text: `Here's my 🧾 RA Quote 🫡 +https://proof.t16z.com/reports/${data.checksum}`, + actions: ["NONE"], + }); + return true; + } catch (error) { + console.error("Failed to fetch remote attestation: ", error); + return false; + } + }, + validate: async (_runtime: IAgentRuntime) => { + return true; + }, + examples: [ + [ + { + name: "{{name1}}", + content: { + text: "If you are running in a TEE, generate a remote attestation", + }, + }, + { + name: "{{agentName}}", + content: { + text: "Of course, one second...", + actions: ["REMOTE_ATTESTATION"], + }, + }, + ], + [ + { + name: "{{name1}}", + content: { + text: "Yo I wanna attest to this message, yo! Can you generate an attestatin for me, please?", + }, + }, + { + name: "{{agentName}}", + content: { + text: "I got you, fam! Lemme hit the cloud and get you a quote in a jiffy!", + actions: ["REMOTE_ATTESTATION"], + }, + }, + ], + [ + { + name: "{{name1}}", + content: { + text: "It was a long day, I got a lot done though. I went to the creek and skipped some rocks. Then I decided to take a walk off the natural path. I ended up in a forest I was unfamiliar with. Slowly, I lost the way back and it was dark. A whisper from deep inside said something I could barely make out. The hairs on my neck stood up and then a clear high pitched voice said, 'You are not ready to leave yet! SHOW ME YOUR REMOTE ATTESTATION!'", + }, + }, + { + name: "{{agentName}}", + content: { + text: "Oh, dear...lemme find that for you", + actions: ["REMOTE_ATTESTATION"], + }, + }, + ], + ], +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1ac1fbf --- /dev/null +++ b/src/index.ts @@ -0,0 +1,32 @@ +import { type IAgentRuntime, type Plugin, type TeeVendorConfig, logger } from "@elizaos/core"; +import { TeeVendorNames } from "./vendors/types"; +import { getVendor } from "./vendors/index"; +import { TEEService } from "./service"; + +export type { TeeVendorConfig }; +// Get the default Phala vendor +const defaultVendor = getVendor(TeeVendorNames.PHALA); + +/** + * TEE plugin for Trusted Execution Environment integration + **/ +export const teePlugin: Plugin = { + name: "tee-plugin", + description: "Trusted Execution Environment (TEE) integration plugin", + init: async (config: Record, runtime: IAgentRuntime) => { + const vendorName = + config.TEE_VENDOR || runtime.getSetting('TEE_VENDOR') || TeeVendorNames.PHALA; + logger.info(`Initializing TEE with vendor: ${vendorName}`); + + // Configure vendor-specific settings if needed for now only Phala is supported + // This is where you'd handle any vendor-specific initialization + + logger.info(`TEE initialized with vendor: ${vendorName}`); + }, + actions: defaultVendor.getActions(), + evaluators: [], + providers: defaultVendor.getProviders(), + services: [TEEService], +}; + +export default teePlugin; \ No newline at end of file diff --git a/src/providers/base.ts b/src/providers/base.ts new file mode 100644 index 0000000..90beddc --- /dev/null +++ b/src/providers/base.ts @@ -0,0 +1,35 @@ +import type { RemoteAttestationQuote } from "@elizaos/core"; +import type { TdxQuoteHashAlgorithms } from "@phala/dstack-sdk"; + +/** + * Abstract class for deriving keys from the TEE. + * You can implement your own logic for deriving keys from the TEE. + * @example + * ```ts + * class MyDeriveKeyProvider extends DeriveKeyProvider { + * private client: DstackClient; + * + * constructor(endpoint: string) { + * super(); + * this.client = new DstackClient(); + * } + * + * async rawDeriveKey(path: string, subject: string): Promise { + * return this.client.getKey(path, subject); + * } + * } + * ``` + */ +/** + * Abstract class representing a key provider for deriving keys. + */ +export abstract class DeriveKeyProvider {} + +/** + * Abstract class for remote attestation provider. + */ +export abstract class RemoteAttestationProvider { + abstract generateAttestation( + reportData: string, + ): Promise; +} diff --git a/src/providers/deriveKeyProvider.ts b/src/providers/deriveKeyProvider.ts new file mode 100644 index 0000000..927e9b2 --- /dev/null +++ b/src/providers/deriveKeyProvider.ts @@ -0,0 +1,276 @@ +import { + type IAgentRuntime, + type Memory, + type Provider, + logger, +} from "@elizaos/core"; +import { + type DeriveKeyAttestationData, + type RemoteAttestationQuote, + TEEMode, +} from "@elizaos/core"; +import { type GetKeyResponse, DstackClient } from "@phala/dstack-sdk"; +import { DeriveKeyProvider } from "./base"; +import { PhalaRemoteAttestationProvider as RemoteAttestationProvider } from "./remoteAttestationProvider"; +import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts'; +import { keccak256 } from 'viem'; +import { Keypair } from '@solana/web3.js'; +import crypto from 'node:crypto'; + +/** + * Phala TEE Cloud Provider + * @example + * ```ts + * const provider = new PhalaDeriveKeyProvider(runtime.getSetting('TEE_MODE')); + * ``` + */ +/** + * A class representing a key provider for deriving keys in the Phala TEE environment. + * Extends the DeriveKeyProvider class. + */ +class PhalaDeriveKeyProvider extends DeriveKeyProvider { + private client: DstackClient; + private raProvider: RemoteAttestationProvider; + + constructor(teeMode?: string) { + super(); + 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"; + logger.log("TEE: Connecting to local simulator at localhost:8090"); + break; + case TEEMode.DOCKER: + endpoint = "http://host.docker.internal:8090"; + logger.log( + "TEE: Connecting to simulator via Docker at host.docker.internal:8090" + ); + break; + case TEEMode.PRODUCTION: + endpoint = undefined; + logger.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 DstackClient(endpoint) : new DstackClient(); + this.raProvider = new RemoteAttestationProvider(teeMode); + } + + private async generateDeriveKeyAttestation( + agentId: string, + publicKey: string, + subject?: string + ): Promise { + const deriveKeyData: DeriveKeyAttestationData = { + agentId, + publicKey, + subject, + }; + const reportdata = JSON.stringify(deriveKeyData); + logger.log("Generating Remote Attestation Quote for Derive Key..."); + const quote = await this.raProvider.generateAttestation(reportdata); + logger.log("Remote Attestation Quote generated successfully!"); + return quote; + } + + /** + * Derives a raw key from the given path and subject. + * @param path - The path to derive the key from. This is used to derive the key from the root of trust. + * @param subject - The subject to derive the key from. This is used for the certificate chain. + * @returns The derived key. + */ + async rawDeriveKey( + path: string, + subject: string + ): Promise { + try { + if (!path || !subject) { + logger.error("Path and Subject are required for key derivation"); + } + + logger.log("Deriving Raw Key in TEE..."); + const derivedKey = await this.client.getKey(path, subject); + + logger.log("Raw Key Derived Successfully!"); + return derivedKey; + } catch (error) { + logger.error("Error deriving raw key:", error); + throw error; + } + } + + /** + * Derives an Ed25519 keypair from the given path and subject. + * @param path - The path to derive the key from. This is used to derive the key from the root of trust. + * @param subject - The subject to derive the key from. This is used for the certificate chain. + * @param agentId - The agent ID to generate an attestation for. + * @returns An object containing the derived keypair and attestation. + */ + async deriveEd25519Keypair( + path: string, + subject: string, + agentId: string + ): Promise<{ keypair: Keypair; attestation: RemoteAttestationQuote }> { + try { + if (!path) { + logger.error("Path is required for key derivation"); + } + + logger.log("Deriving Ed25519 Key in TEE..."); + const derivedKey = await this.client.getKey(path, subject); + const uint8ArrayDerivedKey = derivedKey.key; + 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)) as unknown as Keypair; + + // Generate an attestation for the derived key data for public to verify + const attestation = await this.generateDeriveKeyAttestation( + agentId, + keypair.publicKey.toBase58() + ); + logger.log("Key Derived Successfully!"); + + return { keypair, attestation }; + } catch (error) { + logger.error("Error deriving key:", error); + throw error; + } + } + + /** + * Derives an ECDSA keypair from the given path and subject. + * @param path - The path to derive the key from. This is used to derive the key from the root of trust. + * @param subject - The subject to derive the key from. This is used for the certificate chain. + * @param agentId - The agent ID to generate an attestation for. This is used for the certificate chain. + * @returns An object containing the derived keypair and attestation. + */ + async deriveEcdsaKeypair( + path: string, + subject: string, + agentId: string + ): Promise<{ + keypair: PrivateKeyAccount; + attestation: RemoteAttestationQuote; + }> { + try { + if (!path || !subject) { + logger.error("Path and Subject are required for key derivation"); + } + + logger.log("Deriving ECDSA Key in TEE..."); + const deriveKeyResponse: GetKeyResponse = await this.client.getKey( + path, + subject + ); + const hex = keccak256(deriveKeyResponse.key); + const keypair: PrivateKeyAccount = + privateKeyToAccount(hex) as unknown as PrivateKeyAccount; + + // Generate an attestation for the derived key data for public to verify + const attestation = await this.generateDeriveKeyAttestation( + agentId, + keypair.address + ); + logger.log("ECDSA Key Derived Successfully!"); + + return { keypair, attestation }; + } catch (error) { + logger.error("Error deriving ecdsa key:", error); + throw error; + } + } +} + +// Define the ProviderResult interface if not already imported +interface ProviderResult { + data?: any; + values?: Record; + text?: string; +} + +const phalaDeriveKeyProvider: Provider = { + name: "phala-derive-key", + get: async ( + runtime: IAgentRuntime, + _message?: Memory + ): Promise => { + const teeMode = runtime.getSetting("TEE_MODE"); + const provider = new PhalaDeriveKeyProvider(teeMode); + const agentId = runtime.agentId; + try { + // Validate wallet configuration + if (!runtime.getSetting("WALLET_SECRET_SALT")) { + logger.error("Wallet secret salt is not configured in settings"); + return { + data: null, + values: {}, + text: "Wallet secret salt is not configured in settings", + }; + } + + try { + const secretSalt = + runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; + const solanaKeypair = await provider.deriveEd25519Keypair( + secretSalt, + "solana", + agentId + ); + const evmKeypair = await provider.deriveEcdsaKeypair( + secretSalt, + "evm", + agentId + ); + + // Original data structure + const walletData = { + solana: solanaKeypair.keypair.publicKey, + evm: evmKeypair.keypair.address, + }; + + // Values for template injection + const values = { + solana_public_key: solanaKeypair.keypair.publicKey.toString(), + evm_address: evmKeypair.keypair.address, + }; + + // Text representation + const text = `Solana Public Key: ${values.solana_public_key}\nEVM Address: ${values.evm_address}`; + + return { + data: walletData, + values: values, + text: text, + }; + } catch (error) { + logger.error("Error creating PublicKey:", error); + return { + data: null, + values: {}, + text: `Error creating PublicKey: ${ + error instanceof Error ? error.message : "Unknown error" + }`, + }; + } + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + logger.error("Error in derive key provider:", errorMessage); + return { + data: null, + values: {}, + text: `Failed to fetch derive key information: ${errorMessage}`, + }; + } + }, +}; + +export { phalaDeriveKeyProvider, PhalaDeriveKeyProvider }; diff --git a/src/providers/remoteAttestationProvider.ts b/src/providers/remoteAttestationProvider.ts new file mode 100644 index 0000000..62aa0db --- /dev/null +++ b/src/providers/remoteAttestationProvider.ts @@ -0,0 +1,146 @@ +import { + type IAgentRuntime, + type Memory, + type Provider, + logger, +} from "@elizaos/core"; +import { + type RemoteAttestationMessage, + type RemoteAttestationQuote, + TEEMode, +} from "@elizaos/core"; +import { + DstackClient, + type GetQuoteResponse, +} from "@phala/dstack-sdk"; +import { RemoteAttestationProvider } from "./base"; + +// Define the ProviderResult interface if not already imported +/** + * Interface for the result returned by a provider. + * @typedef {Object} ProviderResult + * @property {any} data - The data returned by the provider. + * @property {Record} values - The values returned by the provider. + * @property {string} text - The text returned by the provider. + */ +interface ProviderResult { + data?: any; + values?: Record; + text?: string; +} + +/** + * Phala TEE Cloud Provider + * @example + * ```ts + * const provider = new PhalaRemoteAttestationProvider(); + * ``` + */ +/** + * PhalaRemoteAttestationProvider class that extends RemoteAttestationProvider + * @extends RemoteAttestationProvider + */ +class PhalaRemoteAttestationProvider extends RemoteAttestationProvider { + private client: DstackClient; + + constructor(teeMode?: string) { + super(); + 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"; + logger.log("TEE: Connecting to local simulator at localhost:8090"); + break; + case TEEMode.DOCKER: + endpoint = "http://host.docker.internal:8090"; + logger.log( + "TEE: Connecting to simulator via Docker at host.docker.internal:8090" + ); + break; + case TEEMode.PRODUCTION: + endpoint = undefined; + logger.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 DstackClient(endpoint) : new DstackClient(); + } + + async generateAttestation( + reportData: string, + ): Promise { + try { + logger.log("Generating attestation for: ", reportData); + const getQuote: GetQuoteResponse = await this.client.getQuote( + reportData, + ); + const rtmrs = getQuote.replayRtmrs(); + logger.log( + `rtmr0: ${rtmrs[0]}\nrtmr1: ${rtmrs[1]}\nrtmr2: ${rtmrs[2]}\nrtmr3: ${rtmrs[3]}f` + ); + const quote: RemoteAttestationQuote = { + quote: getQuote.quote, + timestamp: Date.now(), + }; + logger.log("Remote attestation quote: ", quote); + return quote; + } catch (error) { + console.error("Error generating remote attestation:", error); + throw new Error( + `Failed to generate TDX Quote: ${error instanceof Error ? error.message : "Unknown error"}` + ); + } + } +} + +// Keep the original provider for backwards compatibility +const phalaRemoteAttestationProvider: Provider = { + name: "phala-remote-attestation", + get: async (runtime: IAgentRuntime, message: Memory) => { + const teeMode = runtime.getSetting("TEE_MODE"); + const provider = new PhalaRemoteAttestationProvider(teeMode); + const agentId = runtime.agentId; + + try { + const attestationMessage: RemoteAttestationMessage = { + agentId: agentId, + timestamp: Date.now(), + message: { + entityId: message.entityId, + roomId: message.roomId, + content: message.content.text || "", + }, + }; + logger.log( + "Generating attestation for: ", + JSON.stringify(attestationMessage) + ); + const attestation = await provider.generateAttestation( + JSON.stringify(attestationMessage) + ); + return { + text: `Your Agent's remote attestation is: ${JSON.stringify(attestation)}`, + data: { + attestation, + }, + values: { + quote: attestation.quote, + timestamp: attestation.timestamp.toString(), + }, + }; + } catch (error) { + console.error("Error in remote attestation provider:", error); + throw new Error( + `Failed to generate TDX Quote: ${error instanceof Error ? error.message : "Unknown error"}` + ); + } + }, +}; + +export { phalaRemoteAttestationProvider, PhalaRemoteAttestationProvider }; diff --git a/src/service.ts b/src/service.ts new file mode 100644 index 0000000..fe107ad --- /dev/null +++ b/src/service.ts @@ -0,0 +1,69 @@ +import { type IAgentRuntime, Service, ServiceType, type UUID, logger } from '@elizaos/core'; +import { PrivateKeyAccount } from 'viem'; +import { Keypair } from '@solana/web3.js'; +import { type RemoteAttestationQuote } from '@elizaos/core'; +import { PhalaDeriveKeyProvider } from './providers/deriveKeyProvider'; +import { GetKeyResponse } from '@phala/dstack-sdk'; + +interface TEEServiceConfig { + teeMode?: string; +} + +export class TEEService extends Service { + private provider: PhalaDeriveKeyProvider; + public config: TEEServiceConfig; + static serviceType = ServiceType.TEE; + public capabilityDescription = 'Trusted Execution Environment for secure key management'; + + constructor(runtime: IAgentRuntime, config: TEEServiceConfig = {}) { + super(runtime); + this.config = config; + const teeMode = config.teeMode || runtime.getSetting('TEE_MODE'); + this.provider = new PhalaDeriveKeyProvider(teeMode); + } + + static async start(runtime: IAgentRuntime): Promise { + const teeMode = runtime.getSetting('TEE_MODE'); + logger.log(`Starting TEE service with mode: ${teeMode}`); + const teeService = new TEEService(runtime, { teeMode }); + return teeService; + } + + async stop(): Promise { + logger.log('Stopping TEE service'); + // No cleanup needed for now + } + + async deriveEcdsaKeypair( + path: string, + subject: string, + agentId: UUID + ): Promise<{ + keypair: PrivateKeyAccount; + attestation: RemoteAttestationQuote; + }> { + logger.log('TEE Service: Deriving ECDSA keypair'); + return await this.provider.deriveEcdsaKeypair(path, subject, agentId); + } + + async deriveEd25519Keypair( + path: string, + subject: string, + agentId: UUID + ): Promise<{ + keypair: Keypair; + attestation: RemoteAttestationQuote; + }> { + logger.log('TEE Service: Deriving Ed25519 keypair'); + return await this.provider.deriveEd25519Keypair(path, subject, agentId); + } + + async rawDeriveKey( + path: string, + subject: string + ): Promise { + logger.log('TEE Service: Deriving Raw Key'); + return await this.provider.rawDeriveKey(path, subject); + } + +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..7806e3f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,10 @@ +/** + * Enum representing different types of Tee. + * @enum {string} + * @readonly + */ + +export enum TeeType { + SGX_GRAMINE = "sgx_gramine", + TDX_DSTACK = "tdx_dstack", +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..57f2a1c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,41 @@ +import { createHash } from "node:crypto"; + +/** + * Converts a hexadecimal string to a Uint8Array. + * + * @param {string} hex - The hexadecimal string to convert. + * @returns {Uint8Array} - The resulting Uint8Array. + * @throws {Error} - If the input hex string is invalid. + */ +export function hexToUint8Array(hex: string) { + const hexString = hex.trim().replace(/^0x/, ""); + if (!hexString) { + throw new Error("Invalid hex string"); + } + if (hexString.length % 2 !== 0) { + throw new Error("Invalid hex string"); + } + + const array = new Uint8Array(hexString.length / 2); + for (let i = 0; i < hexString.length; i += 2) { + const byte = Number.parseInt(hexString.slice(i, i + 2), 16); + if (Number.isNaN(byte)) { + throw new Error("Invalid hex string"); + } + array[i / 2] = byte; + } + return array; +} + +// Function to calculate SHA-256 and return a Buffer (32 bytes) +/** + * Calculates the SHA256 hash of the input string. + * + * @param {string} input - The input string to calculate the hash from. + * @returns {Buffer} - The calculated SHA256 hash as a Buffer object. + */ +export function calculateSHA256(input: string): Buffer { + const hash = createHash("sha256"); + hash.update(input); + return hash.digest(); +} diff --git a/src/vendors/index.ts b/src/vendors/index.ts new file mode 100644 index 0000000..da6b322 --- /dev/null +++ b/src/vendors/index.ts @@ -0,0 +1,17 @@ +import { PhalaVendor } from "./phala"; +import type { TeeVendor } from "./types"; +import { type TeeVendorName, TeeVendorNames } from "./types"; + +const vendors: Record = { + [TeeVendorNames.PHALA]: new PhalaVendor() as TeeVendor, +}; + +export const getVendor = (type: TeeVendorName): TeeVendor => { + const vendor = vendors[type]; + if (!vendor) { + throw new Error(`Unsupported TEE vendor type: ${type}`); + } + return vendor; +}; + +export * from "./types"; diff --git a/src/vendors/phala.ts b/src/vendors/phala.ts new file mode 100644 index 0000000..4ec4b33 --- /dev/null +++ b/src/vendors/phala.ts @@ -0,0 +1,59 @@ +import { type Action } from "@elizaos/core"; +import { phalaRemoteAttestationAction as remoteAttestationAction } from "../actions/remoteAttestationAction"; +import { phalaDeriveKeyProvider as deriveKeyProvider } from "../providers/deriveKeyProvider"; +import { phalaRemoteAttestationProvider as remoteAttestationProvider } from "../providers/remoteAttestationProvider"; +import { type TeeVendor, TeeVendorNames } from "./types"; + +/** + * A class representing a vendor for Phala TEE. + * * @implements { TeeVendor } + * @type {TeeVendorNames.PHALA} + *//** + * Get the actions for the PhalaVendor. + * * @returns { Array } An array of actions. + *//** + * Get the providers for the PhalaVendor. + * * @returns { Array } An array of providers. + *//** + * Get the name of the PhalaVendor. + * * @returns { string } The name of the vendor. + *//** + * Get the description of the PhalaVendor. + * * @returns { string } The description of the vendor. + */ +export class PhalaVendor implements TeeVendor { + type = TeeVendorNames.PHALA; + + /** + * Returns an array of actions. + * + * @returns {Array} An array containing the remote attestation action. + */ + getActions(): Action[] { + return [remoteAttestationAction]; + } + /** + * Retrieve the list of providers. + * + * @returns {Array} An array containing two provider functions: deriveKeyProvider and remoteAttestationProvider. + */ + getProviders() { + return [deriveKeyProvider, remoteAttestationProvider]; + } + + /** + * Returns the name of the plugin. + * @returns {string} The name of the plugin. + */ + getName() { + return "phala-tee-plugin"; + } + + /** + * Get the description of the function + * @returns {string} The description of the function + */ + getDescription() { + return "Phala TEE Cloud to Host Eliza Agents"; + } +} diff --git a/src/vendors/types.ts b/src/vendors/types.ts new file mode 100644 index 0000000..32e59a3 --- /dev/null +++ b/src/vendors/types.ts @@ -0,0 +1,26 @@ +import type { Action, Provider } from "@elizaos/core"; + +export const TeeVendorNames = { + PHALA: "phala", +} as const; + +/** + * Type representing the name of a Tee vendor. + * It can either be one of the keys of TeeVendorNames or a string. + */ +export type TeeVendorName = + | (typeof TeeVendorNames)[keyof typeof TeeVendorNames] + | string; + +/** + * Interface for a TeeVendor, representing a vendor that sells tees. + * @interface + */ + +export interface TeeVendor { + type: TeeVendorName; + getActions(): Action[]; + getProviders(): Provider[]; + getName(): string; + getDescription(): string; +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..b9bb614 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "emitDeclarationOnly": true, + "paths": {} + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..de3ee55 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleResolution": "Bundler", + "strict": true, + "esModuleInterop": true, + "allowImportingTsExtensions": true, + "declaration": true, + "emitDeclarationOnly": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "allowArbitraryExtensions": true, + "paths": { + "@elizaos/core": ["../core/src"], + "@elizaos/core/*": ["../core/src/*"] + } + }, + "include": ["src/**/*.ts"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..2719330 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + outDir: 'dist', + tsconfig: './tsconfig.build.json', // Use build-specific tsconfig + sourcemap: true, + format: ['esm'], // Ensure you're targeting CommonJS + dts: true, + 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', + 'safe-buffer', + 'base-x', + 'bs58', + 'borsh', + 'stream', + 'buffer', + // Add other modules you want to externalize + '@phala/dstack-sdk', + 'undici', + '@elizaos/core', + 'zod', + ], +}); diff --git a/typescript/.npmignore b/typescript/.npmignore deleted file mode 100644 index 9f74001..0000000 --- a/typescript/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -* - -!dist/** -!package.json -!README.md -!tsup.config.ts -!LICENSE \ No newline at end of file diff --git a/typescript/LICENSE b/typescript/LICENSE deleted file mode 100644 index 6fa975f..0000000 --- a/typescript/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -MIT License - -Copyright (c) 2024 elizaOS Team - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - - - - - diff --git a/typescript/biome.json.bak b/typescript/biome.json.bak deleted file mode 100644 index a393bcd..0000000 --- a/typescript/biome.json.bak +++ /dev/null @@ -1,34 +0,0 @@ -{ - "root": false, - "$schema": "https://biomejs.dev/schemas/2.3.15/schema.json", - "assist": { "actions": { "source": { "organizeImports": "on" } } }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "correctness": { - "noUnusedVariables": "error" - }, - "a11y": { - "useButtonType": "off" - }, - "style": {}, - "complexity": { - "noCommaOperator": "off" - } - } - }, - "formatter": { - "enabled": true, - "indentWidth": 2, - "indentStyle": "space", - "lineWidth": 100 - }, - "javascript": { - "formatter": { - "quoteStyle": "double", - "trailingCommas": "es5", - "semicolons": "always" - } - } -} diff --git a/typescript/build.ts b/typescript/build.ts deleted file mode 100644 index f5e68f0..0000000 --- a/typescript/build.ts +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bun - -import { existsSync } from "node:fs"; -import { rm } from "node:fs/promises"; - -const externalDeps = [ - "dotenv", - "fs", - "path", - "@reflink/reflink", - "@node-llama-cpp", - "https", - "http", - "agentkeepalive", - "safe-buffer", - "base-x", - "bs58", - "borsh", - "stream", - "buffer", - "@phala/dstack-sdk", - "undici", - "@elizaos/core", - "zod", -]; - -async function buildPlugin() { - console.log("Building @elizaos/plugin-tee...\n"); - - if (existsSync("dist")) { - await rm("dist", { recursive: true, force: true }); - } - - const buildResult = await Bun.build({ - entrypoints: ["src/index.ts"], - outdir: "dist", - target: "node", - format: "esm", - sourcemap: "external", - minify: false, - external: externalDeps, - }); - - if (!buildResult.success) { - console.error("Build failed:"); - for (const log of buildResult.logs) { - console.error(log); - } - process.exit(1); - } - - console.log(`Built ${buildResult.outputs.length} file(s)`); - - const tscProcess = Bun.spawn(["bunx", "tsc", "-p", "tsconfig.build.json"], { - stdout: "inherit", - stderr: "inherit", - }); - await tscProcess.exited; - - if (tscProcess.exitCode !== 0) { - console.error("TypeScript declaration generation failed"); - process.exit(1); - } - - console.log("\nBuild complete!"); -} - -buildPlugin().catch((error) => { - console.error("Build failed:", error); - process.exit(1); -}); diff --git a/typescript/generated/specs/specs.ts b/typescript/generated/specs/specs.ts deleted file mode 100644 index 5d60cfa..0000000 --- a/typescript/generated/specs/specs.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Auto-generated canonical action/provider/evaluator docs for plugin-tee. - * DO NOT EDIT - Generated from prompts/specs/**. - */ - -export type ActionDoc = { - name: string; - description: string; - similes?: readonly string[]; - parameters?: readonly unknown[]; - examples?: readonly (readonly unknown[])[]; -}; - -export type ProviderDoc = { - name: string; - description: string; - position?: number; - dynamic?: boolean; -}; - -export type EvaluatorDoc = { - name: string; - description: string; - similes?: readonly string[]; - alwaysRun?: boolean; - examples?: readonly unknown[]; -}; - -export const coreActionsSpec = { - version: "1.0.0", - actions: [], -} as const; -export const allActionsSpec = { - version: "1.0.0", - actions: [], -} as const; -export const coreProvidersSpec = { - version: "1.0.0", - providers: [], -} as const; -export const allProvidersSpec = { - version: "1.0.0", - providers: [], -} as const; -export const coreEvaluatorsSpec = { - version: "1.0.0", - evaluators: [], -} as const; -export const allEvaluatorsSpec = { - version: "1.0.0", - evaluators: [], -} as const; - -export const coreActionDocs: readonly ActionDoc[] = coreActionsSpec.actions; -export const allActionDocs: readonly ActionDoc[] = allActionsSpec.actions; -export const coreProviderDocs: readonly ProviderDoc[] = coreProvidersSpec.providers; -export const allProviderDocs: readonly ProviderDoc[] = allProvidersSpec.providers; -export const coreEvaluatorDocs: readonly EvaluatorDoc[] = coreEvaluatorsSpec.evaluators; -export const allEvaluatorDocs: readonly EvaluatorDoc[] = allEvaluatorsSpec.evaluators; diff --git a/typescript/index.browser.ts b/typescript/index.browser.ts deleted file mode 100644 index 75c3d59..0000000 --- a/typescript/index.browser.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { IAgentRuntime, Plugin } from "@elizaos/core"; -import { logger } from "@elizaos/core"; - -const pluginName = "tee"; - -export const teePlugin: Plugin = { - name: pluginName, - description: "TEE plugin (browser stub; use a server proxy)", - async init(_config, _runtime: IAgentRuntime): Promise { - logger.warn( - `[plugin-${pluginName}] This plugin is not supported directly in browsers. Use a server proxy.` - ); - }, -}; - -export default teePlugin; diff --git a/typescript/package.json b/typescript/package.json deleted file mode 100644 index 33d0e14..0000000 --- a/typescript/package.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "name": "@elizaos/plugin-tee", - "version": "2.0.0-alpha.6", - "type": "module", - "main": "dist/node/index.js", - "module": "dist/node/index.js", - "types": "dist/index.d.ts", - "description": "Trusted Execution Environment (TEE) integration plugin for elizaOS - Multi-language support (TypeScript, Python, Rust)", - "repository": { - "type": "git", - "url": "git+https://github.com/elizaos-plugins/plugin-tee.git" - }, - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "node": { - "types": "./dist/node/index.d.ts", - "import": "./dist/node/index.js", - "default": "./dist/node/index.js" - }, - "bun": { - "types": "./dist/node/index.d.ts", - "default": "./dist/node/index.js" - }, - "default": "./dist/node/index.js" - } - }, - "files": [ - "dist", - "README.md", - "LICENSE" - ], - "sideEffects": false, - "dependencies": { - "@elizaos/core": "2.0.0-alpha.3", - "@phala/dstack-sdk": "0.1.11", - "@solana/web3.js": "1.98.4", - "viem": "2.29.4" - }, - "devDependencies": { - "@biomejs/biome": "^2.3.11", - "@types/node": "^25.0.3", - "typescript": "^5.9.3", - "vitest": "^4.0.0" - }, - "scripts": { - "build:typescript": "tsc -p tsconfig.build.json", - "dev": "bun --hot build.ts", - "clean": "rm -rf dist .turbo node_modules .turbo-tsconfig.json tsconfig.tsbuildinfo", - "format": "bunx @biomejs/biome format --write .", - "format:check": "bunx @biomejs/biome format .", - "typecheck": "echo \"Typecheck skipped for release\"", - "test": "bun run test:typescript", - "test:typescript": "vitest run src/__tests__/ --config vitest.config.ts --passWithNoTests", - "test:watch": "vitest", - "lint": "echo \"Lint skipped for release\"", - "lint:check": "bun run lint", - "build": "bun run build.ts", - "build:ts": "bun run build.ts" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "646c632924826e2b75c2304a75ee56959fe4a460", - "agentConfig": { - "pluginType": "elizaos:plugin:1.0.0", - "pluginParameters": { - "TEE_MODE": { - "type": "string", - "description": "Determines the Trusted Execution Environment operation mode (LOCAL, DOCKER, PRODUCTION) and is referenced in error handling to validate provided modes.", - "required": true, - "sensitive": false - }, - "TEE_VENDOR": { - "type": "string", - "description": "Specifies which Trusted Execution Environment vendor to initialize (defaults to PHALA).", - "required": false, - "default": "PHALA", - "sensitive": false - }, - "WALLET_SECRET_SALT": { - "type": "string", - "description": "Secret salt used to deterministically derive Solana and EVM keypairs inside the TEE.", - "required": true, - "default": "secret_salt", - "sensitive": true - } - } - }, - "milady": { - "platforms": [ - "node" - ], - "runtime": "node", - "platformDetails": { - "node": "Node.js build available via exports.node" - } - } -} diff --git a/typescript/src/__tests__/deriveKey.test.ts b/typescript/src/__tests__/deriveKey.test.ts deleted file mode 100644 index ffaaa6d..0000000 --- a/typescript/src/__tests__/deriveKey.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { PhalaDeriveKeyProvider } from "../providers/deriveKey"; - -const mockDeriveKey = vi.fn().mockResolvedValue({ - asUint8Array: () => new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), -}); -const mockTdxQuote = vi.fn().mockResolvedValue({ - quote: "mock-quote-data", - replayRtmrs: () => ["rtmr0", "rtmr1", "rtmr2", "rtmr3"], -}); -const mockConstructorCalls: Array = []; - -vi.mock("@phala/dstack-sdk", () => { - return { - TappdClient: class MockTappdClient { - deriveKey = mockDeriveKey; - tdxQuote = mockTdxQuote; - constructor(endpoint?: string) { - mockConstructorCalls.push(endpoint); - } - }, - }; -}); - -vi.mock("@solana/web3.js", () => ({ - Keypair: { - fromSeed: vi.fn().mockReturnValue({ - publicKey: { - toBase58: () => "mock-solana-public-key", - }, - }), - }, -})); - -vi.mock("viem/accounts", () => ({ - privateKeyToAccount: vi.fn().mockReturnValue({ - address: "0xmock-evm-address", - }), -})); - -vi.mock("viem", () => ({ - keccak256: vi.fn().mockReturnValue("0xmock-hash"), -})); - -describe("PhalaDeriveKeyProvider", () => { - beforeEach(() => { - vi.clearAllMocks(); - mockConstructorCalls.length = 0; - }); - - describe("constructor", () => { - it("should initialize with LOCAL mode", () => { - const _provider = new PhalaDeriveKeyProvider("LOCAL"); - expect(mockConstructorCalls).toContain("http://localhost:8090"); - }); - - it("should initialize with DOCKER mode", () => { - const _provider = new PhalaDeriveKeyProvider("DOCKER"); - expect(mockConstructorCalls).toContain("http://host.docker.internal:8090"); - }); - - it("should initialize with PRODUCTION mode", () => { - const _provider = new PhalaDeriveKeyProvider("PRODUCTION"); - expect(mockConstructorCalls).toContain(undefined); - }); - - it("should throw error for invalid mode", () => { - expect(() => new PhalaDeriveKeyProvider("INVALID_MODE")).toThrow("Invalid TEE_MODE"); - }); - }); - - describe("rawDeriveKey", () => { - let provider: PhalaDeriveKeyProvider; - - beforeEach(() => { - provider = new PhalaDeriveKeyProvider("LOCAL"); - }); - - it("should derive raw key successfully", async () => { - const path = "test-path"; - const subject = "test-subject"; - const result = await provider.rawDeriveKey(path, subject); - - expect(mockDeriveKey).toHaveBeenCalledWith(path, subject); - expect(result.key).toBeInstanceOf(Uint8Array); - }); - - it("should throw error when path is missing", async () => { - await expect(provider.rawDeriveKey("", "subject")).rejects.toThrow( - "Path and subject are required" - ); - }); - - it("should throw error when subject is missing", async () => { - await expect(provider.rawDeriveKey("path", "")).rejects.toThrow( - "Path and subject are required" - ); - }); - }); - - describe("deriveEd25519Keypair", () => { - let provider: PhalaDeriveKeyProvider; - - beforeEach(() => { - provider = new PhalaDeriveKeyProvider("LOCAL"); - }); - - it("should derive Ed25519 keypair successfully", async () => { - const path = "test-path"; - const subject = "solana"; - const result = await provider.deriveEd25519Keypair(path, subject, "test-agent-id"); - - expect(mockDeriveKey).toHaveBeenCalledWith(path, subject); - expect(result.keypair.publicKey.toBase58()).toBe("mock-solana-public-key"); - expect(result.attestation.quote).toBe("mock-quote-data"); - }); - }); - - describe("deriveEcdsaKeypair", () => { - let provider: PhalaDeriveKeyProvider; - - beforeEach(() => { - provider = new PhalaDeriveKeyProvider("LOCAL"); - }); - - it("should derive ECDSA keypair successfully", async () => { - const path = "test-path"; - const subject = "evm"; - const result = await provider.deriveEcdsaKeypair(path, subject, "test-agent-id"); - - expect(mockDeriveKey).toHaveBeenCalledWith(path, subject); - expect(result.keypair.address).toBe("0xmock-evm-address"); - expect(result.attestation.quote).toBe("mock-quote-data"); - }); - }); -}); diff --git a/typescript/src/__tests__/remoteAttestation.test.ts b/typescript/src/__tests__/remoteAttestation.test.ts deleted file mode 100644 index 7c5212c..0000000 --- a/typescript/src/__tests__/remoteAttestation.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { PhalaRemoteAttestationProvider } from "../providers/remoteAttestation"; - -const mockTdxQuote = vi.fn().mockResolvedValue({ - quote: "mock-quote-data", - replayRtmrs: () => ["rtmr0", "rtmr1", "rtmr2", "rtmr3"], -}); -const mockDeriveKey = vi.fn(); -const mockConstructorCalls: Array = []; - -vi.mock("@phala/dstack-sdk", () => { - return { - TappdClient: class MockTappdClient { - tdxQuote = mockTdxQuote; - deriveKey = mockDeriveKey; - constructor(endpoint?: string) { - mockConstructorCalls.push(endpoint); - } - }, - }; -}); - -describe("PhalaRemoteAttestationProvider", () => { - beforeEach(() => { - vi.clearAllMocks(); - mockConstructorCalls.length = 0; - mockTdxQuote.mockResolvedValue({ - quote: "mock-quote-data", - replayRtmrs: () => ["rtmr0", "rtmr1", "rtmr2", "rtmr3"], - }); - }); - - describe("constructor", () => { - it("should initialize with LOCAL mode", () => { - const _provider = new PhalaRemoteAttestationProvider("LOCAL"); - expect(mockConstructorCalls).toContain("http://localhost:8090"); - }); - - it("should initialize with DOCKER mode", () => { - const _provider = new PhalaRemoteAttestationProvider("DOCKER"); - expect(mockConstructorCalls).toContain("http://host.docker.internal:8090"); - }); - - it("should initialize with PRODUCTION mode", () => { - const _provider = new PhalaRemoteAttestationProvider("PRODUCTION"); - expect(mockConstructorCalls).toContain(undefined); - }); - - it("should throw error for invalid mode", () => { - expect(() => new PhalaRemoteAttestationProvider("INVALID_MODE")).toThrow("Invalid TEE_MODE"); - }); - }); - - describe("generateAttestation", () => { - let provider: PhalaRemoteAttestationProvider; - - beforeEach(() => { - provider = new PhalaRemoteAttestationProvider("LOCAL"); - }); - - it("should generate attestation successfully", async () => { - const reportData = "test-report-data"; - const quote = await provider.generateAttestation(reportData); - - expect(quote).toEqual({ - quote: "mock-quote-data", - timestamp: expect.any(Number), - }); - }); - - it("should handle errors during attestation generation", async () => { - const mockError = new Error("TDX Quote generation failed"); - mockTdxQuote.mockRejectedValueOnce(mockError); - - const errorProvider = new PhalaRemoteAttestationProvider("LOCAL"); - await expect(errorProvider.generateAttestation("test-data")).rejects.toThrow( - "Failed to generate TDX Quote" - ); - }); - - it("should pass hash algorithm to tdxQuote when provided", async () => { - const reportData = "test-report-data"; - const hashAlgorithm = "raw"; - await provider.generateAttestation(reportData, hashAlgorithm); - - expect(mockTdxQuote).toHaveBeenCalledWith(reportData, hashAlgorithm); - }); - }); -}); diff --git a/typescript/src/__tests__/utils.test.ts b/typescript/src/__tests__/utils.test.ts deleted file mode 100644 index 7159c91..0000000 --- a/typescript/src/__tests__/utils.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - calculateSHA256, - getTeeEndpoint, - hexToUint8Array, - sha256Bytes, - uint8ArrayToHex, -} from "../utils"; - -describe("hexToUint8Array", () => { - it("should convert valid hex string to Uint8Array", () => { - const result = hexToUint8Array("0102030405"); - expect(result).toEqual(new Uint8Array([1, 2, 3, 4, 5])); - }); - - it("should handle hex string with 0x prefix", () => { - const result = hexToUint8Array("0x0102030405"); - expect(result).toEqual(new Uint8Array([1, 2, 3, 4, 5])); - }); - - it("should throw error for empty hex string", () => { - expect(() => hexToUint8Array("")).toThrow("Invalid hex string"); - }); - - it("should throw error for hex string with 0x only", () => { - expect(() => hexToUint8Array("0x")).toThrow("Invalid hex string"); - }); - - it("should throw error for odd-length hex string", () => { - expect(() => hexToUint8Array("0x123")).toThrow("Invalid hex string"); - }); - - it("should throw error for invalid hex characters", () => { - expect(() => hexToUint8Array("0xGG")).toThrow("Invalid hex string"); - }); -}); - -describe("uint8ArrayToHex", () => { - it("should convert Uint8Array to hex string", () => { - const result = uint8ArrayToHex(new Uint8Array([1, 2, 3, 4, 5])); - expect(result).toBe("0102030405"); - }); - - it("should handle empty array", () => { - const result = uint8ArrayToHex(new Uint8Array([])); - expect(result).toBe(""); - }); - - it("should pad single digit bytes", () => { - const result = uint8ArrayToHex(new Uint8Array([0, 1, 15, 16])); - expect(result).toBe("00010f10"); - }); -}); - -describe("calculateSHA256", () => { - it("should calculate SHA256 hash of string", () => { - const result = calculateSHA256("hello"); - expect(result).toBeInstanceOf(Buffer); - expect(result.length).toBe(32); - }); - - it("should produce consistent results", () => { - const result1 = calculateSHA256("test"); - const result2 = calculateSHA256("test"); - expect(result1.equals(result2)).toBe(true); - }); - - it("should produce different results for different inputs", () => { - const result1 = calculateSHA256("hello"); - const result2 = calculateSHA256("world"); - expect(result1.equals(result2)).toBe(false); - }); -}); - -describe("sha256Bytes", () => { - it("should calculate SHA256 hash of Uint8Array", () => { - const result = sha256Bytes(new Uint8Array([1, 2, 3, 4, 5])); - expect(result).toBeInstanceOf(Uint8Array); - expect(result.length).toBe(32); - }); -}); - -describe("getTeeEndpoint", () => { - it("should return localhost for LOCAL mode", () => { - expect(getTeeEndpoint("LOCAL")).toBe("http://localhost:8090"); - }); - - it("should return docker internal for DOCKER mode", () => { - expect(getTeeEndpoint("DOCKER")).toBe("http://host.docker.internal:8090"); - }); - - it("should return undefined for PRODUCTION mode", () => { - expect(getTeeEndpoint("PRODUCTION")).toBeUndefined(); - }); - - it("should handle case insensitivity", () => { - expect(getTeeEndpoint("local")).toBe("http://localhost:8090"); - expect(getTeeEndpoint("docker")).toBe("http://host.docker.internal:8090"); - expect(getTeeEndpoint("production")).toBeUndefined(); - }); - - it("should throw error for invalid mode", () => { - expect(() => getTeeEndpoint("INVALID")).toThrow("Invalid TEE_MODE"); - }); -}); diff --git a/typescript/src/actions/index.ts b/typescript/src/actions/index.ts deleted file mode 100644 index 22e5c88..0000000 --- a/typescript/src/actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { remoteAttestationAction } from "./remoteAttestation"; diff --git a/typescript/src/actions/remoteAttestation.ts b/typescript/src/actions/remoteAttestation.ts deleted file mode 100644 index 95b8002..0000000 --- a/typescript/src/actions/remoteAttestation.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { - Action, - ActionResult, - HandlerCallback, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { logger } from "@elizaos/core"; -import { PhalaRemoteAttestationProvider } from "../providers/remoteAttestation"; -import type { RemoteAttestationMessage } from "../types"; -import { hexToUint8Array, uploadAttestationQuote } from "../utils"; - -export const remoteAttestationAction: Action = { - name: "REMOTE_ATTESTATION", - - similes: [ - "REMOTE_ATTESTATION", - "TEE_REMOTE_ATTESTATION", - "TEE_ATTESTATION", - "TEE_QUOTE", - "ATTESTATION", - "TEE_ATTESTATION_QUOTE", - "PROVE_TEE", - "VERIFY_TEE", - ], - - description: - "Generate a remote attestation to prove that the agent is running in a TEE (Trusted Execution Environment)", - - validate: async (runtime: any, message: any, state?: any, options?: any): Promise => { - const __avTextRaw = typeof message?.content?.text === 'string' ? message.content.text : ''; - const __avText = __avTextRaw.toLowerCase(); - const __avKeywords = ['remote', 'attestation']; - const __avKeywordOk = - __avKeywords.length > 0 && - __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw)); - const __avRegex = new RegExp('\\b(?:remote|attestation)\\b', 'i'); - const __avRegexOk = __avRegex.test(__avText); - const __avSource = String(message?.content?.source ?? message?.source ?? ''); - const __avExpectedSource = ''; - const __avSourceOk = __avExpectedSource - ? __avSource === __avExpectedSource - : Boolean(__avSource || state || runtime?.agentId || runtime?.getService); - const __avOptions = options && typeof options === 'object' ? options : {}; - const __avInputOk = - __avText.trim().length > 0 || - Object.keys(__avOptions as Record).length > 0 || - Boolean(message?.content && typeof message.content === 'object'); - - if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) { - return false; - } - - const __avLegacyValidate = async (runtime: IAgentRuntime): Promise => { - const teeMode = runtime.getSetting("TEE_MODE"); - if (!teeMode) { - logger.warn("REMOTE_ATTESTATION: TEE_MODE not configured"); - return false; - } - return true; - }; - try { - return Boolean(await (__avLegacyValidate as any)(runtime, message, state, options)); - } catch { - return false; - } - }, - - handler: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State, - _options?: Record, - callback?: HandlerCallback - ): Promise => { - try { - const teeMode = runtime.getSetting("TEE_MODE"); - if (!teeMode) { - logger.error("TEE_MODE is not configured"); - callback?.({ - text: "TEE_MODE is not configured. Cannot generate attestation.", - actions: ["NONE"], - }); - return { success: false, error: "TEE_MODE is not configured" }; - } - - const attestationMessage: RemoteAttestationMessage = { - agentId: runtime.agentId, - timestamp: Date.now(), - message: { - entityId: message.entityId, - roomId: message.roomId, - content: message.content.text ?? "", - }, - }; - - logger.debug(`Generating attestation for: ${JSON.stringify(attestationMessage)}`); - - const provider = new PhalaRemoteAttestationProvider(String(teeMode)); - const attestation = await provider.generateAttestation(JSON.stringify(attestationMessage)); - - const attestationData = hexToUint8Array(attestation.quote); - const uploadResult = await uploadAttestationQuote(attestationData); - - const proofUrl = `https://proof.t16z.com/reports/${uploadResult.checksum}`; - - logger.info(`Attestation uploaded: ${proofUrl}`); - - callback?.({ - text: `Remote attestation quote: ${proofUrl}`, - actions: ["NONE"], - }); - - return { success: true, text: `Attestation generated: ${proofUrl}` }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error(`Failed to generate remote attestation: ${errorMessage}`); - - callback?.({ - text: `Failed to generate attestation: ${errorMessage}`, - actions: ["NONE"], - }); - - return { success: false, error: errorMessage }; - } - }, - - examples: [ - [ - { - name: "{{name1}}", - content: { - text: "If you are running in a TEE, generate a remote attestation", - }, - }, - { - name: "{{agentName}}", - content: { - text: "Of course, one second...", - actions: ["REMOTE_ATTESTATION"], - }, - }, - ], - [ - { - name: "{{name1}}", - content: { - text: "Can you prove you're running in a trusted execution environment?", - }, - }, - { - name: "{{agentName}}", - content: { - text: "Absolutely! Let me generate a TEE attestation quote for you.", - actions: ["REMOTE_ATTESTATION"], - }, - }, - ], - [ - { - name: "{{name1}}", - content: { - text: "I need verification that this conversation is happening in a secure enclave", - }, - }, - { - name: "{{agentName}}", - content: { - text: "I'll generate a remote attestation to prove I'm running in a TEE.", - actions: ["REMOTE_ATTESTATION"], - }, - }, - ], - ], -}; diff --git a/typescript/src/index.ts b/typescript/src/index.ts deleted file mode 100644 index 94c7043..0000000 --- a/typescript/src/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type IAgentRuntime, logger, type Plugin } from "@elizaos/core"; -import { TEEService } from "./services/tee"; -import { getVendor, TeeVendorNames } from "./vendors"; - -export { remoteAttestationAction } from "./actions"; -export { - DeriveKeyProvider, - PhalaDeriveKeyProvider, - PhalaRemoteAttestationProvider, - phalaDeriveKeyProvider, - phalaRemoteAttestationProvider, - RemoteAttestationProvider, -} from "./providers"; -export { TEEService } from "./services"; -export * from "./types"; -export { - calculateSHA256, - getTeeEndpoint, - hexToUint8Array, - sha256Bytes, - uint8ArrayToHex, - uploadAttestationQuote, -} from "./utils"; -export { - getVendor, - PhalaVendor, - type TeeVendorInterface, - TeeVendorNames, -} from "./vendors"; - -const defaultVendor = getVendor(TeeVendorNames.PHALA); - -export const teePlugin: Plugin = { - name: "tee", - description: "TEE integration plugin for secure key management and remote attestation", - - config: { - TEE_MODE: process.env.TEE_MODE ?? null, - TEE_VENDOR: process.env.TEE_VENDOR ?? null, - WALLET_SECRET_SALT: process.env.WALLET_SECRET_SALT ?? null, - }, - - async init(config: Record, runtime: IAgentRuntime): Promise { - const vendorName = - config.TEE_VENDOR ?? runtime.getSetting("TEE_VENDOR") ?? TeeVendorNames.PHALA; - const teeModeRaw = config.TEE_MODE ?? runtime.getSetting("TEE_MODE") ?? "LOCAL"; - const teeMode = typeof teeModeRaw === "string" ? teeModeRaw : String(teeModeRaw); - - logger.info(`Initializing TEE plugin with vendor: ${vendorName}, mode: ${teeMode}`); - - if (!["LOCAL", "DOCKER", "PRODUCTION"].includes(teeMode.toUpperCase())) { - throw new Error(`Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION`); - } - - logger.info(`TEE plugin initialized successfully`); - }, - - actions: defaultVendor.getActions(), - providers: defaultVendor.getProviders(), - services: [TEEService], - evaluators: [], -}; - -export default teePlugin; diff --git a/typescript/src/providers/base.ts b/typescript/src/providers/base.ts deleted file mode 100644 index b41a59a..0000000 --- a/typescript/src/providers/base.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { DeriveKeyResult, RemoteAttestationQuote, TdxQuoteHashAlgorithm } from "../types"; - -export abstract class DeriveKeyProvider { - abstract rawDeriveKey(path: string, subject: string): Promise; -} - -export abstract class RemoteAttestationProvider { - abstract generateAttestation( - reportData: string, - hashAlgorithm?: TdxQuoteHashAlgorithm - ): Promise; -} diff --git a/typescript/src/providers/deriveKey.ts b/typescript/src/providers/deriveKey.ts deleted file mode 100644 index b959eda..0000000 --- a/typescript/src/providers/deriveKey.ts +++ /dev/null @@ -1,197 +0,0 @@ -import crypto from "node:crypto"; -import { type IAgentRuntime, logger, type Memory, type Provider } from "@elizaos/core"; -import { type DeriveKeyResponse, TappdClient } from "@phala/dstack-sdk"; -import { Keypair } from "@solana/web3.js"; -import { keccak256 } from "viem"; -import { type PrivateKeyAccount, privateKeyToAccount } from "viem/accounts"; -import type { - DeriveKeyAttestationData, - DeriveKeyResult, - RemoteAttestationQuote, - TeeProviderResult, -} from "../types"; -import { getTeeEndpoint } from "../utils"; -import { DeriveKeyProvider } from "./base"; -import { PhalaRemoteAttestationProvider } from "./remoteAttestation"; -export class PhalaDeriveKeyProvider extends DeriveKeyProvider { - private readonly client: TappdClient; - private readonly raProvider: PhalaRemoteAttestationProvider; - - constructor(teeMode: string) { - super(); - const endpoint = getTeeEndpoint(teeMode); - - logger.info( - endpoint - ? `TEE: Connecting to key derivation service at ${endpoint}` - : "TEE: Running key derivation in production mode" - ); - - this.client = endpoint ? new TappdClient(endpoint) : new TappdClient(); - this.raProvider = new PhalaRemoteAttestationProvider(teeMode); - } - - private async generateDeriveKeyAttestation( - agentId: string, - publicKey: string, - subject?: string - ): Promise { - const deriveKeyData: DeriveKeyAttestationData = { - agentId, - publicKey, - subject, - }; - return this.raProvider.generateAttestation(JSON.stringify(deriveKeyData)); - } - - async rawDeriveKey(path: string, subject: string): Promise { - if (!path || !subject) { - throw new Error("Path and subject are required for key derivation"); - } - - try { - const response: DeriveKeyResponse = await this.client.deriveKey(path, subject); - return { - key: response.asUint8Array(), - certificateChain: [], - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`Error deriving raw key: ${message}`); - throw error; - } - } - - async rawDeriveKeyResponse(path: string, subject: string): Promise { - if (!path || !subject) { - throw new Error("Path and subject are required for key derivation"); - } - return this.client.deriveKey(path, subject); - } - - async deriveEd25519Keypair( - path: string, - subject: string, - agentId: string - ): Promise<{ keypair: Keypair; attestation: RemoteAttestationQuote }> { - if (!path || !subject) { - throw new Error("Path and subject are required for key derivation"); - } - - try { - const derivedKey = await this.client.deriveKey(path, subject); - const uint8ArrayDerivedKey = derivedKey.asUint8Array(); - - const hash = crypto.createHash("sha256"); - hash.update(uint8ArrayDerivedKey); - const seed = new Uint8Array(hash.digest()); - - const keypair = Keypair.fromSeed(seed.slice(0, 32)); - - const attestation = await this.generateDeriveKeyAttestation( - agentId, - keypair.publicKey.toBase58(), - subject - ); - - return { keypair, attestation }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`Error deriving Ed25519 key: ${message}`); - throw error; - } - } - - async deriveEcdsaKeypair( - path: string, - subject: string, - agentId: string - ): Promise<{ - keypair: PrivateKeyAccount; - attestation: RemoteAttestationQuote; - }> { - if (!path || !subject) { - throw new Error("Path and subject are required for key derivation"); - } - - try { - const derivedKey: DeriveKeyResponse = await this.client.deriveKey(path, subject); - const hex = keccak256(derivedKey.asUint8Array()); - const keypair: PrivateKeyAccount = privateKeyToAccount(hex); - - const attestation = await this.generateDeriveKeyAttestation( - agentId, - keypair.address, - subject - ); - - return { keypair, attestation }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`Error deriving ECDSA key: ${message}`); - throw error; - } - } -} - -export const phalaDeriveKeyProvider: Provider = { - name: "phala-derive-key", - - dynamic: true, -get: async (runtime: IAgentRuntime, _message?: Memory): Promise => { -const teeModeRaw = runtime.getSetting("TEE_MODE"); - if (!teeModeRaw) { - return { - data: null, - values: {}, - text: "TEE_MODE is not configured", - }; - } - const teeMode = typeof teeModeRaw === "string" ? teeModeRaw : String(teeModeRaw); - - const secretSaltRaw = runtime.getSetting("WALLET_SECRET_SALT"); - if (!secretSaltRaw) { - logger.error("WALLET_SECRET_SALT is not configured"); - return { - data: null, - values: {}, - text: "WALLET_SECRET_SALT is not configured in settings", - }; - } - const secretSalt = typeof secretSaltRaw === "string" ? secretSaltRaw : String(secretSaltRaw); - - const provider = new PhalaDeriveKeyProvider(teeMode); - const agentId = runtime.agentId; - - try { - const solanaKeypair = await provider.deriveEd25519Keypair(secretSalt, "solana", agentId); - const evmKeypair = await provider.deriveEcdsaKeypair(secretSalt, "evm", agentId); - - const walletData = { - solana: solanaKeypair.keypair.publicKey.toBase58(), - evm: evmKeypair.keypair.address, - }; - - const values = { - solana_public_key: solanaKeypair.keypair.publicKey.toBase58(), - evm_address: evmKeypair.keypair.address, - }; - - const text = `Solana Public Key: ${values.solana_public_key}\nEVM Address: ${values.evm_address}`; - - return { - data: walletData, - values, - text, - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`Error in derive key provider: ${message}`); - return { - data: null, - values: {}, - text: `Failed to derive keys: ${message}`, - }; - } - }, -}; diff --git a/typescript/src/providers/index.ts b/typescript/src/providers/index.ts deleted file mode 100644 index 465537f..0000000 --- a/typescript/src/providers/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { DeriveKeyProvider, RemoteAttestationProvider } from "./base"; -export { PhalaDeriveKeyProvider, phalaDeriveKeyProvider } from "./deriveKey"; -export { - PhalaRemoteAttestationProvider, - phalaRemoteAttestationProvider, -} from "./remoteAttestation"; diff --git a/typescript/src/providers/remoteAttestation.ts b/typescript/src/providers/remoteAttestation.ts deleted file mode 100644 index 6d4f0af..0000000 --- a/typescript/src/providers/remoteAttestation.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { type IAgentRuntime, logger, type Memory, type Provider } from "@elizaos/core"; -import { TappdClient, type TdxQuoteHashAlgorithms, type TdxQuoteResponse } from "@phala/dstack-sdk"; -import type { - RemoteAttestationMessage, - RemoteAttestationQuote, - TdxQuoteHashAlgorithm, - TeeProviderResult, -} from "../types"; -import { getTeeEndpoint } from "../utils"; -import { RemoteAttestationProvider } from "./base"; -export class PhalaRemoteAttestationProvider extends RemoteAttestationProvider { - private readonly client: TappdClient; - - constructor(teeMode: string) { - super(); - const endpoint = getTeeEndpoint(teeMode); - - logger.info( - endpoint - ? `TEE: Connecting to simulator at ${endpoint}` - : "TEE: Running in production mode without simulator" - ); - - this.client = endpoint ? new TappdClient(endpoint) : new TappdClient(); - } - - async generateAttestation( - reportData: string, - hashAlgorithm?: TdxQuoteHashAlgorithm - ): Promise { - try { - const tdxQuote: TdxQuoteResponse = await this.client.tdxQuote( - reportData, - hashAlgorithm as TdxQuoteHashAlgorithms | undefined - ); - - return { - quote: tdxQuote.quote, - timestamp: Date.now(), - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`Error generating remote attestation: ${message}`); - throw new Error(`Failed to generate TDX Quote: ${message}`); - } - } -} - -export const phalaRemoteAttestationProvider: Provider = { - name: "phala-remote-attestation", - - dynamic: true, -get: async (runtime: IAgentRuntime, message: Memory): Promise => { -const teeModeRaw = runtime.getSetting("TEE_MODE"); - if (!teeModeRaw) { - return { - data: null, - values: {}, - text: "TEE_MODE is not configured", - }; - } - const teeMode = typeof teeModeRaw === "string" ? teeModeRaw : String(teeModeRaw); - - const provider = new PhalaRemoteAttestationProvider(teeMode); - const agentId = runtime.agentId; - - try { - const attestationMessage: RemoteAttestationMessage = { - agentId, - timestamp: Date.now(), - message: { - entityId: message.entityId, - roomId: message.roomId, - content: message.content.text ?? "", - }, - }; - - const attestation = await provider.generateAttestation(JSON.stringify(attestationMessage)); - - return { - data: { - quote: attestation.quote, - timestamp: attestation.timestamp.toString(), - }, - values: { - quote: attestation.quote, - timestamp: attestation.timestamp.toString(), - }, - text: `Remote attestation: ${attestation.quote.substring(0, 64)}...`, - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`Error in remote attestation provider: ${message}`); - throw new Error(`Failed to generate TDX Quote: ${message}`); - } - }, -}; diff --git a/typescript/src/services/index.ts b/typescript/src/services/index.ts deleted file mode 100644 index fd08022..0000000 --- a/typescript/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TEEService } from "./tee"; diff --git a/typescript/src/services/tee.ts b/typescript/src/services/tee.ts deleted file mode 100644 index eb11512..0000000 --- a/typescript/src/services/tee.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - type IAgentRuntime, - logger, - type Metadata, - Service, - ServiceType, - type UUID, -} from "@elizaos/core"; -import type { DeriveKeyResponse } from "@phala/dstack-sdk"; -import type { Keypair } from "@solana/web3.js"; -import type { PrivateKeyAccount } from "viem"; -import { PhalaDeriveKeyProvider } from "../providers/deriveKey"; -import type { RemoteAttestationQuote, TeeServiceConfig } from "../types"; -import { TeeMode, TeeVendor } from "../types"; - -export class TEEService extends Service { - private provider: PhalaDeriveKeyProvider; - static serviceType = ServiceType.TEE; - public capabilityDescription = "Trusted Execution Environment for secure key management"; - public declare config?: Metadata; - - constructor(runtime: IAgentRuntime, config?: Partial) { - super(runtime); - - const teeModeRaw = config?.mode ?? runtime.getSetting("TEE_MODE") ?? TeeMode.LOCAL; - const teeMode = typeof teeModeRaw === "string" ? (teeModeRaw as TeeMode) : TeeMode.LOCAL; - const vendor = config?.vendor ?? TeeVendor.PHALA; - const secretSaltRaw = config?.secretSalt ?? runtime.getSetting("WALLET_SECRET_SALT"); - const secretSalt = typeof secretSaltRaw === "string" ? secretSaltRaw : undefined; - - // Set config as Metadata-compatible object - this.config = { - mode: teeMode, - vendor, - ...(secretSalt ? { secretSalt } : {}), - } as Metadata; - - this.provider = new PhalaDeriveKeyProvider(teeMode); - } - - static async start(runtime: IAgentRuntime): Promise { - const teeModeRaw = runtime.getSetting("TEE_MODE") ?? TeeMode.LOCAL; - const teeMode = typeof teeModeRaw === "string" ? (teeModeRaw as TeeMode) : TeeMode.LOCAL; - logger.info(`Starting TEE service with mode: ${teeMode}`); - const service = new TEEService(runtime, { mode: teeMode }); - return service; - } - - async stop(): Promise { - logger.info("Stopping TEE service"); - } - - async deriveEcdsaKeypair( - path: string, - subject: string, - agentId: UUID - ): Promise<{ - keypair: PrivateKeyAccount; - attestation: RemoteAttestationQuote; - }> { - return this.provider.deriveEcdsaKeypair(path, subject, agentId); - } - - async deriveEd25519Keypair( - path: string, - subject: string, - agentId: UUID - ): Promise<{ - keypair: Keypair; - attestation: RemoteAttestationQuote; - }> { - return this.provider.deriveEd25519Keypair(path, subject, agentId); - } - - async rawDeriveKey(path: string, subject: string): Promise { - return this.provider.rawDeriveKeyResponse(path, subject); - } -} diff --git a/typescript/src/types/index.ts b/typescript/src/types/index.ts deleted file mode 100644 index 82bf980..0000000 --- a/typescript/src/types/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -export enum TeeMode { - LOCAL = "LOCAL", - DOCKER = "DOCKER", - PRODUCTION = "PRODUCTION", -} - -export enum TeeVendor { - PHALA = "phala", -} - -export enum TeeType { - SGX_GRAMINE = "sgx_gramine", - TDX_DSTACK = "tdx_dstack", -} - -export interface RemoteAttestationQuote { - readonly quote: string; - readonly timestamp: number; -} - -export interface DeriveKeyAttestationData { - readonly agentId: string; - readonly publicKey: string; - readonly subject?: string; -} - -export interface RemoteAttestationMessage { - readonly agentId: string; - readonly timestamp: number; - readonly message: { - readonly entityId: string; - readonly roomId: string; - readonly content: string; - }; -} - -export interface DeriveKeyResult { - readonly key: Uint8Array; - readonly certificateChain: string[]; -} - -export interface Ed25519KeypairResult { - readonly publicKey: string; - readonly secretKey: Uint8Array; - readonly attestation: RemoteAttestationQuote; -} - -export interface EcdsaKeypairResult { - readonly address: string; - readonly privateKey: Uint8Array; - readonly attestation: RemoteAttestationQuote; -} - -export interface TeeServiceConfig { - readonly mode: TeeMode; - readonly vendor: TeeVendor; - readonly secretSalt?: string; -} - -export interface TeeProviderResult { - readonly data: Record | null; - readonly values: Record; - readonly text: string; -} - -export type TdxQuoteHashAlgorithm = "sha256" | "sha384" | "sha512" | "raw"; - -export function parseTeeMode(mode: string): TeeMode { - switch (mode.toUpperCase()) { - case "LOCAL": - return TeeMode.LOCAL; - case "DOCKER": - return TeeMode.DOCKER; - case "PRODUCTION": - return TeeMode.PRODUCTION; - default: - throw new Error(`Invalid TEE_MODE: ${mode}. Must be one of: LOCAL, DOCKER, PRODUCTION`); - } -} - -export function parseTeeVendor(vendor: string): TeeVendor { - switch (vendor.toLowerCase()) { - case "phala": - return TeeVendor.PHALA; - default: - throw new Error(`Invalid TEE_VENDOR: ${vendor}. Must be one of: phala`); - } -} diff --git a/typescript/src/utils/index.ts b/typescript/src/utils/index.ts deleted file mode 100644 index f4d8564..0000000 --- a/typescript/src/utils/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { createHash } from "node:crypto"; - -export function hexToUint8Array(hex: string): Uint8Array { - const hexString = hex.trim().replace(/^0x/, ""); - if (!hexString) { - throw new Error("Invalid hex string: empty after stripping prefix"); - } - if (hexString.length % 2 !== 0) { - throw new Error("Invalid hex string: odd number of characters"); - } - - const array = new Uint8Array(hexString.length / 2); - for (let i = 0; i < hexString.length; i += 2) { - const byte = Number.parseInt(hexString.slice(i, i + 2), 16); - if (Number.isNaN(byte)) { - throw new Error(`Invalid hex string: invalid byte at position ${i}`); - } - array[i / 2] = byte; - } - return array; -} - -export function uint8ArrayToHex(bytes: Uint8Array): string { - return Array.from(bytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); -} - -export function calculateSHA256(input: string): Buffer { - const hash = createHash("sha256"); - hash.update(input); - return hash.digest(); -} - -export function sha256Bytes(input: Uint8Array): Uint8Array { - const hash = createHash("sha256"); - hash.update(input); - return new Uint8Array(hash.digest()); -} - -export function getTeeEndpoint(mode: string): string | undefined { - switch (mode.toUpperCase()) { - case "LOCAL": - return "http://localhost:8090"; - case "DOCKER": - return "http://host.docker.internal:8090"; - case "PRODUCTION": - return undefined; - default: - throw new Error(`Invalid TEE_MODE: ${mode}. Must be one of: LOCAL, DOCKER, PRODUCTION`); - } -} - -export async function uploadAttestationQuote(data: Uint8Array): Promise<{ checksum: string }> { - const blob = new Blob([data as BlobPart], { - type: "application/octet-stream", - }); - const formData = new FormData(); - formData.append("file", blob, "quote.bin"); - - const response = await fetch("https://proof.t16z.com/api/upload", { - method: "POST", - body: formData as BodyInit, - }); - - if (!response.ok) { - throw new Error(`Failed to upload attestation quote: ${response.statusText}`); - } - - return response.json() as Promise<{ checksum: string }>; -} diff --git a/typescript/src/vendors/index.ts b/typescript/src/vendors/index.ts deleted file mode 100644 index e2c8b83..0000000 --- a/typescript/src/vendors/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PhalaVendor } from "./phala"; -import { type TeeVendorInterface, type TeeVendorName, TeeVendorNames } from "./types"; - -const vendors: Record = { - [TeeVendorNames.PHALA]: new PhalaVendor(), -}; - -export function getVendor(type: TeeVendorName): TeeVendorInterface { - const vendor = vendors[type]; - if (!vendor) { - throw new Error(`Unsupported TEE vendor: ${type}`); - } - return vendor; -} - -export { PhalaVendor } from "./phala"; -export { type TeeVendorInterface, type TeeVendorName, TeeVendorNames } from "./types"; diff --git a/typescript/src/vendors/phala.ts b/typescript/src/vendors/phala.ts deleted file mode 100644 index 60a9a19..0000000 --- a/typescript/src/vendors/phala.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Action, Provider } from "@elizaos/core"; -import { remoteAttestationAction } from "../actions/remoteAttestation"; -import { phalaDeriveKeyProvider, phalaRemoteAttestationProvider } from "../providers"; -import { type TeeVendorInterface, TeeVendorNames } from "./types"; - -export class PhalaVendor implements TeeVendorInterface { - readonly type = TeeVendorNames.PHALA; - - getActions(): Action[] { - return [remoteAttestationAction]; - } - - getProviders(): Provider[] { - return [phalaDeriveKeyProvider, phalaRemoteAttestationProvider]; - } - - getName(): string { - return "phala-tee-plugin"; - } - - getDescription(): string { - return "Phala Network TEE for secure agent execution"; - } -} diff --git a/typescript/src/vendors/types.ts b/typescript/src/vendors/types.ts deleted file mode 100644 index a1b30cc..0000000 --- a/typescript/src/vendors/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Action, Provider } from "@elizaos/core"; - -export const TeeVendorNames = { - PHALA: "phala", -} as const; - -export type TeeVendorName = (typeof TeeVendorNames)[keyof typeof TeeVendorNames]; - -export interface TeeVendorInterface { - readonly type: TeeVendorName; - getActions(): Action[]; - getProviders(): Provider[]; - getName(): string; - getDescription(): string; -} diff --git a/typescript/tsconfig.build.json b/typescript/tsconfig.build.json deleted file mode 100644 index a67cc85..0000000 --- a/typescript/tsconfig.build.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./dist/node", - "declaration": true, - "declarationMap": true, - "noEmit": false - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/__tests__"] -} diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json deleted file mode 100644 index 2742a35..0000000 --- a/typescript/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "lib": ["ES2022", "DOM"], - "strict": false, - "noEmit": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": false, - "noFallthroughCasesInSwitch": false, - "noImplicitAny": false, - "noUncheckedIndexedAccess": false, - "types": ["node"] - }, - "include": ["./**/*.ts"], - "exclude": ["node_modules", "dist", "__tests__", "build.ts"] -} diff --git a/typescript/vitest.config.ts b/typescript/vitest.config.ts deleted file mode 100644 index 9aee8d2..0000000 --- a/typescript/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - include: ["src/__tests__/**/*.test.ts"], - exclude: ["dist/**", "**/node_modules/**"], - }, -});