@antseed/antseed-verifier 0.1.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.
- package/LICENSE +674 -0
- package/README.md +86 -0
- package/dist/adapters/aci.d.ts +4 -0
- package/dist/adapters/aci.d.ts.map +1 -0
- package/dist/adapters/aci.js +42 -0
- package/dist/adapters/aci.js.map +1 -0
- package/dist/adapters/chutes.d.ts +4 -0
- package/dist/adapters/chutes.d.ts.map +1 -0
- package/dist/adapters/chutes.js +72 -0
- package/dist/adapters/chutes.js.map +1 -0
- package/dist/adapters/index.d.ts +26 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +19 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/antseed-node-types.d.ts +55 -0
- package/dist/antseed-node-types.d.ts.map +1 -0
- package/dist/antseed-node-types.js +8 -0
- package/dist/antseed-node-types.js.map +1 -0
- package/dist/capability.d.ts +50 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +15 -0
- package/dist/capability.js.map +1 -0
- package/dist/caps/gpu-nvidia.d.ts +66 -0
- package/dist/caps/gpu-nvidia.d.ts.map +1 -0
- package/dist/caps/gpu-nvidia.js +142 -0
- package/dist/caps/gpu-nvidia.js.map +1 -0
- package/dist/caps/index.d.ts +7 -0
- package/dist/caps/index.d.ts.map +1 -0
- package/dist/caps/index.js +21 -0
- package/dist/caps/index.js.map +1 -0
- package/dist/caps/measured-image.d.ts +14 -0
- package/dist/caps/measured-image.d.ts.map +1 -0
- package/dist/caps/measured-image.js +46 -0
- package/dist/caps/measured-image.js.map +1 -0
- package/dist/caps/provider-claims.d.ts +52 -0
- package/dist/caps/provider-claims.d.ts.map +1 -0
- package/dist/caps/provider-claims.js +195 -0
- package/dist/caps/provider-claims.js.map +1 -0
- package/dist/caps/seller-bound.d.ts +9 -0
- package/dist/caps/seller-bound.d.ts.map +1 -0
- package/dist/caps/seller-bound.js +104 -0
- package/dist/caps/seller-bound.js.map +1 -0
- package/dist/caps/tee-tdx.d.ts +107 -0
- package/dist/caps/tee-tdx.d.ts.map +1 -0
- package/dist/caps/tee-tdx.js +210 -0
- package/dist/caps/tee-tdx.js.map +1 -0
- package/dist/collect/configfs.d.ts +6 -0
- package/dist/collect/configfs.d.ts.map +1 -0
- package/dist/collect/configfs.js +38 -0
- package/dist/collect/configfs.js.map +1 -0
- package/dist/collect/dstack.d.ts +8 -0
- package/dist/collect/dstack.d.ts.map +1 -0
- package/dist/collect/dstack.js +79 -0
- package/dist/collect/dstack.js.map +1 -0
- package/dist/collect/http.d.ts +25 -0
- package/dist/collect/http.d.ts.map +1 -0
- package/dist/collect/http.js +83 -0
- package/dist/collect/http.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/nras.d.ts +81 -0
- package/dist/nras.d.ts.map +1 -0
- package/dist/nras.js +190 -0
- package/dist/nras.js.map +1 -0
- package/dist/prover.d.ts +5 -0
- package/dist/prover.d.ts.map +1 -0
- package/dist/prover.js +204 -0
- package/dist/prover.js.map +1 -0
- package/dist/report-data.d.ts +47 -0
- package/dist/report-data.d.ts.map +1 -0
- package/dist/report-data.js +111 -0
- package/dist/report-data.js.map +1 -0
- package/dist/shared.d.ts +89 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +158 -0
- package/dist/shared.js.map +1 -0
- package/dist/verifier.d.ts +21 -0
- package/dist/verifier.d.ts.map +1 -0
- package/dist/verifier.js +149 -0
- package/dist/verifier.js.map +1 -0
- package/package.json +42 -0
package/dist/prover.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import './caps/index.js'; // side-effect: register the capability menu
|
|
2
|
+
import { getCapability, listCapabilities } from './capability.js';
|
|
3
|
+
import { evmAddressFromPrivateKey, signerFromPrivateKey } from './caps/seller-bound.js';
|
|
4
|
+
import { NODE_TEE_CAP_ID, PROVIDER_TEE_CAP_ID, encodeTeeTdxEvidence, tdxConfigKey } from './caps/tee-tdx.js';
|
|
5
|
+
import { GPU_CAP_ID, gpuConfigKey } from './caps/gpu-nvidia.js';
|
|
6
|
+
import { claimsConfigKey } from './caps/provider-claims.js';
|
|
7
|
+
import { loadAdapter } from './adapters/index.js';
|
|
8
|
+
import { VERIFIER_ID, decodeAttestRequest, encodeAttestResponse, evidenceConfigKey, normalizePeerId, } from './shared.js';
|
|
9
|
+
/**
|
|
10
|
+
* Seller half — embedded prover (type:'prover'). For each requested capability the seller
|
|
11
|
+
* supports (has a collector for, and whose collection succeeds), it produces evidence and
|
|
12
|
+
* builds a per-cap evidence map. It omits unsupported caps. Config alone handles provider
|
|
13
|
+
* differences (no provider specifics live here).
|
|
14
|
+
*
|
|
15
|
+
* Env config (the seller offers each provider cap only when the evidence URL and its field are
|
|
16
|
+
* set, all off the same route):
|
|
17
|
+
* ANTSEED_TEE_PEER_ID this node's peer id (EVM address, no 0x) — required
|
|
18
|
+
* ANTSEED_VERIFIER_NODE_TEE seller-node-tee-genuine source: "configfs" (default), "dstack", or "http"
|
|
19
|
+
* ANTSEED_VERIFIER_DSTACK_SOCKET override the dstack guest-agent socket path (default /var/run/dstack.sock)
|
|
20
|
+
* ANTSEED_VERIFIER_PROVIDER_ADAPTER in-process provider adapter id (e.g. "chutes", "aci"); fetches provider evidence directly, no separate shim
|
|
21
|
+
* ANTSEED_VERIFIER_PROVIDER_EVIDENCE_URL provider evidence route ({nonce} hex placeholder); offers seller-provider-tee-genuine via http (alternative to an adapter)
|
|
22
|
+
* ANTSEED_VERIFIER_PROVIDER_TEE_FIELD JSON field that holds the base64 provider quote (default "quote")
|
|
23
|
+
* ANTSEED_VERIFIER_PROVIDER_GPU_FIELD JSON field that holds the provider's GPU CC evidence; offers seller-provider-gpu-cc
|
|
24
|
+
* ANTSEED_VERIFIER_PROVIDER_CLAIMS_FIELD JSON field that holds the provider's base64 claims document; offers seller-provider-claims
|
|
25
|
+
* ANTSEED_VERIFIER_PROVIDER_BINDING_SCHEME frozen report_data scheme the provider quote uses (e.g. "nonce-pubkey-sha256-v1"); buyer then verifies round binding
|
|
26
|
+
* ANTSEED_VERIFIER_PROVIDER_BINDING_PUBKEY_FIELD JSON field that holds the provider's base64 E2E pubkey (the ingredient that scheme binds)
|
|
27
|
+
* ANTSEED_VERIFIER_SIGNING_KEY seller identity private key (hex) for seller-bound
|
|
28
|
+
*/
|
|
29
|
+
const PEER_ID_KEY = 'ANTSEED_TEE_PEER_ID';
|
|
30
|
+
const SIGNING_KEY = 'ANTSEED_VERIFIER_SIGNING_KEY';
|
|
31
|
+
const PROVIDER_ADAPTER = 'ANTSEED_VERIFIER_PROVIDER_ADAPTER';
|
|
32
|
+
const NODE_TEE_SOURCE = 'ANTSEED_VERIFIER_NODE_TEE';
|
|
33
|
+
const DSTACK_SOCKET = 'ANTSEED_VERIFIER_DSTACK_SOCKET';
|
|
34
|
+
const PROVIDER_EVIDENCE_URL = 'ANTSEED_VERIFIER_PROVIDER_EVIDENCE_URL';
|
|
35
|
+
const PROVIDER_TEE_FIELD = 'ANTSEED_VERIFIER_PROVIDER_TEE_FIELD';
|
|
36
|
+
const PROVIDER_GPU_FIELD = 'ANTSEED_VERIFIER_PROVIDER_GPU_FIELD';
|
|
37
|
+
const PROVIDER_CLAIMS_FIELD = 'ANTSEED_VERIFIER_PROVIDER_CLAIMS_FIELD';
|
|
38
|
+
const PROVIDER_BINDING_SCHEME = 'ANTSEED_VERIFIER_PROVIDER_BINDING_SCHEME';
|
|
39
|
+
const PROVIDER_BINDING_PUBKEY_FIELD = 'ANTSEED_VERIFIER_PROVIDER_BINDING_PUBKEY_FIELD';
|
|
40
|
+
function msg(err) {
|
|
41
|
+
return err instanceof Error ? err.message : String(err);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Provider adapter path: when ANTSEED_VERIFIER_PROVIDER_ADAPTER names an adapter, fetch the
|
|
45
|
+
* provider's evidence in-process (the adapter handles that provider's auth and response shape)
|
|
46
|
+
* and map it onto the provider caps, instead of the generic http evidence route. The evidence
|
|
47
|
+
* is also exposed as evidence:<capId> config so seller-bound binds it. A failure omits the
|
|
48
|
+
* provider caps, like an unconfigured http route.
|
|
49
|
+
*/
|
|
50
|
+
async function collectViaAdapter(nonce, caps, evidence, config) {
|
|
51
|
+
const id = process.env[PROVIDER_ADAPTER]?.trim();
|
|
52
|
+
if (!id)
|
|
53
|
+
return;
|
|
54
|
+
const put = (capId, bytes) => {
|
|
55
|
+
evidence[capId] = bytes;
|
|
56
|
+
config[evidenceConfigKey(capId)] = Buffer.from(bytes).toString('base64');
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
const adapter = await loadAdapter(id);
|
|
60
|
+
const ev = await adapter.fetchEvidence(nonce, process.env);
|
|
61
|
+
if (caps.includes(PROVIDER_TEE_CAP_ID)) {
|
|
62
|
+
const binding = ev.bindingScheme ? { scheme: ev.bindingScheme, ingredients: ev.ingredients ?? {} } : undefined;
|
|
63
|
+
put(PROVIDER_TEE_CAP_ID, encodeTeeTdxEvidence(ev.quote, undefined, binding));
|
|
64
|
+
}
|
|
65
|
+
if (ev.gpuEvidence && caps.includes(GPU_CAP_ID)) {
|
|
66
|
+
put(GPU_CAP_ID, new TextEncoder().encode(JSON.stringify(ev.gpuEvidence)));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
process.stderr.write(`[${VERIFIER_ID}] provider adapter "${id}" not offered: ${msg(err)}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function json(statusCode, body) {
|
|
74
|
+
return {
|
|
75
|
+
statusCode,
|
|
76
|
+
headers: { 'content-type': 'application/json' },
|
|
77
|
+
body: new TextEncoder().encode(JSON.stringify(body)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Generic, provider-neutral collector config, namespaced per TDX cap so the node cap and
|
|
82
|
+
* the provider cap never collide. The node cap defaults to a local configfs quote; the
|
|
83
|
+
* seller offers the provider cap (via http) only when a provider evidence URL is configured.
|
|
84
|
+
*/
|
|
85
|
+
function baseConfig() {
|
|
86
|
+
const cfg = {};
|
|
87
|
+
// seller-node-tee-genuine: mint the quote locally; default to configfs when unset.
|
|
88
|
+
cfg[tdxConfigKey(NODE_TEE_CAP_ID, 'source')] = process.env[NODE_TEE_SOURCE]?.trim() || 'configfs';
|
|
89
|
+
// Optional dstack guest-agent socket override (Phala CVMs); default locations are used when unset.
|
|
90
|
+
const dstackSocket = process.env[DSTACK_SOCKET]?.trim();
|
|
91
|
+
if (dstackSocket)
|
|
92
|
+
cfg[tdxConfigKey(NODE_TEE_CAP_ID, 'dstack.socket')] = dstackSocket;
|
|
93
|
+
// seller-provider-tee-genuine: offered only when a provider evidence route is configured.
|
|
94
|
+
const provUrl = process.env[PROVIDER_EVIDENCE_URL]?.trim();
|
|
95
|
+
if (provUrl) {
|
|
96
|
+
cfg[tdxConfigKey(PROVIDER_TEE_CAP_ID, 'source')] = 'http';
|
|
97
|
+
cfg[tdxConfigKey(PROVIDER_TEE_CAP_ID, 'url')] = provUrl;
|
|
98
|
+
cfg[tdxConfigKey(PROVIDER_TEE_CAP_ID, 'field')] = process.env[PROVIDER_TEE_FIELD]?.trim() || 'quote';
|
|
99
|
+
// Optional: declare the provider's frozen report_data scheme so the buyer verifies the
|
|
100
|
+
// provider quote is bound to this round and instance (e.g. Chutes' nonce-pubkey-sha256-v1).
|
|
101
|
+
const bindingScheme = process.env[PROVIDER_BINDING_SCHEME]?.trim();
|
|
102
|
+
if (bindingScheme) {
|
|
103
|
+
cfg[tdxConfigKey(PROVIDER_TEE_CAP_ID, 'binding.scheme')] = bindingScheme;
|
|
104
|
+
const pubkeyField = process.env[PROVIDER_BINDING_PUBKEY_FIELD]?.trim();
|
|
105
|
+
if (pubkeyField)
|
|
106
|
+
cfg[tdxConfigKey(PROVIDER_TEE_CAP_ID, 'binding.pubkeyField')] = pubkeyField;
|
|
107
|
+
}
|
|
108
|
+
// seller-provider-gpu-cc: same evidence route, offered only when a GPU field is named.
|
|
109
|
+
const gpuField = process.env[PROVIDER_GPU_FIELD]?.trim();
|
|
110
|
+
if (gpuField) {
|
|
111
|
+
cfg[gpuConfigKey('url')] = provUrl;
|
|
112
|
+
cfg[gpuConfigKey('field')] = gpuField;
|
|
113
|
+
}
|
|
114
|
+
// seller-provider-claims: same evidence route, offered only when a claims field is named.
|
|
115
|
+
const claimsField = process.env[PROVIDER_CLAIMS_FIELD]?.trim();
|
|
116
|
+
if (claimsField) {
|
|
117
|
+
cfg[claimsConfigKey('url')] = provUrl;
|
|
118
|
+
cfg[claimsConfigKey('field')] = claimsField;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return cfg;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A signer for seller-bound, built from the seller's private key. It returns undefined when
|
|
125
|
+
* no key is set (cap not offered) or the key's address does not match this node's peer id
|
|
126
|
+
* (a mismatch would only fail on the buyer, so the prover disables it and logs instead).
|
|
127
|
+
*/
|
|
128
|
+
function buildSigner(peerId) {
|
|
129
|
+
const key = process.env[SIGNING_KEY]?.trim();
|
|
130
|
+
if (!key)
|
|
131
|
+
return undefined;
|
|
132
|
+
try {
|
|
133
|
+
if (evmAddressFromPrivateKey(key) !== peerId) {
|
|
134
|
+
process.stderr.write(`[${VERIFIER_ID}] ${SIGNING_KEY} address != ${PEER_ID_KEY}; seller-bound disabled\n`);
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return signerFromPrivateKey(key);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
process.stderr.write(`[${VERIFIER_ID}] invalid ${SIGNING_KEY}; seller-bound disabled: ${msg(err)}\n`);
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const prover = {
|
|
145
|
+
type: 'prover',
|
|
146
|
+
name: VERIFIER_ID,
|
|
147
|
+
displayName: 'TEE attestation prover (capability-based)',
|
|
148
|
+
version: '0.1.0',
|
|
149
|
+
description: 'Produces per-capability seller attestation evidence (Intel TDX quote, seller-identity signature) for buyers to verify.',
|
|
150
|
+
async prove(req) {
|
|
151
|
+
const rawPeer = process.env[PEER_ID_KEY]?.trim();
|
|
152
|
+
if (!rawPeer) {
|
|
153
|
+
return json(500, {
|
|
154
|
+
error: { message: `${PEER_ID_KEY} is not set; the prover must know this node's peer id (EVM address)`, type: 'tee_error' },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
let peerId;
|
|
158
|
+
try {
|
|
159
|
+
peerId = normalizePeerId(rawPeer);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
return json(500, { error: { message: msg(err), type: 'tee_error' } });
|
|
163
|
+
}
|
|
164
|
+
let nonce;
|
|
165
|
+
let caps;
|
|
166
|
+
try {
|
|
167
|
+
;
|
|
168
|
+
({ nonce, caps } = decodeAttestRequest(req.body ?? new Uint8Array()));
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
return json(400, { error: { message: msg(err), type: 'invalid_request_error' } });
|
|
172
|
+
}
|
|
173
|
+
const config = baseConfig();
|
|
174
|
+
const sign = buildSigner(peerId);
|
|
175
|
+
const evidence = {};
|
|
176
|
+
// A selected provider adapter pre-fills the provider caps in-process, before the collect loop.
|
|
177
|
+
await collectViaAdapter(nonce, caps, evidence, config);
|
|
178
|
+
// Registry order guarantees a dependency (tee-tdx) is collected before its dependents.
|
|
179
|
+
for (const cap of listCapabilities()) {
|
|
180
|
+
if (!caps.includes(cap.id) || !cap.collect)
|
|
181
|
+
continue;
|
|
182
|
+
if (evidence[cap.id])
|
|
183
|
+
continue; // already provided by the adapter
|
|
184
|
+
try {
|
|
185
|
+
const bytes = await cap.collect({ nonce, peerId, config, sign });
|
|
186
|
+
evidence[cap.id] = bytes;
|
|
187
|
+
// Expose to later dependent collectors (seller-bound binds to the tee-tdx evidence).
|
|
188
|
+
config[evidenceConfigKey(cap.id)] = Buffer.from(bytes).toString('base64');
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
// Unsupported by this seller's infra (or a dependency was missing) — omit the cap.
|
|
192
|
+
process.stderr.write(`[${VERIFIER_ID}] capability "${cap.id}" not offered: ${msg(err)}\n`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// One-line collection summary, so the seller sees which caps produced evidence this round.
|
|
196
|
+
const collected = Object.keys(evidence);
|
|
197
|
+
const skipped = caps.filter((id) => !evidence[id] && getCapability(id)?.collect);
|
|
198
|
+
process.stderr.write(`[${VERIFIER_ID}] attest: collected [${collected.join(', ')}]`
|
|
199
|
+
+ (skipped.length ? `; skipped [${skipped.join(', ')}]` : '') + '\n');
|
|
200
|
+
return { statusCode: 200, headers: { 'content-type': 'application/json' }, body: encodeAttestResponse(evidence) };
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
export default prover;
|
|
204
|
+
//# sourceMappingURL=prover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prover.js","sourceRoot":"","sources":["../src/prover.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compositional report_data schemes. A TDX quote has one 64-byte report_data; how a
|
|
3
|
+
* provider commits this round's freshness (and any TEE-bound key or payload) into it varies by
|
|
4
|
+
* hardware stack. The SDK verifies against a closed, version-pinned registry:
|
|
5
|
+
*
|
|
6
|
+
* - antseed-rd-v1 the canonical, compositional scheme. The nonce is always bound;
|
|
7
|
+
* optional fields (peerId, e2ePubkey) are included only when their
|
|
8
|
+
* ingredient is present, in canonical tag order, each tagged and
|
|
9
|
+
* length-prefixed. One rule covers every combination — the node
|
|
10
|
+
* cap is the {peerId} case. A new field takes a new tag and version bump.
|
|
11
|
+
* - nonce-pubkey-sha256-v1 a foreign construction (e.g. Chutes) the SDK does not control:
|
|
12
|
+
* report_data[0:32] = SHA-256(nonce_hex ‖ e2ePubkey_b64). The SDK
|
|
13
|
+
* replicates its exact bytes only to verify a quote it minted.
|
|
14
|
+
*
|
|
15
|
+
* The same build() runs on the prover (to mint report_data) and the buyer (to recompute and
|
|
16
|
+
* compare). Determinism is the safety property, and because the nonce is always bound, a
|
|
17
|
+
* mis-declared or downgraded field set can only fail, never falsely pass. This registry is
|
|
18
|
+
* frozen per SDK version; the trust registry pins the SDK, so prover and verifier agree.
|
|
19
|
+
*/
|
|
20
|
+
export interface BindingIngredients {
|
|
21
|
+
/** 40-hex EVM address — identity binding (the node cap's ingredient). */
|
|
22
|
+
peerId?: string;
|
|
23
|
+
/** base64 TEE-generated public key — E2E or signing providers. */
|
|
24
|
+
e2ePubkey?: string;
|
|
25
|
+
/** ACI workload keyset digest, "sha256:<64 hex>" — the ACI ingredient. */
|
|
26
|
+
keysetDigest?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ReportDataScheme {
|
|
29
|
+
id: string;
|
|
30
|
+
/** Leading report_data bytes this scheme commits (the rest may be provider-defined). */
|
|
31
|
+
compareLen: 32 | 64;
|
|
32
|
+
/** Build the report_data commitment (prover mints it; buyer recomputes it). */
|
|
33
|
+
build(nonce: Uint8Array, ing: BindingIngredients): Uint8Array;
|
|
34
|
+
/** The nonce a downstream GPU (NRAS) verifier must bind to under this scheme. */
|
|
35
|
+
gpuNonce(nonce: Uint8Array, ing: BindingIngredients): Uint8Array;
|
|
36
|
+
}
|
|
37
|
+
export declare const antseedRdV1: ReportDataScheme;
|
|
38
|
+
export declare const noncePubkeySha256V1: ReportDataScheme;
|
|
39
|
+
export declare const aciKeysetV1: ReportDataScheme;
|
|
40
|
+
export declare const REPORT_DATA_SCHEMES: Record<string, ReportDataScheme>;
|
|
41
|
+
export declare function getReportDataScheme(id: string): ReportDataScheme | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Verify a quote's report_data against a frozen scheme. It returns null on a match, else a reason.
|
|
44
|
+
* It compares only the scheme's `compareLen` leading bytes (a provider may use the rest).
|
|
45
|
+
*/
|
|
46
|
+
export declare function verifyReportData(schemeId: string, reportData: Uint8Array, nonce: Uint8Array, ing: BindingIngredients): string | null;
|
|
47
|
+
//# sourceMappingURL=report-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-data.d.ts","sourceRoot":"","sources":["../src/report-data.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,wFAAwF;IACxF,UAAU,EAAE,EAAE,GAAG,EAAE,CAAA;IACnB,+EAA+E;IAC/E,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,kBAAkB,GAAG,UAAU,CAAA;IAC7D,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,kBAAkB,GAAG,UAAU,CAAA;CACjE;AAqBD,eAAO,MAAM,WAAW,EAAE,gBAoBzB,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,gBAmBjC,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,gBAmBzB,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAIhE,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,kBAAkB,GACtB,MAAM,GAAG,IAAI,CAcf"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const RD_DOMAIN = 'antseed-rd-v1';
|
|
3
|
+
/** Prefix a value with its 4-byte big-endian length, so concatenated values stay unambiguous. */
|
|
4
|
+
function lenPrefixed(b) {
|
|
5
|
+
const len = Buffer.alloc(4);
|
|
6
|
+
len.writeUInt32BE(b.length, 0);
|
|
7
|
+
return Buffer.concat([len, b]);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Frozen field table for antseed-rd-v1. `tag` is the canonical ordering key and a per-field
|
|
11
|
+
* domain separator. To add a field, use a new tag and an SDK version bump; never reorder or
|
|
12
|
+
* reuse a tag.
|
|
13
|
+
*/
|
|
14
|
+
const FIELDS = [
|
|
15
|
+
{ tag: 0x01, get: (i) => (i.peerId ? Buffer.from(i.peerId.toLowerCase(), 'hex') : null) },
|
|
16
|
+
{ tag: 0x02, get: (i) => (i.e2ePubkey ? Buffer.from(i.e2ePubkey, 'base64') : null) },
|
|
17
|
+
];
|
|
18
|
+
export const antseedRdV1 = {
|
|
19
|
+
id: RD_DOMAIN,
|
|
20
|
+
compareLen: 64,
|
|
21
|
+
build(nonce, ing) {
|
|
22
|
+
// Unambiguous TLV: fixed domain tag, then every value length-prefixed (including the nonce)
|
|
23
|
+
// in fixed ascending field order, so no two distinct inputs share a preimage.
|
|
24
|
+
const h = createHash('sha512');
|
|
25
|
+
h.update(Buffer.from(RD_DOMAIN, 'utf8'));
|
|
26
|
+
h.update(lenPrefixed(Buffer.from(nonce)));
|
|
27
|
+
for (const f of FIELDS) {
|
|
28
|
+
const b = f.get(ing);
|
|
29
|
+
if (!b)
|
|
30
|
+
continue;
|
|
31
|
+
h.update(Buffer.from([f.tag]));
|
|
32
|
+
h.update(lenPrefixed(b));
|
|
33
|
+
}
|
|
34
|
+
return new Uint8Array(h.digest());
|
|
35
|
+
},
|
|
36
|
+
gpuNonce(nonce) {
|
|
37
|
+
return nonce; // the GPU evidence binds the raw round nonce
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export const noncePubkeySha256V1 = {
|
|
41
|
+
id: 'nonce-pubkey-sha256-v1',
|
|
42
|
+
compareLen: 32,
|
|
43
|
+
build(nonce, ing) {
|
|
44
|
+
if (!ing.e2ePubkey)
|
|
45
|
+
throw new Error(`${this.id} requires e2ePubkey`);
|
|
46
|
+
const commit = createHash('sha256')
|
|
47
|
+
.update(Buffer.from(nonce).toString('hex'), 'utf8') // nonce as its hex string (their choice)
|
|
48
|
+
.update(ing.e2ePubkey, 'utf8') // pubkey as its base64 string (their choice)
|
|
49
|
+
.digest();
|
|
50
|
+
const rd = Buffer.alloc(64); // [0:32] = commitment, [32:64] provider-defined
|
|
51
|
+
commit.copy(rd, 0);
|
|
52
|
+
return new Uint8Array(rd);
|
|
53
|
+
},
|
|
54
|
+
gpuNonce(nonce, ing) {
|
|
55
|
+
// The GPU evidence's NRAS eat_nonce is this derived value, SHA-256(nonce_hex ‖ pubkey_b64) —
|
|
56
|
+
// the same commitment report_data[0:32] carries. (NRAS returns overall-att-result true only
|
|
57
|
+
// for this value, not the raw nonce.)
|
|
58
|
+
return this.build(nonce, ing).subarray(0, 32);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export const aciKeysetV1 = {
|
|
62
|
+
id: 'aci-keyset-v1',
|
|
63
|
+
compareLen: 32,
|
|
64
|
+
build(nonce, ing) {
|
|
65
|
+
// ACI report_data (private-ai-gateway digest.ts): SHA-256 of a fixed statement over the
|
|
66
|
+
// workload keyset digest and the nonce, which the TEE places zero-padded to 64 in the quote.
|
|
67
|
+
const digest = ing.keysetDigest;
|
|
68
|
+
if (!digest || !/^sha256:[0-9a-f]{64}$/.test(digest)) {
|
|
69
|
+
throw new Error(`${this.id} requires keysetDigest "sha256:<64 hex>"`);
|
|
70
|
+
}
|
|
71
|
+
if (nonce.length !== 32)
|
|
72
|
+
throw new Error(`${this.id} requires a 32-byte nonce`);
|
|
73
|
+
const statement = `{"keyset_digest":"${digest}","nonce":"${Buffer.from(nonce).toString('hex')}","purpose":"aci.report_data.v1"}`;
|
|
74
|
+
const rd = Buffer.alloc(64);
|
|
75
|
+
createHash('sha256').update(statement, 'utf8').digest().copy(rd, 0);
|
|
76
|
+
return new Uint8Array(rd);
|
|
77
|
+
},
|
|
78
|
+
gpuNonce(nonce) {
|
|
79
|
+
return nonce; // ACI treats GPU CC as supplemental; bind the raw round nonce
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export const REPORT_DATA_SCHEMES = {
|
|
83
|
+
[antseedRdV1.id]: antseedRdV1,
|
|
84
|
+
[noncePubkeySha256V1.id]: noncePubkeySha256V1,
|
|
85
|
+
[aciKeysetV1.id]: aciKeysetV1,
|
|
86
|
+
};
|
|
87
|
+
export function getReportDataScheme(id) {
|
|
88
|
+
return REPORT_DATA_SCHEMES[id];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Verify a quote's report_data against a frozen scheme. It returns null on a match, else a reason.
|
|
92
|
+
* It compares only the scheme's `compareLen` leading bytes (a provider may use the rest).
|
|
93
|
+
*/
|
|
94
|
+
export function verifyReportData(schemeId, reportData, nonce, ing) {
|
|
95
|
+
const scheme = getReportDataScheme(schemeId);
|
|
96
|
+
if (!scheme)
|
|
97
|
+
return `unknown report_data scheme "${schemeId}" (not in this SDK's frozen registry)`;
|
|
98
|
+
let want;
|
|
99
|
+
try {
|
|
100
|
+
want = scheme.build(nonce, ing);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return `report_data scheme "${schemeId}": ${err instanceof Error ? err.message : String(err)}`;
|
|
104
|
+
}
|
|
105
|
+
const n = scheme.compareLen;
|
|
106
|
+
if (reportData.length < n || !Buffer.from(reportData.subarray(0, n)).equals(Buffer.from(want.subarray(0, n)))) {
|
|
107
|
+
return `report_data does not match scheme "${schemeId}" for this nonce`;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=report-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-data.js","sourceRoot":"","sources":["../src/report-data.ts"],"names":[],"mappings":"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"}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants and wire codecs used by both halves of the SDK (buyer verifier
|
|
3
|
+
* and seller prover). The single source of truth for the attestation request and
|
|
4
|
+
* response shape and the seller-bound preimage, so both sides agree byte-for-byte.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The advertised and pinned verifier id (also the seller-side prover's name).
|
|
8
|
+
* Hyphenated — no '@' or '/' — so capability strings satisfy PEER_CAPABILITY_PATTERN.
|
|
9
|
+
*/
|
|
10
|
+
export declare const VERIFIER_ID = "antseed-verifier";
|
|
11
|
+
/**
|
|
12
|
+
* A claim id is the verifier id namespaced by the capability id, e.g.
|
|
13
|
+
* 'antseed-verifier:seller-node-tee-genuine'. One claim per capability.
|
|
14
|
+
*/
|
|
15
|
+
export declare function claimId(capId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Reserved control-plane path the buyer's verifier reaches the seller's prover at.
|
|
18
|
+
* The prefix must match @antseed/node's ANTSEED_ATTEST_PATH; the seller dispatches it
|
|
19
|
+
* (before provider or service matching) to the prover whose name === VERIFIER_ID.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ATTEST_PATH = "/_antseed/attest/antseed-verifier";
|
|
22
|
+
/** Freshness nonce length, in bytes. */
|
|
23
|
+
export declare const NONCE_BYTES = 32;
|
|
24
|
+
/**
|
|
25
|
+
* A PeerId is the canonical AntSeed identifier: 40 lowercase hex chars (the peer's
|
|
26
|
+
* EVM address, no 0x prefix). Lowercase and validate it, so bindings are symmetric on
|
|
27
|
+
* both sides regardless of input casing.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizePeerId(peerId: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* The seller node's report_data binding: the {peerId} instance of the compositional scheme
|
|
32
|
+
* `antseed-rd-v1` (see report-data.ts). The configfs collector mints exactly this, so the
|
|
33
|
+
* buyer's node cap recomputes and enforces the same bytes.
|
|
34
|
+
*/
|
|
35
|
+
export declare function computeReportData(nonce: Uint8Array, peerId: string): Buffer;
|
|
36
|
+
/**
|
|
37
|
+
* Canonical digest over the whole attestation bundle — every cap's evidence entry
|
|
38
|
+
* except seller-bound's own signature (it cannot sign over itself). A sort by cap id
|
|
39
|
+
* makes both halves agree regardless of map iteration order. Length-prefixing both the id
|
|
40
|
+
* and the bytes (len(id) ‖ id ‖ len(bytes) ‖ bytes) keeps the concatenation unambiguous, so
|
|
41
|
+
* no cap can shift bytes across an entry boundary. One seller signature therefore covers
|
|
42
|
+
* the node quote and the provider quote (and any later caps) together.
|
|
43
|
+
*/
|
|
44
|
+
export declare function bundleDigest(evidence: Record<string, Uint8Array>): Uint8Array;
|
|
45
|
+
/**
|
|
46
|
+
* seller-bound preimage — the 32-byte digest the seller signs with its AntSeed
|
|
47
|
+
* identity key:
|
|
48
|
+
*
|
|
49
|
+
* keccak256( nonce(32) || bundleDigest(32) || utf8(peerId) )
|
|
50
|
+
*
|
|
51
|
+
* It binds the seller's identity signature to the entire bundle (via bundleDigest), this
|
|
52
|
+
* nonce (freshness), and this peer id (identity), so nobody can pair a seller's signature
|
|
53
|
+
* with different evidence or replay it. Defined here so both halves compute identical bytes.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sellerBoundPreimage(nonce: Uint8Array, digest: Uint8Array, peerId: string): Uint8Array;
|
|
56
|
+
/**
|
|
57
|
+
* Config key under which a prover exposes an already-collected capability's evidence
|
|
58
|
+
* (base64) to later collectors in the same request. It lets a dependent capability
|
|
59
|
+
* (seller-bound) read the whole bundle it must bind to, without provider specifics.
|
|
60
|
+
*/
|
|
61
|
+
export declare function evidenceConfigKey(capId: string): string;
|
|
62
|
+
/** Inverse of evidenceConfigKey: the cap id an "evidence:<capId>" config key carries, else undefined. */
|
|
63
|
+
export declare function parseEvidenceConfigKey(key: string): string | undefined;
|
|
64
|
+
/** Body the buyer sends to the prover: a fresh nonce and the menu of caps it wants. */
|
|
65
|
+
export interface AttestRequestBody {
|
|
66
|
+
/** base64 of the 32-byte freshness nonce. */
|
|
67
|
+
nonce: string;
|
|
68
|
+
/** capability ids the buyer is asking the seller to attest. */
|
|
69
|
+
caps: string[];
|
|
70
|
+
}
|
|
71
|
+
/** Body the seller's prover returns: per-capability evidence (a cap may have none). */
|
|
72
|
+
export interface AttestResponseBody {
|
|
73
|
+
/** capId -> base64 of that capability's opaque evidence bytes. */
|
|
74
|
+
evidence: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
export declare function encodeAttestRequest(nonce: Uint8Array, caps: string[]): Uint8Array;
|
|
77
|
+
/**
|
|
78
|
+
* Upper bound on an attestation request or response body before JSON.parse. Both halves
|
|
79
|
+
* parse counterparty-controlled bytes (the prover reads buyer requests; the buyer reads
|
|
80
|
+
* seller responses), so a hostile peer must not be able to force an unbounded parse.
|
|
81
|
+
*/
|
|
82
|
+
export declare const MAX_ATTEST_BODY_BYTES: number;
|
|
83
|
+
export declare function decodeAttestRequest(body: Uint8Array): {
|
|
84
|
+
nonce: Uint8Array;
|
|
85
|
+
caps: string[];
|
|
86
|
+
};
|
|
87
|
+
export declare function encodeAttestResponse(evidence: Record<string, Uint8Array>): Uint8Array;
|
|
88
|
+
export declare function decodeAttestResponse(body: Uint8Array): Record<string, Uint8Array>;
|
|
89
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,WAAW,qBAAqB,CAAA;AAE7C;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,sCAAoC,CAAA;AAE5D,wCAAwC;AACxC,eAAO,MAAM,WAAW,KAAK,CAAA;AAE7B;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3E;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,UAAU,CAa7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAOrG;AAKD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,yGAAyG;AACzG,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtE;AAED,uFAAuF;AACvF,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,IAAI,EAAE,MAAM,EAAE,CAAA;CACf;AAED,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAGjF;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAa,CAAA;AAQ/C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAc3F;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,UAAU,CAOrF;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAcjF"}
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
3
|
+
import { antseedRdV1 } from './report-data.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared constants and wire codecs used by both halves of the SDK (buyer verifier
|
|
6
|
+
* and seller prover). The single source of truth for the attestation request and
|
|
7
|
+
* response shape and the seller-bound preimage, so both sides agree byte-for-byte.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The advertised and pinned verifier id (also the seller-side prover's name).
|
|
11
|
+
* Hyphenated — no '@' or '/' — so capability strings satisfy PEER_CAPABILITY_PATTERN.
|
|
12
|
+
*/
|
|
13
|
+
export const VERIFIER_ID = 'antseed-verifier';
|
|
14
|
+
/**
|
|
15
|
+
* A claim id is the verifier id namespaced by the capability id, e.g.
|
|
16
|
+
* 'antseed-verifier:seller-node-tee-genuine'. One claim per capability.
|
|
17
|
+
*/
|
|
18
|
+
export function claimId(capId) {
|
|
19
|
+
return `${VERIFIER_ID}:${capId}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reserved control-plane path the buyer's verifier reaches the seller's prover at.
|
|
23
|
+
* The prefix must match @antseed/node's ANTSEED_ATTEST_PATH; the seller dispatches it
|
|
24
|
+
* (before provider or service matching) to the prover whose name === VERIFIER_ID.
|
|
25
|
+
*/
|
|
26
|
+
export const ATTEST_PATH = `/_antseed/attest/${VERIFIER_ID}`;
|
|
27
|
+
/** Freshness nonce length, in bytes. */
|
|
28
|
+
export const NONCE_BYTES = 32;
|
|
29
|
+
/**
|
|
30
|
+
* A PeerId is the canonical AntSeed identifier: 40 lowercase hex chars (the peer's
|
|
31
|
+
* EVM address, no 0x prefix). Lowercase and validate it, so bindings are symmetric on
|
|
32
|
+
* both sides regardless of input casing.
|
|
33
|
+
*/
|
|
34
|
+
export function normalizePeerId(peerId) {
|
|
35
|
+
const p = peerId.trim().toLowerCase();
|
|
36
|
+
if (!/^[0-9a-f]{40}$/.test(p)) {
|
|
37
|
+
throw new Error(`invalid peerId: expected 40 hex chars (EVM address, no 0x), got "${peerId.slice(0, 16)}..."`);
|
|
38
|
+
}
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The seller node's report_data binding: the {peerId} instance of the compositional scheme
|
|
43
|
+
* `antseed-rd-v1` (see report-data.ts). The configfs collector mints exactly this, so the
|
|
44
|
+
* buyer's node cap recomputes and enforces the same bytes.
|
|
45
|
+
*/
|
|
46
|
+
export function computeReportData(nonce, peerId) {
|
|
47
|
+
return Buffer.from(antseedRdV1.build(nonce, { peerId: normalizePeerId(peerId) }));
|
|
48
|
+
}
|
|
49
|
+
/** The dependent capability whose own evidence is excluded from the bundle it signs over. */
|
|
50
|
+
const SELLER_BOUND_CAP_ID = 'seller-bound';
|
|
51
|
+
/**
|
|
52
|
+
* Canonical digest over the whole attestation bundle — every cap's evidence entry
|
|
53
|
+
* except seller-bound's own signature (it cannot sign over itself). A sort by cap id
|
|
54
|
+
* makes both halves agree regardless of map iteration order. Length-prefixing both the id
|
|
55
|
+
* and the bytes (len(id) ‖ id ‖ len(bytes) ‖ bytes) keeps the concatenation unambiguous, so
|
|
56
|
+
* no cap can shift bytes across an entry boundary. One seller signature therefore covers
|
|
57
|
+
* the node quote and the provider quote (and any later caps) together.
|
|
58
|
+
*/
|
|
59
|
+
export function bundleDigest(evidence) {
|
|
60
|
+
const ids = Object.keys(evidence).filter((id) => id !== SELLER_BOUND_CAP_ID).sort();
|
|
61
|
+
const h = createHash('sha256');
|
|
62
|
+
const u32 = (n) => { const b = Buffer.alloc(4); b.writeUInt32BE(n, 0); return b; };
|
|
63
|
+
for (const id of ids) {
|
|
64
|
+
const bytes = evidence[id];
|
|
65
|
+
const idBuf = Buffer.from(id, 'utf8');
|
|
66
|
+
h.update(u32(idBuf.length));
|
|
67
|
+
h.update(idBuf);
|
|
68
|
+
h.update(u32(bytes.length));
|
|
69
|
+
h.update(Buffer.from(bytes));
|
|
70
|
+
}
|
|
71
|
+
return new Uint8Array(h.digest());
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* seller-bound preimage — the 32-byte digest the seller signs with its AntSeed
|
|
75
|
+
* identity key:
|
|
76
|
+
*
|
|
77
|
+
* keccak256( nonce(32) || bundleDigest(32) || utf8(peerId) )
|
|
78
|
+
*
|
|
79
|
+
* It binds the seller's identity signature to the entire bundle (via bundleDigest), this
|
|
80
|
+
* nonce (freshness), and this peer id (identity), so nobody can pair a seller's signature
|
|
81
|
+
* with different evidence or replay it. Defined here so both halves compute identical bytes.
|
|
82
|
+
*/
|
|
83
|
+
export function sellerBoundPreimage(nonce, digest, peerId) {
|
|
84
|
+
const pid = new TextEncoder().encode(normalizePeerId(peerId));
|
|
85
|
+
const preimage = new Uint8Array(nonce.length + digest.length + pid.length);
|
|
86
|
+
preimage.set(nonce, 0);
|
|
87
|
+
preimage.set(digest, nonce.length);
|
|
88
|
+
preimage.set(pid, nonce.length + digest.length);
|
|
89
|
+
return keccak_256(preimage);
|
|
90
|
+
}
|
|
91
|
+
/** Prefix of the config keys under which a prover exposes collected evidence to later collectors. */
|
|
92
|
+
const EVIDENCE_KEY_PREFIX = 'evidence:';
|
|
93
|
+
/**
|
|
94
|
+
* Config key under which a prover exposes an already-collected capability's evidence
|
|
95
|
+
* (base64) to later collectors in the same request. It lets a dependent capability
|
|
96
|
+
* (seller-bound) read the whole bundle it must bind to, without provider specifics.
|
|
97
|
+
*/
|
|
98
|
+
export function evidenceConfigKey(capId) {
|
|
99
|
+
return `${EVIDENCE_KEY_PREFIX}${capId}`;
|
|
100
|
+
}
|
|
101
|
+
/** Inverse of evidenceConfigKey: the cap id an "evidence:<capId>" config key carries, else undefined. */
|
|
102
|
+
export function parseEvidenceConfigKey(key) {
|
|
103
|
+
return key.startsWith(EVIDENCE_KEY_PREFIX) ? key.slice(EVIDENCE_KEY_PREFIX.length) : undefined;
|
|
104
|
+
}
|
|
105
|
+
export function encodeAttestRequest(nonce, caps) {
|
|
106
|
+
const body = { nonce: Buffer.from(nonce).toString('base64'), caps };
|
|
107
|
+
return new TextEncoder().encode(JSON.stringify(body));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Upper bound on an attestation request or response body before JSON.parse. Both halves
|
|
111
|
+
* parse counterparty-controlled bytes (the prover reads buyer requests; the buyer reads
|
|
112
|
+
* seller responses), so a hostile peer must not be able to force an unbounded parse.
|
|
113
|
+
*/
|
|
114
|
+
export const MAX_ATTEST_BODY_BYTES = 512 * 1024;
|
|
115
|
+
function assertBodySize(body, what) {
|
|
116
|
+
if (body.length > MAX_ATTEST_BODY_BYTES) {
|
|
117
|
+
throw new Error(`${what} exceeds ${MAX_ATTEST_BODY_BYTES} bytes (${body.length})`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function decodeAttestRequest(body) {
|
|
121
|
+
assertBodySize(body, 'attestation request');
|
|
122
|
+
const parsed = JSON.parse(new TextDecoder().decode(body));
|
|
123
|
+
if (typeof parsed.nonce !== 'string' || parsed.nonce.length === 0) {
|
|
124
|
+
throw new Error('attestation request missing "nonce"');
|
|
125
|
+
}
|
|
126
|
+
const nonce = Buffer.from(parsed.nonce, 'base64');
|
|
127
|
+
if (nonce.length !== NONCE_BYTES) {
|
|
128
|
+
throw new Error(`attestation nonce must be ${NONCE_BYTES} bytes, got ${nonce.length}`);
|
|
129
|
+
}
|
|
130
|
+
if (!Array.isArray(parsed.caps) || !parsed.caps.every((c) => typeof c === 'string')) {
|
|
131
|
+
throw new Error('attestation request missing "caps" (string[])');
|
|
132
|
+
}
|
|
133
|
+
return { nonce, caps: parsed.caps };
|
|
134
|
+
}
|
|
135
|
+
export function encodeAttestResponse(evidence) {
|
|
136
|
+
const enc = {};
|
|
137
|
+
for (const [capId, bytes] of Object.entries(evidence)) {
|
|
138
|
+
enc[capId] = Buffer.from(bytes).toString('base64');
|
|
139
|
+
}
|
|
140
|
+
const body = { evidence: enc };
|
|
141
|
+
return new TextEncoder().encode(JSON.stringify(body));
|
|
142
|
+
}
|
|
143
|
+
export function decodeAttestResponse(body) {
|
|
144
|
+
assertBodySize(body, 'attestation response');
|
|
145
|
+
const parsed = JSON.parse(new TextDecoder().decode(body));
|
|
146
|
+
if (parsed.evidence === null || typeof parsed.evidence !== 'object') {
|
|
147
|
+
throw new Error('attestation response missing "evidence" object');
|
|
148
|
+
}
|
|
149
|
+
const out = {};
|
|
150
|
+
for (const [capId, b64] of Object.entries(parsed.evidence)) {
|
|
151
|
+
if (typeof b64 !== 'string') {
|
|
152
|
+
throw new Error(`attestation evidence for "${capId}" is not base64`);
|
|
153
|
+
}
|
|
154
|
+
out[capId] = new Uint8Array(Buffer.from(b64, 'base64'));
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=shared.js.map
|