@tangle-network/agent-provider-tangle 0.14.0 → 0.14.1
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
CHANGED
|
@@ -195,6 +195,13 @@ const provider = createTangleProvider({
|
|
|
195
195
|
})
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
The provider stores the raw report in `ConfidentialAttestation.quote` through
|
|
199
|
+
`encodeTangleConfidentialAttestationQuote`.
|
|
200
|
+
The quote is a versioned canonical JSON object with base64url byte fields.
|
|
201
|
+
Use `decodeTangleConfidentialAttestationQuote` to recover a report and reject
|
|
202
|
+
legacy numeric-array quotes, unknown fields, malformed bytes, and oversized
|
|
203
|
+
reports before verification.
|
|
204
|
+
|
|
198
205
|
## Environment observation
|
|
199
206
|
|
|
200
207
|
`environment.observe()` returns the normalized `AgentEnvironmentObservation`.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export { createTangleProvider } from "./tangle-provider.js";
|
|
|
4
4
|
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|
|
5
5
|
export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
6
6
|
export type { TangleWorkspaceBranchingOptions } from "./tangle-workspace-branching.js";
|
|
7
|
+
export { decodeTangleConfidentialAttestationQuote, encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION, } from "./tangle-confidential-attestation.js";
|
|
8
|
+
export type { TangleConfidentialAttestationReport } from "./tangle-confidential-attestation.js";
|
|
7
9
|
export { safeEndpointFromConnection } from "./tangle-observation.js";
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export * from "./tangle-types.js";
|
|
|
2
2
|
export { createTangleProvider } from "./tangle-provider.js";
|
|
3
3
|
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|
|
4
4
|
export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
5
|
+
export { decodeTangleConfidentialAttestationQuote, encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION, } from "./tangle-confidential-attestation.js";
|
|
5
6
|
export { safeEndpointFromConnection } from "./tangle-observation.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SandboxTeeAttestationReportLike } from "./tangle-types.js";
|
|
2
|
+
/** Version of the canonical opaque quote carried by a confidential attestation. */
|
|
3
|
+
export declare const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION: 1;
|
|
4
|
+
/** Kind discriminator prevents this codec from accepting another quote format. */
|
|
5
|
+
export declare const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND: "tangle-sandbox-tee";
|
|
6
|
+
/** Raw evidence bound before it can enter a portable attestation. */
|
|
7
|
+
export declare const MAX_TEE_EVIDENCE_BYTES = 16384;
|
|
8
|
+
/** Raw measurement bound before it can enter a portable attestation. */
|
|
9
|
+
export declare const MAX_TEE_MEASUREMENT_BYTES = 256;
|
|
10
|
+
/** Read-only report shape accepted by the quote encoder. */
|
|
11
|
+
export interface TangleConfidentialAttestationReport {
|
|
12
|
+
readonly tee_type: string;
|
|
13
|
+
readonly evidence: readonly number[];
|
|
14
|
+
readonly measurement: readonly number[];
|
|
15
|
+
readonly timestamp: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encode a Sandbox TEE report without decimal JSON byte arrays.
|
|
19
|
+
*
|
|
20
|
+
* The returned JSON uses a fixed key order and unpadded base64url byte fields.
|
|
21
|
+
* `undefined` means the report is not a valid bounded report or the quote does
|
|
22
|
+
* not fit the shared confidential-attestation field.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeTangleConfidentialAttestationQuote(report: TangleConfidentialAttestationReport): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Decode and canonicalize a provider quote.
|
|
27
|
+
*
|
|
28
|
+
* The parser rejects old numeric-array quotes, unknown fields, non-canonical
|
|
29
|
+
* JSON, malformed base64url, and reports outside the provider bounds.
|
|
30
|
+
*/
|
|
31
|
+
export declare function decodeTangleConfidentialAttestationQuote(quote: unknown): SandboxTeeAttestationReportLike | undefined;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH, } from "@tangle-network/agent-interface";
|
|
2
|
+
/** Version of the canonical opaque quote carried by a confidential attestation. */
|
|
3
|
+
export const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION = 1;
|
|
4
|
+
/** Kind discriminator prevents this codec from accepting another quote format. */
|
|
5
|
+
export const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND = "tangle-sandbox-tee";
|
|
6
|
+
/** Raw evidence bound before it can enter a portable attestation. */
|
|
7
|
+
export const MAX_TEE_EVIDENCE_BYTES = 16_384;
|
|
8
|
+
/** Raw measurement bound before it can enter a portable attestation. */
|
|
9
|
+
export const MAX_TEE_MEASUREMENT_BYTES = 256;
|
|
10
|
+
const MAX_TEE_TYPE_LENGTH = 64;
|
|
11
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
12
|
+
const QUOTE_KEYS = [
|
|
13
|
+
"version",
|
|
14
|
+
"kind",
|
|
15
|
+
"tee_type",
|
|
16
|
+
"evidence",
|
|
17
|
+
"measurement",
|
|
18
|
+
"timestamp",
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Encode a Sandbox TEE report without decimal JSON byte arrays.
|
|
22
|
+
*
|
|
23
|
+
* The returned JSON uses a fixed key order and unpadded base64url byte fields.
|
|
24
|
+
* `undefined` means the report is not a valid bounded report or the quote does
|
|
25
|
+
* not fit the shared confidential-attestation field.
|
|
26
|
+
*/
|
|
27
|
+
export function encodeTangleConfidentialAttestationQuote(report) {
|
|
28
|
+
if (!validReportShape(report))
|
|
29
|
+
return undefined;
|
|
30
|
+
const document = {
|
|
31
|
+
version: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION,
|
|
32
|
+
kind: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND,
|
|
33
|
+
tee_type: report.tee_type,
|
|
34
|
+
evidence: bytesToBase64url(report.evidence),
|
|
35
|
+
measurement: bytesToBase64url(report.measurement),
|
|
36
|
+
timestamp: report.timestamp,
|
|
37
|
+
};
|
|
38
|
+
const quote = JSON.stringify(document);
|
|
39
|
+
return quote.length <= CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH
|
|
40
|
+
? quote
|
|
41
|
+
: undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decode and canonicalize a provider quote.
|
|
45
|
+
*
|
|
46
|
+
* The parser rejects old numeric-array quotes, unknown fields, non-canonical
|
|
47
|
+
* JSON, malformed base64url, and reports outside the provider bounds.
|
|
48
|
+
*/
|
|
49
|
+
export function decodeTangleConfidentialAttestationQuote(quote) {
|
|
50
|
+
if (typeof quote !== "string" ||
|
|
51
|
+
quote.length === 0 ||
|
|
52
|
+
quote.length > CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(quote);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const document = quoteDocument(parsed);
|
|
63
|
+
if (document === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const report = reportFromDocument(document);
|
|
66
|
+
if (report === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
return encodeTangleConfidentialAttestationQuote(report) === quote
|
|
69
|
+
? report
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
72
|
+
function validReportShape(report) {
|
|
73
|
+
if (!isPlainObject(report))
|
|
74
|
+
return false;
|
|
75
|
+
if (!hasExactKeys(report, ["tee_type", "evidence", "measurement", "timestamp"])) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return (validTeeType(report.tee_type) &&
|
|
79
|
+
validBytes(report.evidence, MAX_TEE_EVIDENCE_BYTES) &&
|
|
80
|
+
validBytes(report.measurement, MAX_TEE_MEASUREMENT_BYTES) &&
|
|
81
|
+
Number.isSafeInteger(report.timestamp) &&
|
|
82
|
+
report.timestamp > 0);
|
|
83
|
+
}
|
|
84
|
+
function quoteDocument(value) {
|
|
85
|
+
if (!isPlainObject(value) || !hasExactKeys(value, QUOTE_KEYS))
|
|
86
|
+
return undefined;
|
|
87
|
+
const candidate = value;
|
|
88
|
+
const teeType = candidate.tee_type;
|
|
89
|
+
const evidenceValue = candidate.evidence;
|
|
90
|
+
const measurementValue = candidate.measurement;
|
|
91
|
+
const timestamp = candidate.timestamp;
|
|
92
|
+
if (candidate.version !== TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION ||
|
|
93
|
+
candidate.kind !== TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND ||
|
|
94
|
+
!validTeeType(teeType) ||
|
|
95
|
+
typeof evidenceValue !== "string" ||
|
|
96
|
+
typeof measurementValue !== "string" ||
|
|
97
|
+
typeof timestamp !== "number" ||
|
|
98
|
+
!Number.isSafeInteger(timestamp) ||
|
|
99
|
+
timestamp <= 0) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const evidence = base64urlToBytes(evidenceValue, MAX_TEE_EVIDENCE_BYTES);
|
|
103
|
+
const measurement = base64urlToBytes(measurementValue, MAX_TEE_MEASUREMENT_BYTES);
|
|
104
|
+
if (evidence === undefined || measurement === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
version: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION,
|
|
108
|
+
kind: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND,
|
|
109
|
+
tee_type: teeType,
|
|
110
|
+
evidence: evidenceValue,
|
|
111
|
+
measurement: measurementValue,
|
|
112
|
+
timestamp,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function reportFromDocument(document) {
|
|
116
|
+
const evidence = base64urlToBytes(document.evidence, MAX_TEE_EVIDENCE_BYTES);
|
|
117
|
+
const measurement = base64urlToBytes(document.measurement, MAX_TEE_MEASUREMENT_BYTES);
|
|
118
|
+
if (evidence === undefined || measurement === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
const report = {
|
|
121
|
+
tee_type: document.tee_type,
|
|
122
|
+
evidence: Array.from(evidence),
|
|
123
|
+
measurement: Array.from(measurement),
|
|
124
|
+
timestamp: document.timestamp,
|
|
125
|
+
};
|
|
126
|
+
return validReportShape(report) ? report : undefined;
|
|
127
|
+
}
|
|
128
|
+
function validTeeType(value) {
|
|
129
|
+
return (typeof value === "string" &&
|
|
130
|
+
value.length > 0 &&
|
|
131
|
+
value.length <= MAX_TEE_TYPE_LENGTH &&
|
|
132
|
+
value.trim() === value &&
|
|
133
|
+
!/[\u0000-\u001f\u007f]/u.test(value));
|
|
134
|
+
}
|
|
135
|
+
function validBytes(value, maxLength) {
|
|
136
|
+
return (Array.isArray(value) &&
|
|
137
|
+
value.length > 0 &&
|
|
138
|
+
value.length <= maxLength &&
|
|
139
|
+
value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255));
|
|
140
|
+
}
|
|
141
|
+
function bytesToBase64url(bytes) {
|
|
142
|
+
return Buffer.from(Uint8Array.from(bytes)).toString("base64url");
|
|
143
|
+
}
|
|
144
|
+
function base64urlToBytes(value, maxLength) {
|
|
145
|
+
if (value.length === 0 || !BASE64URL_PATTERN.test(value))
|
|
146
|
+
return undefined;
|
|
147
|
+
const bytes = Buffer.from(value, "base64url");
|
|
148
|
+
if (bytes.length === 0 ||
|
|
149
|
+
bytes.length > maxLength ||
|
|
150
|
+
bytes.toString("base64url") !== value) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return bytes;
|
|
154
|
+
}
|
|
155
|
+
function isPlainObject(value) {
|
|
156
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const prototype = Object.getPrototypeOf(value);
|
|
160
|
+
return prototype === Object.prototype || prototype === null;
|
|
161
|
+
}
|
|
162
|
+
function hasExactKeys(value, expected) {
|
|
163
|
+
const keys = Object.keys(value);
|
|
164
|
+
return keys.length === expected.length && keys.every((key) => expected.includes(key));
|
|
165
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConfidentialAttestationSchema, ConfidentialExecutionRequestSchema, ForkedEnvironmentRefSchema, WorkspaceCheckpointRefSchema, WorkspaceCheckpointRequestSchema, WorkspaceCleanupAcknowledgementSchema, WorkspaceCleanupRequestSchema, WorkspaceForkRequestSchema, WorkspaceOperationLookupRequestSchema, WorkspaceCheckpointResultSchema, WorkspaceCheckpointLookupResultSchema, WorkspaceForkResultSchema, WorkspaceForkLookupResultSchema, canonicalCandidateDigest, confidentialExecutionVerified, sha256Bytes, } from "@tangle-network/agent-interface";
|
|
2
2
|
import { awaitWithSignal, boundedIdentifier, boundedString, assertBoundedJson, MAX_LIST_RESULTS, MAX_STRING_LENGTH, SANDBOX_LIST_PAGE_SIZE, } from "./tangle-contract-safety.js";
|
|
3
|
+
import { encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, } from "./tangle-confidential-attestation.js";
|
|
3
4
|
/**
|
|
4
5
|
* Namespace used for provider recovery metadata.
|
|
5
6
|
*
|
|
@@ -532,7 +533,7 @@ async function confidentialAttestationForChild(request, child, provider, verifie
|
|
|
532
533
|
return undefined;
|
|
533
534
|
}
|
|
534
535
|
const measurement = sha256Bytes(Uint8Array.from(response.attestation.measurement));
|
|
535
|
-
const quote =
|
|
536
|
+
const quote = encodeTangleConfidentialAttestationQuote(response.attestation);
|
|
536
537
|
if (quote === undefined)
|
|
537
538
|
return undefined;
|
|
538
539
|
let verifiedAt;
|
|
@@ -610,10 +611,10 @@ function validTeeReport(report) {
|
|
|
610
611
|
return !!report &&
|
|
611
612
|
safeString(report.tee_type) !== undefined &&
|
|
612
613
|
Array.isArray(report.evidence) &&
|
|
613
|
-
report.evidence.length <=
|
|
614
|
+
report.evidence.length <= MAX_TEE_EVIDENCE_BYTES &&
|
|
614
615
|
report.evidence.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
|
|
615
616
|
Array.isArray(report.measurement) &&
|
|
616
|
-
report.measurement.length <=
|
|
617
|
+
report.measurement.length <= MAX_TEE_MEASUREMENT_BYTES &&
|
|
617
618
|
report.measurement.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
|
|
618
619
|
Number.isFinite(report.timestamp) &&
|
|
619
620
|
report.timestamp > 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-tangle",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,11 +83,13 @@
|
|
|
83
83
|
"dist/tangle-types.js",
|
|
84
84
|
"dist/tangle-workspace-branching.d.ts",
|
|
85
85
|
"dist/tangle-workspace-branching.js",
|
|
86
|
+
"dist/tangle-confidential-attestation.d.ts",
|
|
87
|
+
"dist/tangle-confidential-attestation.js",
|
|
86
88
|
"README.md",
|
|
87
89
|
"LICENSE"
|
|
88
90
|
],
|
|
89
91
|
"dependencies": {
|
|
90
|
-
"@tangle-network/agent-interface": "^1.7.
|
|
92
|
+
"@tangle-network/agent-interface": "^1.7.1"
|
|
91
93
|
},
|
|
92
94
|
"peerDependencies": {
|
|
93
95
|
"@tangle-network/sandbox": ">=0.33.1 <1.0.0"
|