@antseed/cli 0.1.158 → 0.1.159
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 +23 -0
- package/dist/cli/commands/buyer/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +7 -1
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/network/browse.d.ts.map +1 -1
- package/dist/cli/commands/network/browse.js +12 -3
- package/dist/cli/commands/network/browse.js.map +1 -1
- package/dist/cli/commands/network/chain-config-helper.d.ts +3 -1
- package/dist/cli/commands/network/chain-config-helper.d.ts.map +1 -1
- package/dist/cli/commands/network/chain-config-helper.js +6 -1
- package/dist/cli/commands/network/chain-config-helper.js.map +1 -1
- package/dist/cli/commands/network/peer.d.ts.map +1 -1
- package/dist/cli/commands/network/peer.js +21 -1
- package/dist/cli/commands/network/peer.js.map +1 -1
- package/dist/cli/commands/seller/contract-clients.test.js +5 -4
- package/dist/cli/commands/seller/contract-clients.test.js.map +1 -1
- package/dist/plugins/verifier-evidence.test.d.ts +2 -0
- package/dist/plugins/verifier-evidence.test.d.ts.map +1 -0
- package/dist/plugins/verifier-evidence.test.js +41 -0
- package/dist/plugins/verifier-evidence.test.js.map +1 -0
- package/dist/plugins/verifier.d.ts +8 -10
- package/dist/plugins/verifier.d.ts.map +1 -1
- package/dist/plugins/verifier.js +23 -51
- package/dist/plugins/verifier.js.map +1 -1
- package/dist/plugins/verifier.test.js +1 -37
- package/dist/plugins/verifier.test.js.map +1 -1
- package/dist/proxy/buyer-proxy.d.ts +2 -1
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +104 -28
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +177 -19
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/network-models.d.ts +1 -2
- package/dist/proxy/network-models.d.ts.map +1 -1
- package/dist/proxy/network-models.js +10 -23
- package/dist/proxy/network-models.js.map +1 -1
- package/dist/proxy/network-models.test.js +56 -27
- package/dist/proxy/network-models.test.js.map +1 -1
- package/dist/proxy/tee-control.d.ts +14 -0
- package/dist/proxy/tee-control.d.ts.map +1 -0
- package/dist/proxy/tee-control.js +100 -0
- package/dist/proxy/tee-control.js.map +1 -0
- package/dist/proxy/tee-control.test.d.ts +2 -0
- package/dist/proxy/tee-control.test.d.ts.map +1 -0
- package/dist/proxy/tee-control.test.js +56 -0
- package/dist/proxy/tee-control.test.js.map +1 -0
- package/dist/proxy/tee-verification.d.ts +24 -0
- package/dist/proxy/tee-verification.d.ts.map +1 -0
- package/dist/proxy/tee-verification.js +128 -0
- package/dist/proxy/tee-verification.js.map +1 -0
- package/dist/proxy/tee-verification.test.d.ts +2 -0
- package/dist/proxy/tee-verification.test.d.ts.map +1 -0
- package/dist/proxy/tee-verification.test.js +241 -0
- package/dist/proxy/tee-verification.test.js.map +1 -0
- package/package.json +7 -6
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { teeControlFileName } from '@antseed/node/tee-status';
|
|
5
|
+
export class TeeControl {
|
|
6
|
+
sessionId;
|
|
7
|
+
token = randomBytes(32).toString('hex');
|
|
8
|
+
file;
|
|
9
|
+
ready = false;
|
|
10
|
+
lastCheck = 0;
|
|
11
|
+
constructor(sessionId) {
|
|
12
|
+
this.sessionId = sessionId;
|
|
13
|
+
}
|
|
14
|
+
async publish(directory, port) {
|
|
15
|
+
await mkdir(directory, { recursive: true });
|
|
16
|
+
this.file = join(directory, teeControlFileName(port));
|
|
17
|
+
const temporary = `${this.file}.${this.sessionId}.tmp`;
|
|
18
|
+
try {
|
|
19
|
+
await writeFile(temporary, JSON.stringify({ token: this.token, sessionId: this.sessionId, port }), { mode: 0o600, flag: 'wx' });
|
|
20
|
+
await chmod(temporary, 0o600);
|
|
21
|
+
await rename(temporary, this.file);
|
|
22
|
+
this.ready = true;
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await unlink(temporary).catch(() => { });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async close() {
|
|
29
|
+
this.ready = false;
|
|
30
|
+
if (!this.file)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const current = JSON.parse(await readFile(this.file, 'utf8'));
|
|
34
|
+
if (current.sessionId === this.sessionId)
|
|
35
|
+
await unlink(this.file);
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
}
|
|
39
|
+
async handle(req, res, method, path, snapshot, check) {
|
|
40
|
+
const reply = (status, body) => {
|
|
41
|
+
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
42
|
+
res.end(JSON.stringify(body));
|
|
43
|
+
};
|
|
44
|
+
const supplied = req.headers.authorization ?? '';
|
|
45
|
+
const expected = `Bearer ${this.token}`;
|
|
46
|
+
const remote = req.socket.remoteAddress;
|
|
47
|
+
if (!this.ready || req.headers.origin !== undefined
|
|
48
|
+
|| !['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(remote ?? '')
|
|
49
|
+
|| Buffer.byteLength(supplied) !== Buffer.byteLength(expected)
|
|
50
|
+
|| !timingSafeEqual(Buffer.from(supplied), Buffer.from(expected))) {
|
|
51
|
+
reply(403, { error: 'Local verification authorization required' });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (method === 'GET' && path === '/_antseed/verification') {
|
|
55
|
+
reply(200, snapshot());
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (method !== 'POST' || path !== '/_antseed/verification/check') {
|
|
59
|
+
reply(404, { error: 'Unknown verification endpoint' });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (Date.now() - this.lastCheck < 1000) {
|
|
63
|
+
reply(429, { error: 'Wait before checking another seller' });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.lastCheck = Date.now();
|
|
67
|
+
try {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
let size = 0;
|
|
70
|
+
const timer = setTimeout(() => req.destroy(), 5000);
|
|
71
|
+
try {
|
|
72
|
+
for await (const chunk of req) {
|
|
73
|
+
const bytes = Buffer.from(chunk);
|
|
74
|
+
size += bytes.length;
|
|
75
|
+
if (size > 1024) {
|
|
76
|
+
reply(413, { error: 'Request too large' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
chunks.push(bytes);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
86
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
87
|
+
|| Object.keys(body).length !== 1 || !('peerId' in body)
|
|
88
|
+
|| typeof body.peerId !== 'string' || !/^(?:0x)?[a-f0-9]{40}$/i.test(body.peerId)) {
|
|
89
|
+
reply(400, { error: 'A known seller peerId is required' });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await check(body.peerId);
|
|
93
|
+
reply(200, snapshot());
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
reply(400, { error: error instanceof Error ? error.message : 'Verification unavailable' });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=tee-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tee-control.js","sourceRoot":"","sources":["../../src/proxy/tee-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,EAAE,kBAAkB,EAAoB,MAAM,0BAA0B,CAAA;AAE/E,MAAM,OAAO,UAAU;IAMQ;IALZ,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAChD,IAAI,CAAoB;IACxB,KAAK,GAAG,KAAK,CAAA;IACb,SAAS,GAAG,CAAC,CAAA;IAErB,YAA6B,SAAiB;QAAjB,cAAS,GAAT,SAAS,CAAQ;IAAG,CAAC;IAElD,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,IAAY;QAC3C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAA;QACrD,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,MAAM,CAAA;QACtD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAC/H,MAAM,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YAC7B,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;YAClC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACnB,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACzC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAM;QACtB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAA2B,CAAA;YACvF,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;gBAAE,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnE,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAoB,EAAE,GAAmB,EAAE,MAAc,EAAE,IAAY,EAClF,QAA2B,EAAE,KAAwC;QACrE,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,IAAa,EAAQ,EAAE;YACpD,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAA;YAC1F,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;QAC/B,CAAC,CAAA;QACD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAA;QAChD,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAA;QACvC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS;eAC9C,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;eAChE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;eAC3D,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACpE,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC,CAAA;YAClE,OAAM;QACR,CAAC;QACD,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,wBAAwB,EAAE,CAAC;YAC1D,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAA;YACtB,OAAM;QACR,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,8BAA8B,EAAE,CAAC;YACjE,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAA;YACtD,OAAM;QACR,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,EAAE,CAAC;YACvC,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC,CAAA;YAC5D,OAAM;QACR,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAa,EAAE,CAAA;YAC3B,IAAI,IAAI,GAAG,CAAC,CAAA;YACZ,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAA;YACnD,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;oBAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAmB,CAAC,CAAA;oBAC9C,IAAI,IAAI,KAAK,CAAC,MAAM,CAAA;oBACpB,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC;wBAAC,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;wBAAC,OAAM;oBAAC,CAAC;oBACvE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACpB,CAAC;YACH,CAAC;oBAAS,CAAC;gBAAC,YAAY,CAAC,KAAK,CAAC,CAAA;YAAC,CAAC;YACjC,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;YACxE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;mBACvD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC;mBACrD,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpF,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC,CAAA;gBAC1D,OAAM;YACR,CAAC;YACD,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACxB,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,EAAE,CAAC,CAAA;QAC5F,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tee-control.test.d.ts","sourceRoot":"","sources":["../../src/proxy/tee-control.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { teeControlFileName } from '@antseed/node/tee-status';
|
|
8
|
+
import { TeeControl } from './tee-control.js';
|
|
9
|
+
test('verification control authenticates, rejects origins/targets, bounds requests and cleans credentials', async (context) => {
|
|
10
|
+
const directory = await mkdtemp(join(tmpdir(), 'tee-control-'));
|
|
11
|
+
const control = new TeeControl('session');
|
|
12
|
+
const checks = [];
|
|
13
|
+
const seller = 'a'.repeat(40);
|
|
14
|
+
const snapshot = { sessionId: 'session', verificationEnabled: true, evidence: [] };
|
|
15
|
+
const server = createServer((req, res) => {
|
|
16
|
+
void control.handle(req, res, req.method, req.url, () => snapshot, async (peerId) => {
|
|
17
|
+
if (peerId !== seller)
|
|
18
|
+
throw new Error('Unknown seller');
|
|
19
|
+
checks.push(peerId);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
23
|
+
const port = server.address().port;
|
|
24
|
+
context.after(async () => {
|
|
25
|
+
await control.close();
|
|
26
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
27
|
+
await rm(directory, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
await control.publish(directory, port);
|
|
30
|
+
const file = join(directory, teeControlFileName(port));
|
|
31
|
+
assert.equal((await stat(file)).mode & 0o777, 0o600);
|
|
32
|
+
const credential = JSON.parse(await readFile(file, 'utf8'));
|
|
33
|
+
const url = `http://127.0.0.1:${port}/_antseed/verification`;
|
|
34
|
+
const headers = { authorization: `Bearer ${credential.token}` };
|
|
35
|
+
assert.equal((await fetch(url)).status, 403);
|
|
36
|
+
assert.equal((await fetch(url, { headers: { authorization: 'Bearer invalid' } })).status, 403);
|
|
37
|
+
assert.equal((await fetch(url, { headers: { ...headers, origin: 'http://127.0.0.1.evil.test' } })).status, 403);
|
|
38
|
+
assert.equal((await fetch(url, { headers: { ...headers, origin: 'http://localhost:3000' } })).status, 403);
|
|
39
|
+
const status = await (await fetch(url, { headers })).json();
|
|
40
|
+
assert.deepEqual(status, snapshot);
|
|
41
|
+
assert.equal((await fetch(`${url}/resume`, { method: 'POST', headers })).status, 404);
|
|
42
|
+
assert.ok(!JSON.stringify(status).includes(credential.token));
|
|
43
|
+
assert.equal((await fetch(`${url}/check`, { method: 'POST', headers, body: JSON.stringify({ peerId: seller, url: 'https://evil.test' }) })).status, 400);
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 1010));
|
|
45
|
+
assert.equal((await fetch(`${url}/check`, { method: 'POST', headers, body: JSON.stringify({ peerId: 'b'.repeat(40) }) })).status, 400);
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, 1010));
|
|
47
|
+
assert.equal((await fetch(`${url}/check`, { method: 'POST', headers, body: JSON.stringify({ peerId: seller }) })).status, 200);
|
|
48
|
+
assert.deepEqual(checks, [seller]);
|
|
49
|
+
assert.equal((await fetch(`${url}/check`, { method: 'POST', headers, body: JSON.stringify({ peerId: seller }) })).status, 429);
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, 1010));
|
|
51
|
+
assert.equal((await fetch(`${url}/check`, { method: 'POST', headers, body: 'x'.repeat(2048) })).status, 413);
|
|
52
|
+
await control.close();
|
|
53
|
+
await assert.rejects(readFile(file));
|
|
54
|
+
assert.equal((await fetch(url, { headers })).status, 403);
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=tee-control.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tee-control.test.js","sourceRoot":"","sources":["../../src/proxy/tee-control.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TeeSnapshot } from '@antseed/node/tee-status';
|
|
2
|
+
import { type VerifierPolicy, type VerifyOutcome } from '../plugins/verifier.js';
|
|
3
|
+
type Peer = {
|
|
4
|
+
peerId: string;
|
|
5
|
+
capabilities?: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare class TeeVerification {
|
|
8
|
+
readonly policy: VerifierPolicy | undefined;
|
|
9
|
+
private readonly now;
|
|
10
|
+
readonly sessionId: `${string}-${string}-${string}-${string}-${string}`;
|
|
11
|
+
private readonly queries;
|
|
12
|
+
private closed;
|
|
13
|
+
private activeChecks;
|
|
14
|
+
constructor(policy: VerifierPolicy | undefined, now?: () => number);
|
|
15
|
+
verify(peer: Peer, policy: VerifierPolicy, run: () => Promise<VerifyOutcome>, force?: boolean): Promise<VerifyOutcome>;
|
|
16
|
+
verifyForDisplay(peer: Peer, run: () => Promise<VerifyOutcome>): Promise<VerifyOutcome>;
|
|
17
|
+
private cacheExpiry;
|
|
18
|
+
private verifyCached;
|
|
19
|
+
observePeers(peers: readonly Peer[]): void;
|
|
20
|
+
snapshot(peers: readonly Peer[]): TeeSnapshot;
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=tee-verification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tee-verification.d.ts","sourceRoot":"","sources":["../../src/proxy/tee-verification.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2E,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAEpI,OAAO,EAA8C,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE5H,KAAK,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAGvD,qBAAa,eAAe;IAQd,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS;IAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAP7E,QAAQ,CAAC,SAAS,sDAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAEtB;IACF,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAI;gBAEH,MAAM,EAAE,cAAc,GAAG,SAAS,EAAmB,GAAG,eAAW;IAElF,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,KAAK,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAIpH,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAK7F,OAAO,CAAC,WAAW;YAKL,YAAY;IAmD1B,YAAY,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,IAAI;IAO1C,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,WAAW;IA2B7C,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { QueryClient } from '@tanstack/query-core';
|
|
3
|
+
import { TEE_BADGE_MAX_AGE_MS, TEE_MAX_AGE_MS, TEE_VERIFIER_ID } from '@antseed/node/tee-status';
|
|
4
|
+
import { parseVerifierCapabilities } from '@antseed/node/verifier-capabilities';
|
|
5
|
+
import { selectVerifier, verifierSupportFingerprint } from '../plugins/verifier.js';
|
|
6
|
+
export class TeeVerification {
|
|
7
|
+
policy;
|
|
8
|
+
now;
|
|
9
|
+
sessionId = randomUUID();
|
|
10
|
+
queries = new QueryClient({
|
|
11
|
+
defaultOptions: { queries: { retry: false, networkMode: 'always', gcTime: Infinity, structuralSharing: false } },
|
|
12
|
+
});
|
|
13
|
+
closed = false;
|
|
14
|
+
activeChecks = 0;
|
|
15
|
+
constructor(policy, now = Date.now) {
|
|
16
|
+
this.policy = policy;
|
|
17
|
+
this.now = now;
|
|
18
|
+
}
|
|
19
|
+
async verify(peer, policy, run, force = false) {
|
|
20
|
+
return this.verifyCached(peer, policy, run, TEE_MAX_AGE_MS, force);
|
|
21
|
+
}
|
|
22
|
+
async verifyForDisplay(peer, run) {
|
|
23
|
+
return this.verifyCached(peer, { require: true, prefer: [TEE_VERIFIER_ID] }, run, TEE_BADGE_MAX_AGE_MS, false, true);
|
|
24
|
+
}
|
|
25
|
+
cacheExpiry(entry, maxAgeMs) {
|
|
26
|
+
return entry.outcome?.sellerNodeVerified && !entry.outcome.transient
|
|
27
|
+
? entry.checkedAt + maxAgeMs : entry.checkedAt + TEE_MAX_AGE_MS;
|
|
28
|
+
}
|
|
29
|
+
async verifyCached(peer, policy, run, maxAgeMs, force = false, requireSellerNode = false) {
|
|
30
|
+
if (this.closed)
|
|
31
|
+
return { ok: false, verified: false, reason: 'Buyer stopped', transient: true };
|
|
32
|
+
const chosen = selectVerifier(policy, parseVerifierCapabilities(peer.capabilities));
|
|
33
|
+
const fingerprint = verifierSupportFingerprint(peer.capabilities);
|
|
34
|
+
const queryKey = [peer.peerId, chosen ?? '', fingerprint];
|
|
35
|
+
const cache = this.queries.getQueryCache();
|
|
36
|
+
for (const query of cache.findAll({ queryKey: [peer.peerId] })) {
|
|
37
|
+
if (query.queryKey[2] !== fingerprint)
|
|
38
|
+
cache.remove(query);
|
|
39
|
+
}
|
|
40
|
+
const previous = cache.find({ queryKey, exact: true });
|
|
41
|
+
const pending = previous?.state.fetchStatus === 'fetching';
|
|
42
|
+
const allow = (outcome) => ({
|
|
43
|
+
...outcome, ok: !policy.require || (requireSellerNode ? outcome.sellerNodeVerified === true : outcome.verified),
|
|
44
|
+
});
|
|
45
|
+
const entry = previous?.state.data;
|
|
46
|
+
if (!pending && !force && entry?.outcome && !entry.outcome.transient && this.cacheExpiry(entry, maxAgeMs) > this.now())
|
|
47
|
+
return allow(entry.outcome);
|
|
48
|
+
if (!pending && this.activeChecks >= 8) {
|
|
49
|
+
return { ok: !policy.require, verified: false, transient: true, code: 'busy', reason: 'Verification busy; retry shortly' };
|
|
50
|
+
}
|
|
51
|
+
if (!previous && cache.getAll().length >= 512) {
|
|
52
|
+
const oldest = cache.getAll().find((query) => query.state.fetchStatus === 'idle');
|
|
53
|
+
if (oldest)
|
|
54
|
+
cache.remove(oldest);
|
|
55
|
+
}
|
|
56
|
+
const checkedAt = pending ? Number(previous.meta?.checkedAt) : this.now();
|
|
57
|
+
const options = {
|
|
58
|
+
queryKey, staleTime: 0, meta: { checkedAt },
|
|
59
|
+
queryFn: async () => {
|
|
60
|
+
this.activeChecks += 1;
|
|
61
|
+
try {
|
|
62
|
+
const { ok: _allowed, ...outcome } = await Promise.resolve().then(run);
|
|
63
|
+
return { checkedAt, outcome };
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return { checkedAt, outcome: {
|
|
67
|
+
verified: false, transient: true, reason: error instanceof Error ? error.message : 'Verification unavailable',
|
|
68
|
+
} };
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
this.activeChecks -= 1;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const resultPromise = this.queries.fetchQuery(options);
|
|
76
|
+
const query = cache.find({ queryKey, exact: true });
|
|
77
|
+
try {
|
|
78
|
+
const result = await resultPromise;
|
|
79
|
+
if (!this.closed && cache.find({ queryKey, exact: true }) === query && result.outcome)
|
|
80
|
+
return allow(result.outcome);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return { ok: !policy.require && !this.closed, verified: false, transient: true, reason: 'Verification session or seller capabilities changed' };
|
|
84
|
+
}
|
|
85
|
+
return { ok: !policy.require && !this.closed, verified: false, transient: true, reason: 'Verification session or seller capabilities changed' };
|
|
86
|
+
}
|
|
87
|
+
observePeers(peers) {
|
|
88
|
+
const fingerprints = new Map(peers.map((peer) => [peer.peerId, verifierSupportFingerprint(peer.capabilities)]));
|
|
89
|
+
for (const query of this.queries.getQueryCache().getAll()) {
|
|
90
|
+
if (fingerprints.get(String(query.queryKey[0])) !== query.queryKey[2])
|
|
91
|
+
this.queries.getQueryCache().remove(query);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
snapshot(peers) {
|
|
95
|
+
const evidence = [];
|
|
96
|
+
this.observePeers(peers);
|
|
97
|
+
for (const peer of peers) {
|
|
98
|
+
if (!parseVerifierCapabilities(peer.capabilities).supported.includes(TEE_VERIFIER_ID))
|
|
99
|
+
continue;
|
|
100
|
+
const fingerprint = verifierSupportFingerprint(peer.capabilities);
|
|
101
|
+
const query = this.queries.getQueryCache().find({ queryKey: [peer.peerId, TEE_VERIFIER_ID, fingerprint], exact: true });
|
|
102
|
+
if (!query)
|
|
103
|
+
continue;
|
|
104
|
+
const checking = query.state.fetchStatus === 'fetching';
|
|
105
|
+
const entry = checking ? { checkedAt: Number(query.meta?.checkedAt) } : query.state.data;
|
|
106
|
+
if (!entry)
|
|
107
|
+
continue;
|
|
108
|
+
evidence.push({
|
|
109
|
+
peerId: peer.peerId, verifierId: TEE_VERIFIER_ID, verifierVersion: entry.outcome?.version,
|
|
110
|
+
fingerprint, checkedAt: entry.checkedAt, expiresAt: checking ? 0 : this.cacheExpiry(entry, TEE_BADGE_MAX_AGE_MS),
|
|
111
|
+
sellerNodeVerified: entry.outcome?.sellerNodeVerified === true,
|
|
112
|
+
claims: entry.outcome?.claims ?? [], reason: entry.outcome?.reason,
|
|
113
|
+
checking,
|
|
114
|
+
unavailable: entry.outcome?.transient === true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
sessionId: this.sessionId,
|
|
119
|
+
verificationEnabled: Boolean(this.policy),
|
|
120
|
+
evidence,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
close() {
|
|
124
|
+
this.closed = true;
|
|
125
|
+
this.queries.clear();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=tee-verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tee-verification.js","sourceRoot":"","sources":["../../src/proxy/tee-verification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tee-verification.test.d.ts","sourceRoot":"","sources":["../../src/proxy/tee-verification.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { onlineManager } from '@tanstack/query-core';
|
|
4
|
+
import { TEE_BADGE_MAX_AGE_MS, TEE_MAX_AGE_MS } from '@antseed/node/tee-status';
|
|
5
|
+
import { TeeVerification } from './tee-verification.js';
|
|
6
|
+
const peer = { peerId: 'a'.repeat(40), capabilities: ['verifier.antseed-verifier'] };
|
|
7
|
+
const strict = { require: true, prefer: ['antseed-verifier'] };
|
|
8
|
+
const pass = { ok: true, verified: true, sellerNodeVerified: true, sdk: 'antseed-verifier' };
|
|
9
|
+
test('display checks reuse successful evidence for 24 hours without extending it on reads', async () => {
|
|
10
|
+
let now = 100;
|
|
11
|
+
let runs = 0;
|
|
12
|
+
const service = new TeeVerification(strict, () => now);
|
|
13
|
+
const run = async () => { runs += 1; return pass; };
|
|
14
|
+
await service.verifyForDisplay(peer, run);
|
|
15
|
+
const evidence = service.snapshot([peer]).evidence[0];
|
|
16
|
+
assert.equal(evidence.checkedAt, 100);
|
|
17
|
+
assert.equal(evidence.expiresAt, 100 + TEE_BADGE_MAX_AGE_MS);
|
|
18
|
+
now += TEE_MAX_AGE_MS;
|
|
19
|
+
await service.verifyForDisplay(peer, run);
|
|
20
|
+
now = evidence.expiresAt - 1;
|
|
21
|
+
await service.verifyForDisplay(peer, run);
|
|
22
|
+
assert.equal(runs, 1);
|
|
23
|
+
assert.deepEqual(service.snapshot([peer]).evidence[0], evidence);
|
|
24
|
+
now = evidence.expiresAt;
|
|
25
|
+
await service.verifyForDisplay(peer, run);
|
|
26
|
+
assert.equal(runs, 2);
|
|
27
|
+
});
|
|
28
|
+
test('a day-long badge never authorizes routing after the five-minute routing expiry', async () => {
|
|
29
|
+
let now = 100;
|
|
30
|
+
const service = new TeeVerification(strict, () => now);
|
|
31
|
+
let runs = 0;
|
|
32
|
+
await service.verifyForDisplay(peer, async () => pass);
|
|
33
|
+
now += TEE_MAX_AGE_MS;
|
|
34
|
+
assert.ok(service.snapshot([peer]).evidence[0].expiresAt > now);
|
|
35
|
+
const outcome = await service.verify(peer, strict, async () => {
|
|
36
|
+
runs += 1;
|
|
37
|
+
return { ok: false, verified: false, sellerNodeVerified: false };
|
|
38
|
+
});
|
|
39
|
+
assert.equal(runs, 1);
|
|
40
|
+
assert.equal(outcome.ok, false);
|
|
41
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.sellerNodeVerified, false);
|
|
42
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.expiresAt, now + TEE_MAX_AGE_MS);
|
|
43
|
+
});
|
|
44
|
+
test('routing and display share in-flight checks and revoke badges on unavailable results', async () => {
|
|
45
|
+
let now = 100;
|
|
46
|
+
const service = new TeeVerification(strict, () => now);
|
|
47
|
+
let finish;
|
|
48
|
+
const display = service.verifyForDisplay(peer, () => new Promise((resolve) => { finish = resolve; }));
|
|
49
|
+
const routed = service.verify(peer, strict, async () => { assert.fail('duplicate attestation'); });
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
finish(pass);
|
|
52
|
+
assert.ok((await display).sellerNodeVerified);
|
|
53
|
+
assert.ok((await routed).ok);
|
|
54
|
+
now += TEE_MAX_AGE_MS;
|
|
55
|
+
await service.verify(peer, strict, async () => ({ ok: false, verified: false, transient: true }));
|
|
56
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.sellerNodeVerified, false);
|
|
57
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.unavailable, true);
|
|
58
|
+
assert.ok((await service.verifyForDisplay(peer, async () => pass)).sellerNodeVerified);
|
|
59
|
+
});
|
|
60
|
+
test('display failures keep the short TTL and capability changes discard day-long successes', async () => {
|
|
61
|
+
let now = 100;
|
|
62
|
+
let runs = 0;
|
|
63
|
+
const service = new TeeVerification(strict, () => now);
|
|
64
|
+
await service.verifyForDisplay(peer, async () => ({ ok: false, verified: false }));
|
|
65
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.expiresAt, now + TEE_MAX_AGE_MS);
|
|
66
|
+
now += TEE_MAX_AGE_MS;
|
|
67
|
+
const run = async () => { runs += 1; return pass; };
|
|
68
|
+
await service.verifyForDisplay(peer, run);
|
|
69
|
+
const changed = { ...peer, capabilities: [...peer.capabilities, 'verifier.other'] };
|
|
70
|
+
service.observePeers([changed]);
|
|
71
|
+
assert.deepEqual(service.snapshot([changed]).evidence, []);
|
|
72
|
+
await service.verifyForDisplay(changed, run);
|
|
73
|
+
assert.equal(runs, 2);
|
|
74
|
+
service.close();
|
|
75
|
+
assert.deepEqual(service.snapshot([changed]).evidence, []);
|
|
76
|
+
assert.equal((await service.verifyForDisplay(changed, run)).ok, false);
|
|
77
|
+
});
|
|
78
|
+
test('evidence cache never reuses an optional routing allowance as required verification', async () => {
|
|
79
|
+
const service = new TeeVerification(strict);
|
|
80
|
+
const fail = { ok: true, verified: false, reason: 'Failed' };
|
|
81
|
+
assert.equal((await service.verify(peer, { require: false }, async () => fail)).ok, true);
|
|
82
|
+
assert.equal((await service.verify(peer, strict, async () => pass)).ok, false);
|
|
83
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.sellerNodeVerified, false);
|
|
84
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.unavailable, false);
|
|
85
|
+
});
|
|
86
|
+
test('badge claim requirements do not become a new CLI routing policy', async () => {
|
|
87
|
+
const service = new TeeVerification(strict);
|
|
88
|
+
const genericPass = { ok: true, verified: true, sellerNodeVerified: false };
|
|
89
|
+
const display = await service.verifyForDisplay(peer, async () => genericPass);
|
|
90
|
+
assert.equal(display.ok, false);
|
|
91
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.sellerNodeVerified, false);
|
|
92
|
+
const routed = await service.verify(peer, strict, async () => { assert.fail('should reuse generic verifier evidence'); });
|
|
93
|
+
assert.equal(routed.ok, true);
|
|
94
|
+
assert.deepEqual(service.snapshot([peer]), {
|
|
95
|
+
sessionId: service.sessionId, verificationEnabled: true, evidence: service.snapshot([peer]).evidence,
|
|
96
|
+
});
|
|
97
|
+
service.close();
|
|
98
|
+
});
|
|
99
|
+
test('expiry, forced failed recheck, transient retries and daemon restart revoke success', async () => {
|
|
100
|
+
let now = 100;
|
|
101
|
+
let runs = 0;
|
|
102
|
+
const service = new TeeVerification(strict, () => now);
|
|
103
|
+
const run = async () => { runs += 1; return pass; };
|
|
104
|
+
await service.verify(peer, strict, run);
|
|
105
|
+
await service.verify(peer, strict, run);
|
|
106
|
+
assert.equal(runs, 1);
|
|
107
|
+
now += TEE_MAX_AGE_MS;
|
|
108
|
+
await service.verify(peer, strict, run);
|
|
109
|
+
assert.equal(runs, 2);
|
|
110
|
+
await service.verify(peer, strict, async () => ({ ok: false, verified: false, transient: true }), true);
|
|
111
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.sellerNodeVerified, false);
|
|
112
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.unavailable, true);
|
|
113
|
+
await service.verify(peer, strict, run);
|
|
114
|
+
assert.equal(runs, 3);
|
|
115
|
+
const restarted = new TeeVerification(strict);
|
|
116
|
+
assert.notEqual(service.sessionId, restarted.sessionId);
|
|
117
|
+
assert.deepEqual(restarted.snapshot([peer]).evidence, []);
|
|
118
|
+
});
|
|
119
|
+
test('concurrent checks deduplicate; superseded advertisements cannot restore old evidence', async () => {
|
|
120
|
+
const service = new TeeVerification(strict);
|
|
121
|
+
let finish;
|
|
122
|
+
let runs = 0;
|
|
123
|
+
const run = () => { runs += 1; return new Promise((resolve) => { finish = resolve; }); };
|
|
124
|
+
const first = service.verify(peer, strict, run);
|
|
125
|
+
const second = service.verify(peer, strict, run, true);
|
|
126
|
+
await Promise.resolve();
|
|
127
|
+
assert.equal(runs, 1);
|
|
128
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.checking, true);
|
|
129
|
+
const changed = { ...peer, capabilities: [...peer.capabilities, 'verifier.other'] };
|
|
130
|
+
await service.verify(changed, strict, async () => ({ ok: false, verified: false }));
|
|
131
|
+
finish(pass);
|
|
132
|
+
assert.equal((await first).ok, false);
|
|
133
|
+
assert.equal((await second).ok, false);
|
|
134
|
+
assert.equal(service.snapshot([changed]).evidence[0]?.sellerNodeVerified, false);
|
|
135
|
+
assert.deepEqual(service.snapshot([{ ...peer, capabilities: [] }]).evidence, []);
|
|
136
|
+
assert.deepEqual(service.snapshot([peer]).evidence, []);
|
|
137
|
+
});
|
|
138
|
+
test('discovery updates revoke evidence even without a status reader', async () => {
|
|
139
|
+
const service = new TeeVerification(strict);
|
|
140
|
+
await service.verify(peer, strict, async () => pass);
|
|
141
|
+
service.observePeers([{ ...peer, capabilities: [] }]);
|
|
142
|
+
service.observePeers([peer]);
|
|
143
|
+
assert.deepEqual(service.snapshot([peer]).evidence, []);
|
|
144
|
+
});
|
|
145
|
+
test('stopped sessions and exhausted concurrency fail closed', async () => {
|
|
146
|
+
const service = new TeeVerification(strict);
|
|
147
|
+
const finishers = [];
|
|
148
|
+
const pending = Array.from({ length: 8 }, (_, index) => service.verify({ ...peer, peerId: String(index) }, strict, () => new Promise((resolve) => finishers.push(resolve))));
|
|
149
|
+
assert.equal((await service.verify(peer, strict, async () => pass)).ok, false);
|
|
150
|
+
await Promise.resolve();
|
|
151
|
+
service.close();
|
|
152
|
+
finishers.forEach((finish) => finish(pass));
|
|
153
|
+
assert.ok((await Promise.all(pending)).every((outcome) => !outcome.ok));
|
|
154
|
+
assert.equal((await service.verify(peer, strict, async () => pass)).ok, false);
|
|
155
|
+
});
|
|
156
|
+
test('verifier exceptions revoke success without automatic retries', async () => {
|
|
157
|
+
const service = new TeeVerification(strict);
|
|
158
|
+
await service.verifyForDisplay(peer, async () => pass);
|
|
159
|
+
let runs = 0;
|
|
160
|
+
const run = async () => { runs += 1; throw new Error('Verifier unavailable'); };
|
|
161
|
+
const result = await service.verify(peer, strict, run, true);
|
|
162
|
+
assert.equal(result.ok, false);
|
|
163
|
+
assert.equal(result.transient, true);
|
|
164
|
+
assert.equal(result.reason, 'Verifier unavailable');
|
|
165
|
+
assert.equal(runs, 1);
|
|
166
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.sellerNodeVerified, false);
|
|
167
|
+
await service.verifyForDisplay(peer, run);
|
|
168
|
+
assert.equal(runs, 2);
|
|
169
|
+
service.close();
|
|
170
|
+
});
|
|
171
|
+
test('refresh hides stale success and shares evidence, not caller routing policy', async () => {
|
|
172
|
+
const service = new TeeVerification(strict);
|
|
173
|
+
await service.verifyForDisplay(peer, async () => pass);
|
|
174
|
+
let finish;
|
|
175
|
+
const optional = service.verify(peer, { require: false }, () => new Promise((resolve) => { finish = resolve; }), true);
|
|
176
|
+
const required = service.verifyForDisplay(peer, async () => { assert.fail('duplicate attestation'); });
|
|
177
|
+
const evidence = service.snapshot([peer]).evidence[0];
|
|
178
|
+
assert.equal(evidence.checking, true);
|
|
179
|
+
assert.equal(evidence.sellerNodeVerified, false);
|
|
180
|
+
await Promise.resolve();
|
|
181
|
+
finish({ ok: true, verified: false, sellerNodeVerified: false });
|
|
182
|
+
assert.equal((await optional).ok, true);
|
|
183
|
+
assert.equal((await required).ok, false);
|
|
184
|
+
service.close();
|
|
185
|
+
});
|
|
186
|
+
test('invalidating pending queries releases waiters but retains the underlying concurrency limit', async () => {
|
|
187
|
+
const service = new TeeVerification(strict);
|
|
188
|
+
const finishers = [];
|
|
189
|
+
const pending = Array.from({ length: 8 }, (_, index) => service.verify({ ...peer, peerId: String(index) }, strict, () => new Promise((resolve) => finishers.push(resolve))));
|
|
190
|
+
await Promise.resolve();
|
|
191
|
+
service.observePeers([]);
|
|
192
|
+
assert.ok((await Promise.all(pending)).every((outcome) => !outcome.ok && outcome.transient));
|
|
193
|
+
const busy = await service.verifyForDisplay(peer, async () => { assert.fail('exceeded concurrency limit'); });
|
|
194
|
+
assert.equal(busy.code, 'busy');
|
|
195
|
+
finishers.forEach((finish) => finish(pass));
|
|
196
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
197
|
+
assert.deepEqual(service.snapshot([peer]).evidence, []);
|
|
198
|
+
assert.equal((await service.verifyForDisplay(peer, async () => pass)).ok, true);
|
|
199
|
+
service.close();
|
|
200
|
+
});
|
|
201
|
+
test('bounded query cache evicts settled entries, not pending checks', async () => {
|
|
202
|
+
const service = new TeeVerification(strict);
|
|
203
|
+
let finish;
|
|
204
|
+
const pending = service.verifyForDisplay(peer, () => new Promise((resolve) => { finish = resolve; }));
|
|
205
|
+
const peers = Array.from({ length: 512 }, (_, index) => ({ ...peer, peerId: String(index) }));
|
|
206
|
+
for (const seller of peers)
|
|
207
|
+
await service.verifyForDisplay(seller, async () => pass);
|
|
208
|
+
const snapshot = service.snapshot([peer, ...peers]);
|
|
209
|
+
assert.equal(snapshot.evidence.length, 512);
|
|
210
|
+
assert.equal(snapshot.evidence.find((entry) => entry.peerId === peer.peerId)?.checking, true);
|
|
211
|
+
assert.equal(snapshot.evidence.some((entry) => entry.peerId === peers[0].peerId), false);
|
|
212
|
+
const duplicate = service.verifyForDisplay(peer, async () => { assert.fail('evicted pending attestation'); });
|
|
213
|
+
finish(pass);
|
|
214
|
+
assert.equal((await pending).ok, true);
|
|
215
|
+
assert.equal((await duplicate).ok, true);
|
|
216
|
+
service.close();
|
|
217
|
+
});
|
|
218
|
+
test('expiry is measured from check start, not query completion time', async () => {
|
|
219
|
+
let now = 100;
|
|
220
|
+
const service = new TeeVerification(strict, () => now);
|
|
221
|
+
await service.verifyForDisplay(peer, async () => { now += TEE_MAX_AGE_MS; return pass; });
|
|
222
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.checkedAt, 100);
|
|
223
|
+
assert.equal(service.snapshot([peer]).evidence[0]?.expiresAt, 100 + TEE_BADGE_MAX_AGE_MS);
|
|
224
|
+
let runs = 0;
|
|
225
|
+
await service.verify(peer, strict, async () => { runs += 1; return pass; });
|
|
226
|
+
assert.equal(runs, 1);
|
|
227
|
+
service.close();
|
|
228
|
+
});
|
|
229
|
+
test('headless buyer checks do not pause for browser connectivity state', async () => {
|
|
230
|
+
const service = new TeeVerification(strict);
|
|
231
|
+
const wasOnline = onlineManager.isOnline();
|
|
232
|
+
onlineManager.setOnline(false);
|
|
233
|
+
try {
|
|
234
|
+
assert.equal((await service.verifyForDisplay(peer, async () => pass)).ok, true);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
service.close();
|
|
238
|
+
onlineManager.setOnline(wasOnline);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
//# sourceMappingURL=tee-verification.test.js.map
|