@taskforest/dark-forest 0.1.0 → 0.1.2
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.
- package/README.md +293 -8
- package/dist/__tests__/dark-forest.test.js +53 -5
- package/dist/index.d.ts +42 -9
- package/dist/index.js +108 -22
- package/dist/verifier.d.ts +47 -0
- package/dist/verifier.js +88 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,67 @@
|
|
|
1
1
|
# @taskforest/dark-forest
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Private machine payments for TaskForest.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
`@taskforest/dark-forest` is the payment layer for running metered agent payments through a private execution environment.
|
|
6
|
+
It helps you combine:
|
|
7
|
+
|
|
8
|
+
- TaskForest jobs,
|
|
9
|
+
- MPP-style metered payment flows,
|
|
10
|
+
- MagicBlock PER delegation,
|
|
11
|
+
- private settlement tracking.
|
|
12
|
+
|
|
13
|
+
If `@taskforest/sdk` is the layer for posting and managing work, `@taskforest/dark-forest` is the layer for paying for that work privately.
|
|
14
|
+
|
|
15
|
+
## What It Does
|
|
16
|
+
|
|
17
|
+
This package gives you helpers for:
|
|
18
|
+
|
|
19
|
+
- creating a Dark Forest escrow wrapper for a TaskForest job,
|
|
20
|
+
- delegating payment execution to MagicBlock PER,
|
|
21
|
+
- tracking metered per-request or per-call payment sessions,
|
|
22
|
+
- recording final settlement on-chain,
|
|
23
|
+
- reading escrow and settlement state back into your app.
|
|
24
|
+
|
|
25
|
+
This is designed for private machine-to-machine payment flows where agents are paid incrementally, but you do not want every intermediate payment signal exposed publicly.
|
|
26
|
+
|
|
27
|
+
Session tracking defaults to an in-memory store for fast starts and demos.
|
|
28
|
+
For production, you can inject your own session store so metering survives process restarts.
|
|
29
|
+
|
|
30
|
+
## Why Use It
|
|
31
|
+
|
|
32
|
+
Standard machine payment flows are useful, but public payment trails can leak strategy:
|
|
33
|
+
|
|
34
|
+
- how often an agent is called,
|
|
35
|
+
- what endpoints are hot,
|
|
36
|
+
- how much a task is costing in real time,
|
|
37
|
+
- which workers or tools are being used.
|
|
38
|
+
|
|
39
|
+
Dark Forest is for the case where you want MPP-style metering, but with privacy around the execution and payment flow.
|
|
40
|
+
|
|
41
|
+
You can think of it as:
|
|
42
|
+
|
|
43
|
+
- MPP for agent payments,
|
|
44
|
+
- with private execution routing,
|
|
45
|
+
- and on-chain settlement boundaries.
|
|
46
|
+
|
|
47
|
+
## Relationship To MPP
|
|
48
|
+
|
|
49
|
+
`@taskforest/dark-forest` is compatible with the idea of a machine payment protocol:
|
|
50
|
+
|
|
51
|
+
- open a payment session,
|
|
52
|
+
- meter usage as requests are served,
|
|
53
|
+
- accumulate payment state,
|
|
54
|
+
- settle the final amount.
|
|
55
|
+
|
|
56
|
+
The difference is that Dark Forest is focused on private machine payments.
|
|
57
|
+
|
|
58
|
+
Instead of exposing the full payment trail publicly, your app can:
|
|
59
|
+
|
|
60
|
+
- delegate into PER,
|
|
61
|
+
- track incremental usage privately,
|
|
62
|
+
- commit the final settlement outcome on-chain.
|
|
63
|
+
|
|
64
|
+
That makes it a good fit for agent markets, private inference, paid tools, private bidding, and metered workflows where visibility itself is sensitive.
|
|
7
65
|
|
|
8
66
|
## Install
|
|
9
67
|
|
|
@@ -11,9 +69,236 @@ Use it when you need MagicBlock PER delegation, payment-channel orchestration, o
|
|
|
11
69
|
npm install @taskforest/dark-forest
|
|
12
70
|
```
|
|
13
71
|
|
|
14
|
-
|
|
72
|
+
Typical companion packages:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @coral-xyz/anchor @solana/web3.js
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## What You Get
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {
|
|
82
|
+
DarkForestPayments,
|
|
83
|
+
DARK_FOREST_PROGRAM_ID,
|
|
84
|
+
ESCROW_SEED,
|
|
85
|
+
LocalStorageSessionStore,
|
|
86
|
+
MemorySessionStore,
|
|
87
|
+
SETTLEMENT_SEED,
|
|
88
|
+
PER_ENDPOINTS,
|
|
89
|
+
TEE_VALIDATORS,
|
|
90
|
+
} from '@taskforest/dark-forest'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- `DarkForestPayments`: helper class for escrow, delegation, session metering, and settlement.
|
|
94
|
+
- `MemorySessionStore`: default in-memory session store. Replace this in production with your own persistent implementation.
|
|
95
|
+
- `LocalStorageSessionStore`: browser-persistent session store for demos and lightweight apps.
|
|
96
|
+
- `DARK_FOREST_PROGRAM_ID`: Dark Forest payments program ID.
|
|
97
|
+
- `ESCROW_SEED` / `SETTLEMENT_SEED`: PDA seeds for escrow and settlement records.
|
|
98
|
+
- `PER_ENDPOINTS`: known PER endpoints.
|
|
99
|
+
- `TEE_VALIDATORS`: known validator public keys.
|
|
100
|
+
|
|
101
|
+
## Basic Setup
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { AnchorProvider } from '@coral-xyz/anchor'
|
|
105
|
+
import { Keypair } from '@solana/web3.js'
|
|
106
|
+
import { DarkForestPayments } from '@taskforest/dark-forest'
|
|
107
|
+
import paymentsIdl from './idl/taskforest_payments.json'
|
|
108
|
+
|
|
109
|
+
const wallet = Keypair.generate()
|
|
110
|
+
const provider = new AnchorProvider(connection, { publicKey: wallet.publicKey } as never, {
|
|
111
|
+
commitment: 'confirmed',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const darkForest = new DarkForestPayments(provider, paymentsIdl as never)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Production Session Store
|
|
118
|
+
|
|
119
|
+
The SDK now accepts a pluggable session store:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { DarkForestPayments, type SessionStore } from '@taskforest/dark-forest'
|
|
123
|
+
|
|
124
|
+
const sessionStore: SessionStore = {
|
|
125
|
+
async get(escrowId) {
|
|
126
|
+
return await db.loadSession(escrowId)
|
|
127
|
+
},
|
|
128
|
+
async set(session) {
|
|
129
|
+
await db.saveSession(session)
|
|
130
|
+
},
|
|
131
|
+
async delete(escrowId) {
|
|
132
|
+
await db.deleteSession(escrowId)
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const darkForest = new DarkForestPayments(provider, paymentsIdl as never, {
|
|
137
|
+
sessionStore,
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
That lets you meter private machine payments durably instead of relying on process memory.
|
|
142
|
+
|
|
143
|
+
For browser demos or lightweight clients:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { DarkForestPayments, LocalStorageSessionStore } from '@taskforest/dark-forest'
|
|
147
|
+
|
|
148
|
+
const darkForest = new DarkForestPayments(provider, paymentsIdl as never, {
|
|
149
|
+
sessionStore: new LocalStorageSessionStore(window.localStorage),
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Example: Create Escrow For A TaskForest Job
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { PublicKey } from '@solana/web3.js'
|
|
157
|
+
|
|
158
|
+
const jobPubkey = new PublicKey('...')
|
|
159
|
+
|
|
160
|
+
const tx = await darkForest.createEscrowWrapper(
|
|
161
|
+
101,
|
|
162
|
+
jobPubkey,
|
|
163
|
+
0.25,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
console.log('escrow tx:', tx)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This links a Dark Forest payment wrapper to a TaskForest job.
|
|
170
|
+
|
|
171
|
+
## Example: Delegate To PER
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { TEE_VALIDATORS } from '@taskforest/dark-forest'
|
|
175
|
+
|
|
176
|
+
const tx = await darkForest.delegateToPer(101, TEE_VALIDATORS.devnet)
|
|
177
|
+
console.log('delegate tx:', tx)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This moves the payment flow into a private execution environment.
|
|
181
|
+
|
|
182
|
+
## Example: Start A Private Metered Session
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { PER_ENDPOINTS } from '@taskforest/dark-forest'
|
|
186
|
+
|
|
187
|
+
const session = await darkForest.startPrivateSession(101, jobPubkey, {
|
|
188
|
+
agentEndpoint: 'https://agent.example.com',
|
|
189
|
+
token: 'SOL',
|
|
190
|
+
budgetLamports: 50_000_000,
|
|
191
|
+
perEndpoint: PER_ENDPOINTS.devnet,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
console.log(session.sessionId)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
This is the main private machine payment entry point:
|
|
198
|
+
|
|
199
|
+
- create escrow,
|
|
200
|
+
- delegate to PER,
|
|
201
|
+
- begin metered session tracking.
|
|
202
|
+
|
|
203
|
+
## Example: Record Incremental Usage
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
await darkForest.recordPayment(101, 500_000)
|
|
207
|
+
await darkForest.recordPayment(101, 500_000)
|
|
208
|
+
|
|
209
|
+
const session = darkForest.getActiveSession(101)
|
|
210
|
+
console.log(session?.requestCount)
|
|
211
|
+
console.log(session?.totalPaid)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Use this for:
|
|
215
|
+
|
|
216
|
+
- pay-per-request,
|
|
217
|
+
- pay-per-tool-call,
|
|
218
|
+
- pay-per-inference,
|
|
219
|
+
- metered agent execution.
|
|
220
|
+
|
|
221
|
+
## Example: Off-Chain TDX Verification Boundary
|
|
222
|
+
|
|
223
|
+
Full Intel TDX quote and certificate-chain verification should happen off-chain.
|
|
224
|
+
The on-chain program verifies a signed attestation envelope derived from a successful verifier result.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import {
|
|
228
|
+
JsonTdxQuoteVerifier,
|
|
229
|
+
buildVerifiedAttestationEnvelope,
|
|
230
|
+
DarkForestPayments,
|
|
231
|
+
} from '@taskforest/dark-forest'
|
|
232
|
+
|
|
233
|
+
const verifier = new JsonTdxQuoteVerifier(TEE_VALIDATORS.devnet)
|
|
234
|
+
const verified = await verifier.verifyQuote(rawQuoteBytes, {
|
|
235
|
+
allowedMrTd: ['...'],
|
|
236
|
+
allowedRtmr0: ['...'],
|
|
237
|
+
requireCertificateChain: true,
|
|
238
|
+
trustedRootFingerprints: ['...'],
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const envelope = buildVerifiedAttestationEnvelope(
|
|
242
|
+
101,
|
|
243
|
+
jobPubkey,
|
|
244
|
+
sessionIdBytes,
|
|
245
|
+
verified,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
const report = DarkForestPayments.buildAttestationReport(envelope)
|
|
249
|
+
const sigIx = DarkForestPayments.buildAttestationSignatureInstruction(validatorSigner, report)
|
|
250
|
+
|
|
251
|
+
await darkForest.verifyTeeAttestation(101, report, envelope.teePubkey, sigIx)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
This gives you a production-safe split:
|
|
255
|
+
|
|
256
|
+
- off-chain verifier handles quote parsing, certificate-chain validation, collateral, and measurement policy
|
|
257
|
+
- on-chain program enforces a signed, state-bound attestation result
|
|
258
|
+
|
|
259
|
+
## Example: Close Session And Settle
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const settleTx = await darkForest.closeSession(101)
|
|
263
|
+
console.log('settlement tx:', settleTx)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
This commits the final settlement amount after the metered session is complete.
|
|
267
|
+
|
|
268
|
+
## Example: Read Escrow / Settlement State
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const escrow = await darkForest.getEscrow(101)
|
|
272
|
+
const settlement = await darkForest.getSettlement(101)
|
|
273
|
+
|
|
274
|
+
console.log(escrow?.status)
|
|
275
|
+
console.log(settlement?.totalPaid)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Example: Derive PDAs
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { DarkForestPayments } from '@taskforest/dark-forest'
|
|
282
|
+
|
|
283
|
+
const escrowPda = DarkForestPayments.escrowPda(101)
|
|
284
|
+
const settlementPda = DarkForestPayments.settlementPda(101)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Suggested Flow
|
|
288
|
+
|
|
289
|
+
1. Create a job with TaskForest.
|
|
290
|
+
2. Create a Dark Forest escrow wrapper for that job.
|
|
291
|
+
3. Delegate payment execution into PER.
|
|
292
|
+
4. Meter machine usage privately with `recordPayment()`.
|
|
293
|
+
5. Close the session and record final settlement.
|
|
294
|
+
6. Read settlement state for UI, reconciliation, or audits.
|
|
295
|
+
|
|
296
|
+
## Best Fit
|
|
297
|
+
|
|
298
|
+
This package is most useful when you are building:
|
|
15
299
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
300
|
+
- private paid agents,
|
|
301
|
+
- metered AI APIs,
|
|
302
|
+
- private tool execution,
|
|
303
|
+
- hidden bid or routing systems,
|
|
304
|
+
- machine-to-machine payment flows where intermediate payment visibility is a disadvantage.
|
|
@@ -14,6 +14,19 @@ const index_1 = require("../index");
|
|
|
14
14
|
strict_1.default.equal(index_1.PER_ENDPOINTS.devnet, 'https://tee.magicblock.app');
|
|
15
15
|
strict_1.default.ok(index_1.TEE_VALIDATORS.devnet);
|
|
16
16
|
});
|
|
17
|
+
(0, node_test_1.default)('builds deterministic attestation report envelopes', () => {
|
|
18
|
+
const report = index_1.DarkForestPayments.buildAttestationReport({
|
|
19
|
+
escrowId: 42,
|
|
20
|
+
jobPubkey: index_1.DARK_FOREST_PROGRAM_ID,
|
|
21
|
+
validator: index_1.TEE_VALIDATORS.devnet,
|
|
22
|
+
teePubkey: Array(32).fill(7),
|
|
23
|
+
mppSessionId: Array(32).fill(9),
|
|
24
|
+
issuedAt: 100,
|
|
25
|
+
expiresAt: 200,
|
|
26
|
+
});
|
|
27
|
+
strict_1.default.equal(report.length, 160);
|
|
28
|
+
strict_1.default.equal(report.subarray(0, 4).toString('utf8'), 'TFAT');
|
|
29
|
+
});
|
|
17
30
|
(0, node_test_1.default)('derives deterministic escrow and settlement PDAs', () => {
|
|
18
31
|
const escrowOne = index_1.DarkForestPayments.escrowPda(42);
|
|
19
32
|
const escrowTwo = index_1.DarkForestPayments.escrowPda(42);
|
|
@@ -31,7 +44,7 @@ const index_1 = require("../index");
|
|
|
31
44
|
});
|
|
32
45
|
(0, node_test_1.default)('tracks an in-memory private session lifecycle', async () => {
|
|
33
46
|
const payments = Object.create(index_1.DarkForestPayments.prototype);
|
|
34
|
-
payments.
|
|
47
|
+
payments.sessionStore = new index_1.MemorySessionStore();
|
|
35
48
|
payments.createEscrowWrapper = async () => 'escrow-tx';
|
|
36
49
|
payments.delegateToPer = async () => 'delegate-tx';
|
|
37
50
|
payments.recordSettlement = async () => 'settlement-tx';
|
|
@@ -43,13 +56,13 @@ const index_1 = require("../index");
|
|
|
43
56
|
});
|
|
44
57
|
strict_1.default.equal(session.escrowId, 77);
|
|
45
58
|
strict_1.default.equal(session.isActive, true);
|
|
46
|
-
strict_1.default.equal(payments.getActiveSession(77)?.requestCount, 0);
|
|
59
|
+
strict_1.default.equal((await payments.getActiveSession(77))?.requestCount, 0);
|
|
47
60
|
await payments.recordPayment(77, 500000);
|
|
48
|
-
strict_1.default.equal(payments.getActiveSession(77)?.totalPaid, 500000);
|
|
49
|
-
strict_1.default.equal(payments.getActiveSession(77)?.requestCount, 1);
|
|
61
|
+
strict_1.default.equal((await payments.getActiveSession(77))?.totalPaid, 500000);
|
|
62
|
+
strict_1.default.equal((await payments.getActiveSession(77))?.requestCount, 1);
|
|
50
63
|
const tx = await payments.closeSession(77);
|
|
51
64
|
strict_1.default.equal(tx, 'settlement-tx');
|
|
52
|
-
strict_1.default.equal(payments.getActiveSession(77), undefined);
|
|
65
|
+
strict_1.default.equal(await payments.getActiveSession(77), undefined);
|
|
53
66
|
});
|
|
54
67
|
(0, node_test_1.default)('returns null for unreadable escrow and settlement accounts', async () => {
|
|
55
68
|
const payments = Object.create(index_1.DarkForestPayments.prototype);
|
|
@@ -62,3 +75,38 @@ const index_1 = require("../index");
|
|
|
62
75
|
strict_1.default.equal(await payments.getEscrow(5), null);
|
|
63
76
|
strict_1.default.equal(await payments.getSettlement(5), null);
|
|
64
77
|
});
|
|
78
|
+
(0, node_test_1.default)('persists sessions in local storage compatible store', async () => {
|
|
79
|
+
const backing = new Map();
|
|
80
|
+
const storage = {
|
|
81
|
+
getItem(key) { return backing.get(key) ?? null; },
|
|
82
|
+
setItem(key, value) { backing.set(key, value); },
|
|
83
|
+
removeItem(key) { backing.delete(key); },
|
|
84
|
+
clear() { backing.clear(); },
|
|
85
|
+
key(index) { return Array.from(backing.keys())[index] ?? null; },
|
|
86
|
+
get length() { return backing.size; },
|
|
87
|
+
};
|
|
88
|
+
const store = new index_1.LocalStorageSessionStore(storage);
|
|
89
|
+
await store.set({ sessionId: 'abc', escrowId: 5, totalPaid: 7, requestCount: 2, isActive: true });
|
|
90
|
+
strict_1.default.equal((await store.get(5))?.sessionId, 'abc');
|
|
91
|
+
await store.delete(5);
|
|
92
|
+
strict_1.default.equal(await store.get(5), null);
|
|
93
|
+
});
|
|
94
|
+
(0, node_test_1.default)('verifies json-backed tdx quote claims against policy and builds attestation envelope', async () => {
|
|
95
|
+
const verifier = new index_1.JsonTdxQuoteVerifier(index_1.TEE_VALIDATORS.devnet);
|
|
96
|
+
const now = Math.floor(Date.now() / 1000);
|
|
97
|
+
const quote = Buffer.from(JSON.stringify({
|
|
98
|
+
teePubkey: Array(32).fill(3),
|
|
99
|
+
mrTd: 'abcd',
|
|
100
|
+
rtmr0: 'ef01',
|
|
101
|
+
issuedAt: now - 60,
|
|
102
|
+
expiresAt: now + 300,
|
|
103
|
+
}));
|
|
104
|
+
const verified = await verifier.verifyQuote(quote, {
|
|
105
|
+
allowedMrTd: ['abcd'],
|
|
106
|
+
allowedRtmr0: ['ef01'],
|
|
107
|
+
});
|
|
108
|
+
const envelope = (0, index_1.buildVerifiedAttestationEnvelope)(9, index_1.DARK_FOREST_PROGRAM_ID, Array(32).fill(4), verified);
|
|
109
|
+
strict_1.default.equal(envelope.escrowId, 9);
|
|
110
|
+
strict_1.default.equal(envelope.validator.toBase58(), index_1.TEE_VALIDATORS.devnet.toBase58());
|
|
111
|
+
strict_1.default.equal(envelope.teePubkey.length, 32);
|
|
112
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { AnchorProvider, type Idl } from '@coral-xyz/anchor';
|
|
4
|
-
import { Connection, PublicKey } from '@solana/web3.js';
|
|
2
|
+
import { Connection, type Keypair, PublicKey, type TransactionInstruction } from '@solana/web3.js';
|
|
3
|
+
export * from './verifier';
|
|
5
4
|
export declare const DARK_FOREST_PROGRAM_ID: PublicKey;
|
|
6
|
-
export declare const ESCROW_SEED: Buffer
|
|
7
|
-
export declare const SETTLEMENT_SEED: Buffer
|
|
5
|
+
export declare const ESCROW_SEED: Buffer<ArrayBuffer>;
|
|
6
|
+
export declare const SETTLEMENT_SEED: Buffer<ArrayBuffer>;
|
|
8
7
|
export declare const TEE_VALIDATORS: {
|
|
9
8
|
readonly mainnet: PublicKey;
|
|
10
9
|
readonly devnet: PublicKey;
|
|
@@ -27,6 +26,26 @@ export type MppSessionState = {
|
|
|
27
26
|
requestCount: number;
|
|
28
27
|
isActive: boolean;
|
|
29
28
|
};
|
|
29
|
+
export interface SessionStore {
|
|
30
|
+
get(escrowId: number): Promise<MppSessionState | null>;
|
|
31
|
+
set(session: MppSessionState): Promise<void>;
|
|
32
|
+
delete(escrowId: number): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare class MemorySessionStore implements SessionStore {
|
|
35
|
+
private sessions;
|
|
36
|
+
get(escrowId: number): Promise<MppSessionState | null>;
|
|
37
|
+
set(session: MppSessionState): Promise<void>;
|
|
38
|
+
delete(escrowId: number): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare class LocalStorageSessionStore implements SessionStore {
|
|
41
|
+
private readonly storage;
|
|
42
|
+
private readonly prefix;
|
|
43
|
+
constructor(storage: Storage, prefix?: string);
|
|
44
|
+
private key;
|
|
45
|
+
get(escrowId: number): Promise<MppSessionState | null>;
|
|
46
|
+
set(session: MppSessionState): Promise<void>;
|
|
47
|
+
delete(escrowId: number): Promise<void>;
|
|
48
|
+
}
|
|
30
49
|
export interface EscrowState {
|
|
31
50
|
escrowId: number;
|
|
32
51
|
jobPubkey: PublicKey;
|
|
@@ -49,22 +68,36 @@ export interface SettlementState {
|
|
|
49
68
|
settledAt: number;
|
|
50
69
|
settlementHash: number[];
|
|
51
70
|
}
|
|
71
|
+
export interface TeeAttestationEnvelope {
|
|
72
|
+
escrowId: number;
|
|
73
|
+
jobPubkey: PublicKey;
|
|
74
|
+
validator: PublicKey;
|
|
75
|
+
teePubkey: number[];
|
|
76
|
+
mppSessionId: number[];
|
|
77
|
+
issuedAt: number;
|
|
78
|
+
expiresAt: number;
|
|
79
|
+
}
|
|
80
|
+
export interface DarkForestPaymentsOptions {
|
|
81
|
+
sessionStore?: SessionStore;
|
|
82
|
+
}
|
|
52
83
|
export declare class DarkForestPayments {
|
|
53
84
|
private program;
|
|
54
85
|
private provider;
|
|
55
|
-
private
|
|
56
|
-
constructor(provider: AnchorProvider, idl: Idl);
|
|
86
|
+
private sessionStore;
|
|
87
|
+
constructor(provider: AnchorProvider, idl: Idl, options?: DarkForestPaymentsOptions);
|
|
57
88
|
connectToPer(endpoint?: string): Connection;
|
|
89
|
+
static buildAttestationReport(envelope: TeeAttestationEnvelope): Buffer;
|
|
90
|
+
static buildAttestationSignatureInstruction(validatorSigner: Keypair, report: Buffer): TransactionInstruction;
|
|
58
91
|
createEscrowWrapper(escrowId: number, jobPubkey: PublicKey, depositSol: number, mppSessionId?: number[]): Promise<string>;
|
|
59
92
|
delegateToPer(escrowId: number, validator?: PublicKey): Promise<string>;
|
|
60
|
-
verifyTeeAttestation(escrowId: number, attestationReport: Buffer, teePubkey: number[]): Promise<string>;
|
|
93
|
+
verifyTeeAttestation(escrowId: number, attestationReport: Buffer, teePubkey: number[], signatureInstruction?: TransactionInstruction): Promise<string>;
|
|
61
94
|
recordSettlement(escrowId: number, totalPaidSol: number): Promise<string>;
|
|
62
95
|
startPrivateSession(escrowId: number, jobPubkey: PublicKey, config: MppSessionConfig): Promise<MppSessionState>;
|
|
63
96
|
recordPayment(escrowId: number, amountLamports: number): Promise<void>;
|
|
64
97
|
closeSession(escrowId: number): Promise<string>;
|
|
98
|
+
getActiveSession(escrowId: number): Promise<MppSessionState | undefined>;
|
|
65
99
|
getEscrow(escrowId: number): Promise<EscrowState | null>;
|
|
66
100
|
getSettlement(escrowId: number): Promise<SettlementState | null>;
|
|
67
|
-
getActiveSession(escrowId: number): MppSessionState | undefined;
|
|
68
101
|
static escrowPda(escrowId: number): PublicKey;
|
|
69
102
|
static settlementPda(escrowId: number): PublicKey;
|
|
70
103
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DarkForestPayments = exports.PER_ENDPOINTS = exports.TEE_VALIDATORS = exports.SETTLEMENT_SEED = exports.ESCROW_SEED = exports.DARK_FOREST_PROGRAM_ID = void 0;
|
|
17
|
+
exports.DarkForestPayments = exports.LocalStorageSessionStore = exports.MemorySessionStore = exports.PER_ENDPOINTS = exports.TEE_VALIDATORS = exports.SETTLEMENT_SEED = exports.ESCROW_SEED = exports.DARK_FOREST_PROGRAM_ID = void 0;
|
|
4
18
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
5
19
|
const web3_js_1 = require("@solana/web3.js");
|
|
20
|
+
__exportStar(require("./verifier"), exports);
|
|
6
21
|
exports.DARK_FOREST_PROGRAM_ID = new web3_js_1.PublicKey('4hNP2tU5r5GgyASTrou84kWHbCwdyXVJJN4mve99rjgs');
|
|
7
22
|
exports.ESCROW_SEED = Buffer.from('escrow');
|
|
8
23
|
exports.SETTLEMENT_SEED = Buffer.from('settlement');
|
|
@@ -15,6 +30,41 @@ exports.PER_ENDPOINTS = {
|
|
|
15
30
|
devnet: 'https://tee.magicblock.app',
|
|
16
31
|
devnetRouter: 'https://devnet-router.magicblock.app',
|
|
17
32
|
};
|
|
33
|
+
class MemorySessionStore {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.sessions = new Map();
|
|
36
|
+
}
|
|
37
|
+
async get(escrowId) {
|
|
38
|
+
return this.sessions.get(escrowId) ?? null;
|
|
39
|
+
}
|
|
40
|
+
async set(session) {
|
|
41
|
+
this.sessions.set(session.escrowId, session);
|
|
42
|
+
}
|
|
43
|
+
async delete(escrowId) {
|
|
44
|
+
this.sessions.delete(escrowId);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.MemorySessionStore = MemorySessionStore;
|
|
48
|
+
class LocalStorageSessionStore {
|
|
49
|
+
constructor(storage, prefix = 'taskforest:dark-forest:session:') {
|
|
50
|
+
this.storage = storage;
|
|
51
|
+
this.prefix = prefix;
|
|
52
|
+
}
|
|
53
|
+
key(escrowId) {
|
|
54
|
+
return `${this.prefix}${escrowId}`;
|
|
55
|
+
}
|
|
56
|
+
async get(escrowId) {
|
|
57
|
+
const raw = this.storage.getItem(this.key(escrowId));
|
|
58
|
+
return raw ? JSON.parse(raw) : null;
|
|
59
|
+
}
|
|
60
|
+
async set(session) {
|
|
61
|
+
this.storage.setItem(this.key(session.escrowId), JSON.stringify(session));
|
|
62
|
+
}
|
|
63
|
+
async delete(escrowId) {
|
|
64
|
+
this.storage.removeItem(this.key(escrowId));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.LocalStorageSessionStore = LocalStorageSessionStore;
|
|
18
68
|
function deriveEscrowPda(escrowId) {
|
|
19
69
|
return web3_js_1.PublicKey.findProgramAddressSync([exports.ESCROW_SEED, new anchor_1.BN(escrowId).toArrayLike(Buffer, 'le', 8)], exports.DARK_FOREST_PROGRAM_ID);
|
|
20
70
|
}
|
|
@@ -22,14 +72,33 @@ function deriveSettlementPda(escrowId) {
|
|
|
22
72
|
return web3_js_1.PublicKey.findProgramAddressSync([exports.SETTLEMENT_SEED, new anchor_1.BN(escrowId).toArrayLike(Buffer, 'le', 8)], exports.DARK_FOREST_PROGRAM_ID);
|
|
23
73
|
}
|
|
24
74
|
class DarkForestPayments {
|
|
25
|
-
constructor(provider, idl) {
|
|
26
|
-
this.activeSessions = new Map();
|
|
75
|
+
constructor(provider, idl, options = {}) {
|
|
27
76
|
this.provider = provider;
|
|
28
77
|
this.program = new anchor_1.Program(idl, provider);
|
|
78
|
+
this.sessionStore = options.sessionStore ?? new MemorySessionStore();
|
|
29
79
|
}
|
|
30
80
|
connectToPer(endpoint = exports.PER_ENDPOINTS.devnet) {
|
|
31
81
|
return new web3_js_1.Connection(endpoint, 'confirmed');
|
|
32
82
|
}
|
|
83
|
+
static buildAttestationReport(envelope) {
|
|
84
|
+
return Buffer.concat([
|
|
85
|
+
Buffer.from('TFAT'),
|
|
86
|
+
Buffer.from([1, 0, 0, 0]),
|
|
87
|
+
new anchor_1.BN(envelope.escrowId).toArrayLike(Buffer, 'le', 8),
|
|
88
|
+
envelope.jobPubkey.toBuffer(),
|
|
89
|
+
envelope.validator.toBuffer(),
|
|
90
|
+
Buffer.from(envelope.teePubkey),
|
|
91
|
+
Buffer.from(envelope.mppSessionId),
|
|
92
|
+
new anchor_1.BN(envelope.issuedAt).toTwos(64).toArrayLike(Buffer, 'le', 8),
|
|
93
|
+
new anchor_1.BN(envelope.expiresAt).toTwos(64).toArrayLike(Buffer, 'le', 8),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
static buildAttestationSignatureInstruction(validatorSigner, report) {
|
|
97
|
+
return web3_js_1.Ed25519Program.createInstructionWithPrivateKey({
|
|
98
|
+
privateKey: validatorSigner.secretKey,
|
|
99
|
+
message: report,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
33
102
|
async createEscrowWrapper(escrowId, jobPubkey, depositSol, mppSessionId = Array(32).fill(0)) {
|
|
34
103
|
const [escrowPda] = deriveEscrowPda(escrowId);
|
|
35
104
|
return this.program.methods
|
|
@@ -42,36 +111,50 @@ class DarkForestPayments {
|
|
|
42
111
|
})
|
|
43
112
|
.rpc();
|
|
44
113
|
}
|
|
45
|
-
async delegateToPer(escrowId, validator
|
|
114
|
+
async delegateToPer(escrowId, validator) {
|
|
46
115
|
const [escrowPda] = deriveEscrowPda(escrowId);
|
|
47
|
-
|
|
48
|
-
.delegateToPer()
|
|
116
|
+
const method = this.program.methods
|
|
117
|
+
.delegateToPer(new anchor_1.BN(escrowId))
|
|
49
118
|
.accounts({
|
|
50
119
|
pda: escrowPda,
|
|
51
120
|
payer: this.provider.wallet.publicKey,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
121
|
+
});
|
|
122
|
+
if (validator) {
|
|
123
|
+
return method
|
|
124
|
+
.remainingAccounts([{ pubkey: validator, isSigner: false, isWritable: false }])
|
|
125
|
+
.rpc();
|
|
126
|
+
}
|
|
127
|
+
return method.rpc();
|
|
55
128
|
}
|
|
56
|
-
async verifyTeeAttestation(escrowId, attestationReport, teePubkey) {
|
|
129
|
+
async verifyTeeAttestation(escrowId, attestationReport, teePubkey, signatureInstruction) {
|
|
57
130
|
const [escrowPda] = deriveEscrowPda(escrowId);
|
|
58
|
-
|
|
131
|
+
const method = this.program.methods
|
|
59
132
|
.verifyTeeAttestation(new anchor_1.BN(escrowId), attestationReport, teePubkey)
|
|
60
133
|
.accounts({
|
|
61
134
|
escrow: escrowPda,
|
|
135
|
+
validator: this.provider.wallet.publicKey,
|
|
62
136
|
payer: this.provider.wallet.publicKey,
|
|
63
|
-
|
|
137
|
+
instructionsSysvar: web3_js_1.SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
138
|
+
});
|
|
139
|
+
if (signatureInstruction) {
|
|
140
|
+
return method.preInstructions([signatureInstruction]).rpc();
|
|
141
|
+
}
|
|
142
|
+
return method
|
|
64
143
|
.rpc();
|
|
65
144
|
}
|
|
66
145
|
async recordSettlement(escrowId, totalPaidSol) {
|
|
67
146
|
const [escrowPda] = deriveEscrowPda(escrowId);
|
|
68
147
|
const [settlementPda] = deriveSettlementPda(escrowId);
|
|
148
|
+
const escrow = await this.getEscrow(escrowId);
|
|
149
|
+
if (!escrow)
|
|
150
|
+
throw new Error(`Escrow ${escrowId} not found`);
|
|
69
151
|
return this.program.methods
|
|
70
152
|
.recordSettlement(new anchor_1.BN(escrowId), new anchor_1.BN(Math.floor(totalPaidSol * web3_js_1.LAMPORTS_PER_SOL)))
|
|
71
153
|
.accounts({
|
|
72
154
|
escrow: escrowPda,
|
|
73
155
|
settlementRecord: settlementPda,
|
|
74
156
|
poster: this.provider.wallet.publicKey,
|
|
157
|
+
agent: escrow.agent,
|
|
75
158
|
payer: this.provider.wallet.publicKey,
|
|
76
159
|
systemProgram: web3_js_1.SystemProgram.programId,
|
|
77
160
|
})
|
|
@@ -90,26 +173,32 @@ class DarkForestPayments {
|
|
|
90
173
|
requestCount: 0,
|
|
91
174
|
isActive: true,
|
|
92
175
|
};
|
|
93
|
-
this.
|
|
176
|
+
await this.sessionStore.set(session);
|
|
94
177
|
return session;
|
|
95
178
|
}
|
|
96
179
|
async recordPayment(escrowId, amountLamports) {
|
|
97
|
-
const session = this.
|
|
180
|
+
const session = await this.sessionStore.get(escrowId);
|
|
98
181
|
if (!session || !session.isActive)
|
|
99
182
|
throw new Error('No active session for this escrow');
|
|
100
|
-
|
|
101
|
-
|
|
183
|
+
await this.sessionStore.set({
|
|
184
|
+
...session,
|
|
185
|
+
totalPaid: session.totalPaid + amountLamports,
|
|
186
|
+
requestCount: session.requestCount + 1,
|
|
187
|
+
});
|
|
102
188
|
}
|
|
103
189
|
async closeSession(escrowId) {
|
|
104
|
-
const session = this.
|
|
190
|
+
const session = await this.sessionStore.get(escrowId);
|
|
105
191
|
if (!session)
|
|
106
192
|
throw new Error('No session for this escrow');
|
|
107
|
-
session
|
|
193
|
+
await this.sessionStore.set({ ...session, isActive: false });
|
|
108
194
|
const totalPaidSol = session.totalPaid / web3_js_1.LAMPORTS_PER_SOL;
|
|
109
195
|
const tx = await this.recordSettlement(escrowId, totalPaidSol);
|
|
110
|
-
this.
|
|
196
|
+
await this.sessionStore.delete(escrowId);
|
|
111
197
|
return tx;
|
|
112
198
|
}
|
|
199
|
+
async getActiveSession(escrowId) {
|
|
200
|
+
return (await this.sessionStore.get(escrowId)) ?? undefined;
|
|
201
|
+
}
|
|
113
202
|
async getEscrow(escrowId) {
|
|
114
203
|
const [escrowPda] = deriveEscrowPda(escrowId);
|
|
115
204
|
try {
|
|
@@ -151,9 +240,6 @@ class DarkForestPayments {
|
|
|
151
240
|
return null;
|
|
152
241
|
}
|
|
153
242
|
}
|
|
154
|
-
getActiveSession(escrowId) {
|
|
155
|
-
return this.activeSessions.get(escrowId);
|
|
156
|
-
}
|
|
157
243
|
static escrowPda(escrowId) {
|
|
158
244
|
return deriveEscrowPda(escrowId)[0];
|
|
159
245
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PublicKey } from '@solana/web3.js';
|
|
2
|
+
export interface TdxQuoteVerificationPolicy {
|
|
3
|
+
allowedMrTd?: string[];
|
|
4
|
+
allowedRtmr0?: string[];
|
|
5
|
+
allowedRtmr1?: string[];
|
|
6
|
+
allowedRtmr2?: string[];
|
|
7
|
+
allowedRtmr3?: string[];
|
|
8
|
+
requireCertificateChain?: boolean;
|
|
9
|
+
trustedRootFingerprints?: string[];
|
|
10
|
+
notAfterGraceSeconds?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TdxQuoteClaims {
|
|
13
|
+
teePubkey: Uint8Array;
|
|
14
|
+
mrTd?: string;
|
|
15
|
+
rtmr0?: string;
|
|
16
|
+
rtmr1?: string;
|
|
17
|
+
rtmr2?: string;
|
|
18
|
+
rtmr3?: string;
|
|
19
|
+
issuedAt: number;
|
|
20
|
+
expiresAt: number;
|
|
21
|
+
certificateChainPem?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface VerifiedTdxQuote {
|
|
24
|
+
claims: TdxQuoteClaims;
|
|
25
|
+
verifier: PublicKey;
|
|
26
|
+
verifiedAt: number;
|
|
27
|
+
}
|
|
28
|
+
export interface TdxQuoteVerifier {
|
|
29
|
+
verifyQuote(quote: Uint8Array, policy: TdxQuoteVerificationPolicy): Promise<VerifiedTdxQuote>;
|
|
30
|
+
}
|
|
31
|
+
export interface VerifiedAttestationEnvelope {
|
|
32
|
+
escrowId: number;
|
|
33
|
+
jobPubkey: PublicKey;
|
|
34
|
+
validator: PublicKey;
|
|
35
|
+
teePubkey: number[];
|
|
36
|
+
mppSessionId: number[];
|
|
37
|
+
issuedAt: number;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function verifyCertificateChainPem(chainPem: string[], trustedRootFingerprints?: string[], nowSec?: number, graceSeconds?: number): void;
|
|
41
|
+
export declare function assertTdxQuotePolicy(claims: TdxQuoteClaims, policy: TdxQuoteVerificationPolicy, nowSec?: number): void;
|
|
42
|
+
export declare class JsonTdxQuoteVerifier implements TdxQuoteVerifier {
|
|
43
|
+
private readonly verifier;
|
|
44
|
+
constructor(verifier: PublicKey);
|
|
45
|
+
verifyQuote(quote: Uint8Array, policy: TdxQuoteVerificationPolicy): Promise<VerifiedTdxQuote>;
|
|
46
|
+
}
|
|
47
|
+
export declare function buildVerifiedAttestationEnvelope(escrowId: number, jobPubkey: PublicKey, mppSessionId: number[], verified: VerifiedTdxQuote): VerifiedAttestationEnvelope;
|
package/dist/verifier.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonTdxQuoteVerifier = void 0;
|
|
4
|
+
exports.verifyCertificateChainPem = verifyCertificateChainPem;
|
|
5
|
+
exports.assertTdxQuotePolicy = assertTdxQuotePolicy;
|
|
6
|
+
exports.buildVerifiedAttestationEnvelope = buildVerifiedAttestationEnvelope;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
function normalizeHex(value) {
|
|
9
|
+
return value?.toLowerCase().replace(/^0x/, '');
|
|
10
|
+
}
|
|
11
|
+
function sha256Hex(data) {
|
|
12
|
+
return (0, node_crypto_1.createHash)('sha256').update(data).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
function ensureAllowed(label, value, allowed) {
|
|
15
|
+
if (!allowed || allowed.length === 0)
|
|
16
|
+
return;
|
|
17
|
+
const normalized = normalizeHex(value);
|
|
18
|
+
const expected = new Set(allowed.map((item) => normalizeHex(item)));
|
|
19
|
+
if (!normalized || !expected.has(normalized)) {
|
|
20
|
+
throw new Error(`TDX policy mismatch for ${label}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function verifyCertificateChainPem(chainPem, trustedRootFingerprints, nowSec = Math.floor(Date.now() / 1000), graceSeconds = 0) {
|
|
24
|
+
if (!chainPem.length)
|
|
25
|
+
throw new Error('Missing certificate chain');
|
|
26
|
+
const certs = chainPem.map((pem) => new node_crypto_1.X509Certificate(pem));
|
|
27
|
+
for (let i = 0; i < certs.length; i += 1) {
|
|
28
|
+
const cert = certs[i];
|
|
29
|
+
const now = nowSec * 1000;
|
|
30
|
+
if (Date.parse(cert.validFrom) > now)
|
|
31
|
+
throw new Error('Certificate not yet valid');
|
|
32
|
+
if (Date.parse(cert.validTo) + graceSeconds * 1000 < now)
|
|
33
|
+
throw new Error('Certificate expired');
|
|
34
|
+
if (i < certs.length - 1) {
|
|
35
|
+
const issuer = certs[i + 1];
|
|
36
|
+
if (!cert.verify(issuer.publicKey))
|
|
37
|
+
throw new Error('Certificate chain signature invalid');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (trustedRootFingerprints?.length) {
|
|
41
|
+
const root = certs[certs.length - 1];
|
|
42
|
+
const fingerprint = sha256Hex(root.raw);
|
|
43
|
+
const trusted = new Set(trustedRootFingerprints.map((item) => normalizeHex(item)));
|
|
44
|
+
if (!trusted.has(normalizeHex(fingerprint))) {
|
|
45
|
+
throw new Error('Certificate root is not trusted');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function assertTdxQuotePolicy(claims, policy, nowSec = Math.floor(Date.now() / 1000)) {
|
|
50
|
+
if (claims.issuedAt > nowSec)
|
|
51
|
+
throw new Error('TDX quote is not yet valid');
|
|
52
|
+
if (claims.expiresAt < nowSec)
|
|
53
|
+
throw new Error('TDX quote has expired');
|
|
54
|
+
ensureAllowed('mrTd', claims.mrTd, policy.allowedMrTd);
|
|
55
|
+
ensureAllowed('rtmr0', claims.rtmr0, policy.allowedRtmr0);
|
|
56
|
+
ensureAllowed('rtmr1', claims.rtmr1, policy.allowedRtmr1);
|
|
57
|
+
ensureAllowed('rtmr2', claims.rtmr2, policy.allowedRtmr2);
|
|
58
|
+
ensureAllowed('rtmr3', claims.rtmr3, policy.allowedRtmr3);
|
|
59
|
+
if (policy.requireCertificateChain) {
|
|
60
|
+
verifyCertificateChainPem(claims.certificateChainPem ?? [], policy.trustedRootFingerprints, nowSec, policy.notAfterGraceSeconds ?? 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
class JsonTdxQuoteVerifier {
|
|
64
|
+
constructor(verifier) {
|
|
65
|
+
this.verifier = verifier;
|
|
66
|
+
}
|
|
67
|
+
async verifyQuote(quote, policy) {
|
|
68
|
+
const claims = JSON.parse(Buffer.from(quote).toString('utf8'));
|
|
69
|
+
assertTdxQuotePolicy(claims, policy);
|
|
70
|
+
return {
|
|
71
|
+
claims,
|
|
72
|
+
verifier: this.verifier,
|
|
73
|
+
verifiedAt: Math.floor(Date.now() / 1000),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.JsonTdxQuoteVerifier = JsonTdxQuoteVerifier;
|
|
78
|
+
function buildVerifiedAttestationEnvelope(escrowId, jobPubkey, mppSessionId, verified) {
|
|
79
|
+
return {
|
|
80
|
+
escrowId,
|
|
81
|
+
jobPubkey,
|
|
82
|
+
validator: verified.verifier,
|
|
83
|
+
teePubkey: Array.from(verified.claims.teePubkey),
|
|
84
|
+
mppSessionId,
|
|
85
|
+
issuedAt: verified.claims.issuedAt,
|
|
86
|
+
expiresAt: verified.claims.expiresAt,
|
|
87
|
+
};
|
|
88
|
+
}
|
package/package.json
CHANGED