@alejoamiras/tee-rex 4.0.0-devnet.2-patch.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,174 @@
1
+ import { BBLazyPrivateKernelProver } from "@aztec/bb-prover/client/lazy";
2
+ import type { PrivateExecutionStep } from "@aztec/stdlib/kernel";
3
+ import { ChonkProofWithPublicInputs } from "@aztec/stdlib/proofs";
4
+ import { schemas } from "@aztec/stdlib/schemas";
5
+ import ky from "ky";
6
+ import ms from "ms";
7
+ import { Base64, Bytes } from "ox";
8
+ import { UnreachableCaseError, type ValueOf } from "ts-essentials";
9
+ import { joinURL } from "ufo";
10
+ import { z } from "zod";
11
+ import { type AttestationVerifyOptions, verifyNitroAttestation } from "./attestation.js";
12
+ import { encrypt } from "./encrypt.js";
13
+ import { logger } from "./logger.js";
14
+
15
+ /** Whether proofs are generated locally (WASM) or on a remote tee-rex server. */
16
+ export type ProvingMode = ValueOf<typeof ProvingMode>;
17
+ export const ProvingMode = {
18
+ local: "local",
19
+ remote: "remote",
20
+ } as const;
21
+
22
+ export interface TeeRexAttestationConfig {
23
+ /** When true, reject servers running in standard (non-TEE) mode. Default: false. */
24
+ requireAttestation?: boolean;
25
+ /** Expected PCR values to verify against the attestation document. */
26
+ expectedPCRs?: AttestationVerifyOptions["expectedPCRs"];
27
+ /** Maximum age of attestation documents in milliseconds. Default: 5 minutes. */
28
+ maxAgeMs?: number;
29
+ }
30
+
31
+ /**
32
+ * Aztec private kernel prover that can generate proofs locally or on a remote
33
+ * tee-rex server running inside an AWS Nitro Enclave.
34
+ *
35
+ * In remote mode, witness data is encrypted with the server's attested public
36
+ * key (curve25519 + AES-256-GCM) before being sent over the network.
37
+ */
38
+ export class TeeRexProver extends BBLazyPrivateKernelProver {
39
+ #provingMode: ProvingMode = ProvingMode.remote;
40
+ #attestationConfig: TeeRexAttestationConfig = {};
41
+
42
+ constructor(
43
+ private apiUrl: string,
44
+ ...args: ConstructorParameters<typeof BBLazyPrivateKernelProver>
45
+ ) {
46
+ super(...args);
47
+ }
48
+
49
+ /** Switch between local WASM proving and remote TEE proving. */
50
+ setProvingMode(mode: ProvingMode) {
51
+ this.#provingMode = mode;
52
+ }
53
+
54
+ /** Update the tee-rex server URL used for remote proving. */
55
+ setApiUrl(url: string) {
56
+ this.apiUrl = url;
57
+ }
58
+
59
+ /** Configure attestation verification (PCR checks, freshness, require TEE). */
60
+ setAttestationConfig(config: TeeRexAttestationConfig) {
61
+ this.#attestationConfig = config;
62
+ }
63
+
64
+ async createChonkProof(
65
+ executionSteps: PrivateExecutionStep[],
66
+ ): Promise<ChonkProofWithPublicInputs> {
67
+ switch (this.#provingMode) {
68
+ case "local": {
69
+ logger.info("Using local prover", {
70
+ steps: executionSteps.length,
71
+ functions: executionSteps.map((s) => s.functionName),
72
+ });
73
+ const start = performance.now();
74
+ const result = await super.createChonkProof(executionSteps);
75
+ logger.info("Local proof completed", {
76
+ durationMs: Math.round(performance.now() - start),
77
+ });
78
+ return result;
79
+ }
80
+ case "remote": {
81
+ logger.info("Using remote prover");
82
+ return this.#remoteCreateChonkProof(executionSteps);
83
+ }
84
+ default: {
85
+ throw new UnreachableCaseError(this.#provingMode);
86
+ }
87
+ }
88
+ }
89
+
90
+ async #remoteCreateChonkProof(
91
+ executionSteps: PrivateExecutionStep[],
92
+ ): Promise<ChonkProofWithPublicInputs> {
93
+ logger.info("Creating chonk proof", { apiUrl: this.apiUrl });
94
+ const executionStepsSerialized = executionSteps.map((step) => ({
95
+ functionName: step.functionName,
96
+ witness: Array.from(step.witness.entries()),
97
+ bytecode: Base64.fromBytes(step.bytecode),
98
+ vk: Base64.fromBytes(step.vk),
99
+ timings: step.timings,
100
+ }));
101
+ logger.debug("Serialized payload", { chars: JSON.stringify(executionStepsSerialized).length });
102
+ const encryptionPublicKey = await this.#fetchEncryptionPublicKey();
103
+ const encryptedData = Base64.fromBytes(
104
+ await encrypt({
105
+ data: Bytes.fromString(JSON.stringify({ executionSteps: executionStepsSerialized })),
106
+ encryptionPublicKey,
107
+ }),
108
+ ); // TODO(perf): serialize executionSteps -> bytes without intermediate encoding. Needs Aztec to support serialization of the PrivateExecutionStep class.
109
+ const response = await ky
110
+ .post(joinURL(this.apiUrl, "prove"), {
111
+ json: { data: encryptedData },
112
+ timeout: ms("5 min"),
113
+ retry: 2,
114
+ })
115
+ .json();
116
+ const data = z
117
+ .object({
118
+ proof: schemas.Buffer,
119
+ })
120
+ .parse(response);
121
+ return ChonkProofWithPublicInputs.fromBuffer(data.proof);
122
+ }
123
+
124
+ async #fetchEncryptionPublicKey() {
125
+ const response = await ky.get(joinURL(this.apiUrl, "attestation"), { retry: 2 }).json();
126
+ const data = z
127
+ .discriminatedUnion("mode", [
128
+ z.object({ mode: z.literal("standard"), publicKey: z.string() }),
129
+ z.object({
130
+ mode: z.literal("nitro"),
131
+ attestationDocument: z.string(),
132
+ publicKey: z.string(),
133
+ }),
134
+ ])
135
+ .parse(response);
136
+
137
+ switch (data.mode) {
138
+ case "standard": {
139
+ if (this.#attestationConfig.requireAttestation) {
140
+ throw new Error(
141
+ "Server is running in standard mode but requireAttestation is enabled. " +
142
+ "The server must run inside a TEE to provide attestation.",
143
+ );
144
+ }
145
+ logger.warn("Server is running in standard mode (no TEE attestation)");
146
+ return data.publicKey;
147
+ }
148
+ case "nitro": {
149
+ try {
150
+ const { publicKey } = await verifyNitroAttestation(data.attestationDocument, {
151
+ expectedPCRs: this.#attestationConfig.expectedPCRs,
152
+ maxAgeMs: this.#attestationConfig.maxAgeMs,
153
+ });
154
+ return publicKey;
155
+ } catch (err) {
156
+ // In browser environments, node:crypto is unavailable. Fall back to the
157
+ // server-provided public key. The attestation document was still fetched
158
+ // over HTTPS; we just can't verify the COSE_Sign1 chain client-side.
159
+ if (
160
+ err instanceof Error &&
161
+ (err.message.includes("node:crypto") || err.message.includes("is not a constructor"))
162
+ ) {
163
+ logger.warn(
164
+ "Nitro attestation verification unavailable (browser environment). Using server-provided public key.",
165
+ { error: err.message },
166
+ );
167
+ return data.publicKey;
168
+ }
169
+ throw err;
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,10 @@
1
+ import { expect } from "bun:test";
2
+
3
+ // Patch expect for @aztec/foundation compatibility
4
+ // @aztec/foundation checks if expect.addEqualityTesters exists (vitest API)
5
+ if (!(expect as any).addEqualityTesters) {
6
+ (expect as any).addEqualityTesters = () => {};
7
+ }
8
+ if ((globalThis as any).expect && !(globalThis as any).expect.addEqualityTesters) {
9
+ (globalThis as any).expect.addEqualityTesters = () => {};
10
+ }