Skip to content

Commit 9095a78

Browse files
Liao1junkwarrior87chzyerwtfsayo
authored
feat: Add plugin-dcap (#2638)
* Dcap -- fix lock conflict (#4) * move the position of action field inside examples * fix pnpm-lock * add initilization --------- Co-authored-by: junkwarrior87 <115852752+junkwarrior87@users.noreply.github.com> Co-authored-by: ChenYe <git@0xdf.com> Co-authored-by: Sayo <hi@sayo.wtf>
1 parent 52660d5 commit 9095a78

15 files changed

+451
-2
lines changed

.env.example

+5-1
Original file line numberDiff line numberDiff line change
@@ -884,4 +884,8 @@ ANKR_TIMEOUT=5000
884884
ANKR_GRANULAR_LOG=true
885885
ANKR_LOG_LEVEL=debug
886886
ANKR_RUNTIME_CHECK_MODE=false
887-
ANKR_SPASH=true
887+
ANKR_SPASH=true
888+
889+
# DCAP Plugin Configuration
890+
DCAP_EVM_PRIVATE_KEY=
891+
DCAP_MODE= # Options: OFF, PLUGIN-SGX, PLUGIN-TEE, MOCK

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
"@elizaos/plugin-ethstorage": "workspace:*",
135135
"@elizaos/plugin-mina": "workspace:*",
136136
"@elizaos/plugin-email-automation": "workspace:*",
137+
"@elizaos/plugin-dcap": "workspace:*",
137138
"@elizaos/plugin-form": "workspace:*",
138139
"@elizaos/plugin-ankr": "workspace:*",
139140
"readline": "1.3.0",

agent/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { agentKitPlugin } from "@elizaos/plugin-agentkit"
2121
import { PrimusAdapter } from "@elizaos/plugin-primus"
2222
import { lightningPlugin } from "@elizaos/plugin-lightning"
2323
import { elizaCodeinPlugin, onchainJson } from "@elizaos/plugin-iq6900"
24-
24+
import { dcapPlugin } from "@elizaos/plugin-dcap"
2525
import {
2626
AgentRuntime,
2727
CacheManager,
@@ -853,6 +853,7 @@ export async function createAgent(character: Character, db: IDatabaseAdapter, ca
853853
getSecret(character, "MINA_PRIVATE_KEY") ? minaPlugin : null,
854854
getSecret(character, "FORM_PRIVATE_KEY") ? formPlugin : null,
855855
getSecret(character, "ANKR_WALLET") ? ankrPlugin : null,
856+
getSecret(character, "DCAP_EVM_PRIVATE_KEY") && getSecret(character, "DCAP_MODE") ? dcapPlugin : null,
856857
].filter(Boolean),
857858
providers: [],
858859
managers: [],

packages/plugin-dcap/README.md

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# @elizaos/plugin-dcap
2+
3+
A plugin for verifying DCAP attestation on-chain built based on the [automata-dcap-attestation](https://github.com/automata-network/automata-dcap-attestation).
4+
5+
## Features
6+
7+
This plugin provides the following features:
8+
- Generate DCAP attestation on TDX using the `remoteAttestationProvider` provided by the [plugin-tee](https://github.com/elizaOS/eliza/tree/develop/packages/plugin-tee).
9+
- Generate DCAP attestation on SGX using the `sgxAttestationProvider` provided by the [plugin-sgx](https://github.com/elizaOS/eliza/tree/develop/packages/plugin-sgx).
10+
- Submit and verify DCAP attestation on-chain.
11+
12+
## Future Features (coming soon)
13+
- Support to verify DCAP attestation on more EVM networks.
14+
- Support to verify DCAP attestation on Solana.
15+
- Support to verify DCAP attestation using ZKVM and verify the zk proof on-chain.
16+
- Support to topup the wallet before submitting the DCAP attestation on testnets.
17+
18+
## Installation
19+
20+
```bash
21+
pnpm install @elizaos/plugin-dcap
22+
```
23+
24+
## Configuration
25+
1. Set up your environment variables:
26+
```env
27+
EVM_PRIVATE_KEY=your-private-key-here
28+
DCAP_MODE=PLUGIN-SGX|PLUGIN-TEE|MOCK
29+
```
30+
The EVM_PRIVATE_KEY used to submit the DCAP attestation on evm networks, please make sure it has enough balance to pay for the transaction fee.
31+
32+
The DCAP_MODE is used to specify the mode of generating DCAP attestation, it can be:
33+
- PLUGIN-SGX: Use the `sgxAttestationProvider` in `plugin-sgx` to generate the DCAP attestation.
34+
- PLUGIN-TEE: Use the `remoteAttestationProvider` in `plugin-tee` to generate the DCAP attestation.
35+
- MOCK: Use a predefined attestation, this option is only for testing purposes.
36+
37+
Check the docs of `plugin-sgx` and `plugin-tee` for how to run your agent in TEE before using the SGX or TDX mode.
38+
39+
2. Register the plugin in your Eliza configuration:
40+
```typescript
41+
import { dcapPlugin } from "@elizaos/plugin-dcap";
42+
43+
// In your Eliza configuration
44+
plugins: [
45+
dcapPlugin,
46+
// ... other plugins
47+
];
48+
```
49+
50+
## Usage
51+
The plugin provides an action `dcapOnChainVerifyAction` which will be triggered by natural languages like:
52+
```plaintext
53+
"Verify the DCAP attestation on-chain"
54+
"Generate a DCAP attestation and verify it on-chain"
55+
"DCAP_ON_CHAIN" # The keyword will also trigger the action
56+
```
57+
58+
## Development
59+
60+
1. Clone the repository
61+
2. Install dependencies:
62+
63+
```bash
64+
pnpm install
65+
```
66+
67+
3. Build the plugin:
68+
69+
```bash
70+
pnpm run build
71+
```
72+
73+
4. Run tests:
74+
75+
```bash
76+
pnpm test
77+
```
78+
79+
We are welcom to any feedback and contributions!
80+
81+
## Credits
82+
- [Automata Network](https://ata.network): Provided the on-chain DCAP verification, enabling the decentralized verification of TEE attestations.
83+
- [Phala Network](https://phala.network): Provided support for running agents in TDX environment and contributed the `plugin-tee` for generating DCAP attestation on TDX.
84+
- [Gramine](https://gramineproject.io/): Provided support for running agents in SGX environment.
85+
86+
## License
87+
88+
This plugin is part of the Eliza project. See the main project repository for license information.

packages/plugin-dcap/package.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@elizaos/plugin-dcap",
3+
"version": "0.1.9-alpha.1",
4+
"type": "module",
5+
"main": "dist/index.js",
6+
"module": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"dependencies": {
9+
"@elizaos/core": "workspace:*",
10+
"@elizaos/plugin-sgx": "workspace:*",
11+
"@elizaos/plugin-tee": "workspace:*",
12+
"ethers": "^6.13.5"
13+
},
14+
"devDependencies": {
15+
"@types/node": "^20.0.0",
16+
"tsup": "8.3.5"
17+
},
18+
"scripts": {
19+
"build": "tsup --format esm --dts",
20+
"dev": "tsup --format esm --dts --watch",
21+
"lint": "eslint --fix --cache ."
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import type { Action } from "@elizaos/core";
2+
import { verifyAndAttestOnChain } from "../dcap.js";
3+
import { getQuote } from "../quote.js";
4+
import { DCAPMode } from "../types.js";
5+
import {
6+
getDCAPMode,
7+
getTEEMode,
8+
hasPrivateKey,
9+
hasTEEMode,
10+
} from "../utils.js";
11+
12+
export const dcapOnChainVerifyAction: Action = {
13+
name: "DCAP_ON_CHAIN",
14+
description:
15+
"This plugin is used to generate DCAP attestation and verify it on-chain. The user can also use the keyword DCAP_ON_CHAIN to trigger this action.",
16+
similes: [
17+
"DCAP",
18+
"DCAP_ATTESTATION",
19+
"DCAP_TEE",
20+
"DCAP_SGX",
21+
"DCAP_TDX",
22+
"VERIFY_ATTESTATION",
23+
"VERIFY_DCAP",
24+
"DCAP_VERIFICATION",
25+
"ATTESTATION",
26+
"GENERATE_ATTESTATION",
27+
],
28+
examples: [
29+
[
30+
{
31+
user: "{{user1}}",
32+
content: {
33+
text: "Generate a DCAP attestation and verify it on-chain",
34+
action: "DCAP_ON_CHAIN",
35+
},
36+
},
37+
{
38+
user: "{{user2}}",
39+
content: {
40+
text: "Of course, hanlding it now...",
41+
},
42+
},
43+
],
44+
[
45+
{
46+
user: "{{user1}}",
47+
content: { text: "Verify the DCAP attestation on-chain" },
48+
action: "DCAP_ON_CHAIN",
49+
},
50+
{
51+
user: "{{user2}}",
52+
content: {
53+
text: "Of course, hanlding it now...",
54+
},
55+
},
56+
],
57+
[
58+
{
59+
user: "{{user1}}",
60+
content: { text: "DCAP_ON_CHAIN" },
61+
action: "DCAP_ON_CHAIN",
62+
},
63+
{
64+
user: "{{user2}}",
65+
content: {
66+
text: "Of course, hanlding it now...",
67+
},
68+
},
69+
],
70+
],
71+
async validate(runtime, message) {
72+
if (!hasPrivateKey(runtime)) return false;
73+
const mode = getDCAPMode(runtime);
74+
if (!mode) return false;
75+
if (mode === DCAPMode.PLUGIN_TEE) return hasTEEMode(runtime);
76+
return true;
77+
},
78+
async handler(runtime, message, state, options, callback) {
79+
const { agentId } = runtime;
80+
const { userId, roomId, content } = message;
81+
const quote = await getQuote(
82+
// Attestation will be generated based on the message info
83+
JSON.stringify({
84+
agentId,
85+
timestamp: Date.now(),
86+
message: { userId, roomId, content: content.text },
87+
}),
88+
getDCAPMode(runtime),
89+
getTEEMode(runtime)
90+
);
91+
92+
const reply = (text: string) =>
93+
callback({
94+
text,
95+
// source: quote,
96+
action: "DCAP_ON_CHAIN",
97+
});
98+
try {
99+
const tx = await verifyAndAttestOnChain(
100+
runtime.getSetting("EVM_PRIVATE_KEY")!,
101+
quote
102+
);
103+
reply("Verified! Transaction hash: " + tx.hash);
104+
return true;
105+
} catch (e) {
106+
reply(e instanceof Error ? e.message : "Attestation failed");
107+
return false;
108+
}
109+
},
110+
suppressInitialMessage: true,
111+
};

packages/plugin-dcap/src/dcap.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { TransactionResponse } from "ethers";
2+
import { Contract, JsonRpcProvider, Wallet } from "ethers";
3+
4+
export namespace Chain {
5+
export enum Testnet {
6+
AUTOMATA = "automata_testnet",
7+
}
8+
9+
export enum Mainnet {}
10+
11+
export const Config: Record<Chain, { rpcUrl: string; address: string }> = {
12+
[Testnet.AUTOMATA]: {
13+
rpcUrl: "https://1rpc.io/ata/testnet",
14+
address: "0x6D67Ae70d99A4CcE500De44628BCB4DaCfc1A145",
15+
},
16+
};
17+
}
18+
export type Chain = Chain.Testnet | Chain.Mainnet;
19+
20+
export async function verifyAndAttestOnChain(
21+
privateKey: string,
22+
rawQuote: string,
23+
chain: Chain = Chain.Testnet.AUTOMATA
24+
) {
25+
const { rpcUrl, address } = Chain.Config[chain];
26+
const provider = new JsonRpcProvider(rpcUrl);
27+
const wallet = new Wallet(privateKey, provider);
28+
const contract = new Contract(
29+
address,
30+
[
31+
"function getBp() public view returns (uint16)",
32+
"function verifyAndAttestOnChain(bytes calldata rawQuote) external payable returns (bool success, bytes memory output)",
33+
],
34+
wallet
35+
);
36+
const estimateGas = async (value: bigint) =>
37+
await contract.verifyAndAttestOnChain.estimateGas(rawQuote, { value });
38+
39+
const $bp = contract.getBp();
40+
const $fee = provider.getFeeData();
41+
const gas = await estimateGas(await provider.getBalance(wallet));
42+
const bp = await $bp;
43+
const { gasPrice, maxFeePerGas } = await $fee;
44+
const tx = await contract.verifyAndAttestOnChain(rawQuote, {
45+
value: (gas * (gasPrice || maxFeePerGas)! * bp * 105n) / 1000000n,
46+
});
47+
return await (tx as TransactionResponse).wait();
48+
}

packages/plugin-dcap/src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Plugin } from "@elizaos/core";
2+
import { dcapOnChainVerifyAction } from "./actions/on-chain";
3+
4+
export const dcapPlugin: Plugin = {
5+
name: "dcap",
6+
description: "Basic DCAP attestation plugin",
7+
actions: [dcapOnChainVerifyAction],
8+
};

packages/plugin-dcap/src/quote.ts

+38
Large diffs are not rendered by default.
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, it } from "vitest";
2+
import { Chain, verifyAndAttestOnChain } from "../dcap";
3+
import { DEFAULT_QUOTE } from "../quote";
4+
5+
const privateKey =
6+
"0xc4389080437072a09215803a6b540f1e054797eeda2eec6d49076760d48e7589";
7+
const chain = Chain.Testnet.AUTOMATA;
8+
9+
describe("Verify rawQuote", () => {
10+
it("should verify rawQuote", async () => {
11+
const tx = await verifyAndAttestOnChain(
12+
privateKey,
13+
DEFAULT_QUOTE,
14+
chain
15+
);
16+
expect(tx).toBeDefined();
17+
});
18+
});
19+
20+
describe("Verify random hex will fail", () => {
21+
it("should not verify random hex", async () => {
22+
await expect(
23+
verifyAndAttestOnChain(privateKey, "0x1234", chain)
24+
).rejects.toThrow();
25+
});
26+
});

packages/plugin-dcap/src/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export enum DCAPMode {
2+
OFF = "OFF",
3+
PLUGIN_SGX = "PLUGIN-SGX",
4+
PLUGIN_TEE = "PLUGIN-TEE",
5+
MOCK = "MOCK",
6+
}

packages/plugin-dcap/src/utils.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { IAgentRuntime } from "@elizaos/core";
2+
import { TEEMode } from "@elizaos/plugin-tee";
3+
import { DCAPMode } from "./types";
4+
5+
export const is0xString = (s: string) =>
6+
typeof s === "string" && s.startsWith("0x");
7+
8+
export function hasPrivateKey(runtime: IAgentRuntime) {
9+
try {
10+
return is0xString(runtime.getSetting("EVM_PRIVATE_KEY"));
11+
} catch {
12+
return false;
13+
}
14+
}
15+
16+
export function getDCAPMode(runtime: IAgentRuntime) {
17+
try {
18+
const mode = runtime.getSetting("DCAP_MODE");
19+
if (!mode) return;
20+
switch (mode.toUpperCase()) {
21+
case DCAPMode.PLUGIN_SGX:
22+
return DCAPMode.PLUGIN_SGX;
23+
case DCAPMode.PLUGIN_TEE:
24+
return DCAPMode.PLUGIN_TEE;
25+
case DCAPMode.MOCK:
26+
return DCAPMode.MOCK;
27+
}
28+
} catch {}
29+
}
30+
31+
export const getTEEMode = (runtime: IAgentRuntime) =>
32+
runtime.getSetting("TEE_MODE") as TEEMode;
33+
34+
export function hasTEEMode(runtime: IAgentRuntime) {
35+
try {
36+
const mode = getTEEMode(runtime);
37+
return mode && mode !== TEEMode.OFF;
38+
} catch {
39+
return false;
40+
}
41+
}

0 commit comments

Comments
 (0)