@opendatalabs/vana-sdk 3.21.0 → 3.22.0-pr.207.e47cd30
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 +64 -0
- package/dist/index.browser.d.ts +3 -1
- package/dist/index.browser.js +320 -4
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +364 -34
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +3 -1
- package/dist/index.node.js +323 -7
- package/dist/index.node.js.map +4 -4
- package/dist/protocol/derivative-questions.cjs +22 -0
- package/dist/protocol/derivative-questions.cjs.map +1 -1
- package/dist/protocol/derivative-questions.d.ts +53 -0
- package/dist/protocol/derivative-questions.js +19 -0
- package/dist/protocol/derivative-questions.js.map +1 -1
- package/dist/protocol/derivative-status.cjs +209 -0
- package/dist/protocol/derivative-status.cjs.map +1 -0
- package/dist/protocol/derivative-status.d.ts +196 -0
- package/dist/protocol/derivative-status.js +190 -0
- package/dist/protocol/derivative-status.js.map +1 -0
- package/dist/protocol/derivative-status.test.d.ts +1 -0
- package/dist/protocol/identity.cjs +169 -0
- package/dist/protocol/identity.cjs.map +1 -0
- package/dist/protocol/identity.d.ts +151 -0
- package/dist/protocol/identity.js +132 -0
- package/dist/protocol/identity.js.map +1 -0
- package/dist/protocol/identity.test.d.ts +1 -0
- package/dist/tests/mock-personal-server.d.ts +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derivative status: the lifecycle of the question behind a derived scope,
|
|
3
|
+
* as the party that READS the answer sees it.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A builder registers a question with {@link registerQuestion} and follows it
|
|
7
|
+
* with {@link waitForQuestion}, both of which need a write session. The app
|
|
8
|
+
* that only consumes the answer holds no write entry at all — a consent flow
|
|
9
|
+
* grants it a bare read on the derived scope — so it cannot open one, and
|
|
10
|
+
* `GET /v1/data/<derivedScope>` answers 404 for all three of "computing right
|
|
11
|
+
* now", "failed but retrying" and "failed for good".
|
|
12
|
+
*
|
|
13
|
+
* `GET /v1/derivatives/status?derivedScope=<scope>` is that reader's view.
|
|
14
|
+
* Authorization is the data read's (a live grant covering the derived scope,
|
|
15
|
+
* or the owner), nothing is served and nothing is charged, and the view is
|
|
16
|
+
* deliberately narrow: lifecycle, a coarse {@link DerivativeErrorCode} and
|
|
17
|
+
* the next retry. The question text, the source scopes, the question id, the
|
|
18
|
+
* registrar and the server's raw error string stay owner-only.
|
|
19
|
+
*
|
|
20
|
+
* Requires `personal-server-ts` with the status route; an older Personal
|
|
21
|
+
* Server answers 404 for the route itself.
|
|
22
|
+
*
|
|
23
|
+
* @category Protocol
|
|
24
|
+
*/
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
import { type QuestionStatus } from "./derivative-questions.js";
|
|
27
|
+
import { type ResolveWriteSignerOptions, type WriteSignerSource } from "./write-signer.js";
|
|
28
|
+
export { DERIVATIVE_ERROR_CODES, DerivativeErrorCodeSchema, type DerivativeErrorCode, } from "./derivative-questions.js";
|
|
29
|
+
/** The reader-facing status route. */
|
|
30
|
+
export declare const DERIVATIVE_STATUS_PATH = "/v1/derivatives/status";
|
|
31
|
+
/** How long {@link waitForDerivativeStatus} polls before giving up. */
|
|
32
|
+
export declare const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 120000;
|
|
33
|
+
/**
|
|
34
|
+
* How long {@link waitForDerivativeStatus} waits between polls when the
|
|
35
|
+
* server names no retry time of its own.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2000;
|
|
38
|
+
/**
|
|
39
|
+
* The status of the derived scope, not of one registration: when several
|
|
40
|
+
* questions write the same scope, the server reports the most optimistic
|
|
41
|
+
* true state, because serving data is registration-agnostic.
|
|
42
|
+
*/
|
|
43
|
+
export declare const DerivativeStatusSchema: z.ZodObject<{
|
|
44
|
+
derivedScope: z.ZodString;
|
|
45
|
+
status: z.ZodEnum<{
|
|
46
|
+
pending: "pending";
|
|
47
|
+
ready: "ready";
|
|
48
|
+
failed: "failed";
|
|
49
|
+
stale: "stale";
|
|
50
|
+
}>;
|
|
51
|
+
lastComputedAt: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
|
|
52
|
+
derivedVersion: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | null, number | null | undefined>>;
|
|
53
|
+
derivedCollectedAt: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
|
|
54
|
+
errorCode: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
55
|
+
internal: "internal";
|
|
56
|
+
inference_unavailable: "inference_unavailable";
|
|
57
|
+
source_missing: "source_missing";
|
|
58
|
+
grant_invalid: "grant_invalid";
|
|
59
|
+
}>>>, z.ZodTransform<NonNullable<"internal" | "inference_unavailable" | "source_missing" | "grant_invalid"> | null, "internal" | "inference_unavailable" | "source_missing" | "grant_invalid" | null | undefined>>;
|
|
60
|
+
retryAfterSeconds: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | null, number | null | undefined>>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
/** @see {@link DerivativeStatusSchema} */
|
|
63
|
+
export type DerivativeStatus = z.infer<typeof DerivativeStatusSchema>;
|
|
64
|
+
/** What {@link getDerivativeStatus} needs to sign and send one read. */
|
|
65
|
+
export interface GetDerivativeStatusParams extends ResolveWriteSignerOptions {
|
|
66
|
+
/** Personal Server origin, e.g. `https://ps.example.com`. */
|
|
67
|
+
personalServerUrl: string;
|
|
68
|
+
/** The derived scope whose question to observe. */
|
|
69
|
+
derivedScope: string;
|
|
70
|
+
/**
|
|
71
|
+
* A grant covering the derived scope, sent as the signed `grantId` claim.
|
|
72
|
+
* Omit only when the signer is the Personal Server's owner, who is
|
|
73
|
+
* authorized without one.
|
|
74
|
+
*/
|
|
75
|
+
grantId?: string;
|
|
76
|
+
/** Reader key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */
|
|
77
|
+
signer: WriteSignerSource;
|
|
78
|
+
/** Web3Signed audience; defaults to `personalServerUrl`. */
|
|
79
|
+
audience?: string;
|
|
80
|
+
/** `fetch` to use; defaults to `globalThis.fetch`. */
|
|
81
|
+
fetch?: typeof fetch;
|
|
82
|
+
/** Extra request headers. */
|
|
83
|
+
headers?: HeadersInit;
|
|
84
|
+
/** Aborts the request in flight. */
|
|
85
|
+
signal?: AbortSignal;
|
|
86
|
+
}
|
|
87
|
+
/** What {@link waitForDerivativeStatus} polls with. */
|
|
88
|
+
export interface WaitForDerivativeStatusParams extends GetDerivativeStatusParams {
|
|
89
|
+
/** Give up after this long (default 120s). */
|
|
90
|
+
timeoutMs?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Wait between polls when the server names no retry time (default 2s).
|
|
93
|
+
* A `retryAfterSeconds` from the server replaces this outright, longer or
|
|
94
|
+
* shorter: it is when the next compute actually happens.
|
|
95
|
+
*/
|
|
96
|
+
pollIntervalMs?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Aborts the wait, and the request in flight with it: the signal is passed
|
|
99
|
+
* to every poll, so an abort during a stalled request does not sit until
|
|
100
|
+
* the transport gives up.
|
|
101
|
+
*/
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The request target for a status read: the query carries the derived scope,
|
|
106
|
+
* the signed `uri` does not.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* Like every Web3Signed read (data, lineage), the Personal Server verifies
|
|
110
|
+
* the signature over the PATH; per-scope authorization is enforced live
|
|
111
|
+
* against the caller's grant on each request, so the query needs no
|
|
112
|
+
* signature to be safe. Only the write path signs path AND query, where a
|
|
113
|
+
* parameter decides what is written.
|
|
114
|
+
*/
|
|
115
|
+
export declare function derivativeStatusTarget(derivedScope: string): string;
|
|
116
|
+
/**
|
|
117
|
+
* Is this a state the reader can act on?
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* `ready` means the derived scope has an answer to read. A `failed` status
|
|
121
|
+
* is settled only when no retry is pending: with `retryAfterSeconds` set the
|
|
122
|
+
* Personal Server will compute again on its own, so the answer may still
|
|
123
|
+
* arrive. `pending` and `stale` are always in flight.
|
|
124
|
+
*/
|
|
125
|
+
export declare function isDerivativeStatusSettled(status: DerivativeStatus): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Read the lifecycle of the question behind a derived scope.
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* Sends `GET /v1/derivatives/status?derivedScope=<scope>` with a Web3Signed
|
|
131
|
+
* `Authorization` header carrying `grantId`, the same authentication a data
|
|
132
|
+
* read uses. Nothing is charged: the route authorizes, it does not serve
|
|
133
|
+
* data, so a priced grant raises no 402 here.
|
|
134
|
+
*
|
|
135
|
+
* @returns The status of the derived scope. When several registrations write
|
|
136
|
+
* it, the most optimistic true state answers — `ready`, then `stale`, then
|
|
137
|
+
* `pending`, then `failed` — because a duplicate that never wrote anything
|
|
138
|
+
* must not report away an answer the scope has.
|
|
139
|
+
* @throws {DerivativeQuestionNotFoundError} 404: the caller may read the
|
|
140
|
+
* scope but no question stands behind it (and, on an older Personal
|
|
141
|
+
* Server, the route itself is unknown).
|
|
142
|
+
* @throws {WriteForbiddenError} 403: the grant does not cover the derived
|
|
143
|
+
* scope. The check runs before any store lookup, so a caller cannot probe
|
|
144
|
+
* which scopes have questions.
|
|
145
|
+
* @throws {DerivativeQuestionRejectedError} On any other non-2xx answer or
|
|
146
|
+
* an unparseable body.
|
|
147
|
+
* @throws {WriteTransportError} When `fetch` itself failed.
|
|
148
|
+
* @throws {WriteRequestError} On a missing `derivedScope` or no `fetch`.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const status = await getDerivativeStatus({
|
|
153
|
+
* personalServerUrl: "https://ps.example.com",
|
|
154
|
+
* derivedScope: "coach.weekly",
|
|
155
|
+
* grantId,
|
|
156
|
+
* signer,
|
|
157
|
+
* });
|
|
158
|
+
* if (status.status === "ready") {
|
|
159
|
+
* const record = await readPersonalServerData({ ... });
|
|
160
|
+
* } else if (status.retryAfterSeconds !== null) {
|
|
161
|
+
* // Computing or retrying: come back then.
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export declare function getDerivativeStatus(params: GetDerivativeStatusParams): Promise<DerivativeStatus>;
|
|
166
|
+
/**
|
|
167
|
+
* Poll {@link getDerivativeStatus} until the derived scope has an answer or
|
|
168
|
+
* has stopped trying to get one.
|
|
169
|
+
*
|
|
170
|
+
* @remarks
|
|
171
|
+
* Returns as soon as {@link isDerivativeStatusSettled} holds: `ready`, or
|
|
172
|
+
* `failed` with no retry pending. A failure the server will retry is not a
|
|
173
|
+
* result, so the wait continues through it — on the server's own cadence,
|
|
174
|
+
* because `retryAfterSeconds` is when the next compute actually happens and
|
|
175
|
+
* polling faster only spends requests. A failed status is returned, not
|
|
176
|
+
* thrown: the reader branches on `errorCode`.
|
|
177
|
+
*
|
|
178
|
+
* @returns The settled status.
|
|
179
|
+
* @throws {DerivativeQuestionTimeoutError} When the budget ran out first.
|
|
180
|
+
* The question keeps computing on the server; call again.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const status = await waitForDerivativeStatus({
|
|
185
|
+
* personalServerUrl,
|
|
186
|
+
* derivedScope: "coach.weekly",
|
|
187
|
+
* grantId,
|
|
188
|
+
* signer,
|
|
189
|
+
* timeoutMs: 60_000,
|
|
190
|
+
* });
|
|
191
|
+
* if (status.status !== "ready") console.log(status.errorCode);
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export declare function waitForDerivativeStatus(params: WaitForDerivativeStatusParams): Promise<DerivativeStatus>;
|
|
195
|
+
/** Statuses that mean a compute is in flight. @see {@link QuestionStatus} */
|
|
196
|
+
export type PendingDerivativeStatus = Extract<QuestionStatus, "pending" | "stale">;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { buildWeb3SignedHeader } from "../auth/web3-signed-builder.js";
|
|
3
|
+
import {
|
|
4
|
+
DerivativeQuestionRejectedError,
|
|
5
|
+
DerivativeQuestionTimeoutError,
|
|
6
|
+
WriteRequestError,
|
|
7
|
+
WriteTransportError
|
|
8
|
+
} from "../errors.js";
|
|
9
|
+
import {
|
|
10
|
+
DerivativeErrorCodeSchema,
|
|
11
|
+
personalServerErrorFromQuestionResponse,
|
|
12
|
+
QuestionStatusSchema
|
|
13
|
+
} from "./derivative-questions.js";
|
|
14
|
+
import {
|
|
15
|
+
resolveWriteSigner
|
|
16
|
+
} from "./write-signer.js";
|
|
17
|
+
import {
|
|
18
|
+
DERIVATIVE_ERROR_CODES,
|
|
19
|
+
DerivativeErrorCodeSchema as DerivativeErrorCodeSchema2
|
|
20
|
+
} from "./derivative-questions.js";
|
|
21
|
+
const DERIVATIVE_STATUS_PATH = "/v1/derivatives/status";
|
|
22
|
+
const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 12e4;
|
|
23
|
+
const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2e3;
|
|
24
|
+
const nullable = (schema) => schema.nullish().transform((value) => value ?? null);
|
|
25
|
+
const DerivativeStatusSchema = z.object({
|
|
26
|
+
derivedScope: z.string().min(1),
|
|
27
|
+
status: QuestionStatusSchema,
|
|
28
|
+
/** When the last compute finished, or `null` if none ever has. */
|
|
29
|
+
lastComputedAt: nullable(z.string()),
|
|
30
|
+
/** Local version of the derived record the last compute wrote. */
|
|
31
|
+
derivedVersion: nullable(z.number()),
|
|
32
|
+
derivedCollectedAt: nullable(z.string()),
|
|
33
|
+
/** The failure class; `null` unless `status` is `failed`. */
|
|
34
|
+
errorCode: nullable(DerivativeErrorCodeSchema),
|
|
35
|
+
/**
|
|
36
|
+
* Seconds until the Personal Server's next automatic retry, or `null` when
|
|
37
|
+
* none is pending or running — the terminal signature. Poll on this cadence
|
|
38
|
+
* rather than guessing one.
|
|
39
|
+
*/
|
|
40
|
+
retryAfterSeconds: nullable(z.number())
|
|
41
|
+
});
|
|
42
|
+
function derivativeStatusTarget(derivedScope) {
|
|
43
|
+
return `${DERIVATIVE_STATUS_PATH}?derivedScope=${encodeURIComponent(derivedScope)}`;
|
|
44
|
+
}
|
|
45
|
+
function normalizeBaseUrl(url) {
|
|
46
|
+
return url.replace(/\/+$/, "");
|
|
47
|
+
}
|
|
48
|
+
function resolveFetch(fetchFn) {
|
|
49
|
+
const resolved = fetchFn ?? globalThis.fetch;
|
|
50
|
+
if (resolved === void 0) {
|
|
51
|
+
throw new WriteRequestError("No fetch implementation available");
|
|
52
|
+
}
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
function requireDerivedScope(derivedScope) {
|
|
56
|
+
if (typeof derivedScope !== "string" || derivedScope.length === 0) {
|
|
57
|
+
throw new WriteRequestError("derivedScope is required");
|
|
58
|
+
}
|
|
59
|
+
return derivedScope;
|
|
60
|
+
}
|
|
61
|
+
function sleep(ms, signal) {
|
|
62
|
+
if (ms <= 0) return Promise.resolve();
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
signal?.removeEventListener("abort", onAbort);
|
|
66
|
+
resolve();
|
|
67
|
+
}, ms);
|
|
68
|
+
const onAbort = () => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
reject(abortError(signal));
|
|
71
|
+
};
|
|
72
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function timeoutError(latest, timeoutMs) {
|
|
76
|
+
return new DerivativeQuestionTimeoutError(
|
|
77
|
+
`Derived scope ${latest.derivedScope} was still ${latest.status} after ${timeoutMs}ms`,
|
|
78
|
+
{
|
|
79
|
+
derivedScope: latest.derivedScope,
|
|
80
|
+
status: latest.status,
|
|
81
|
+
errorCode: latest.errorCode,
|
|
82
|
+
retryAfterSeconds: latest.retryAfterSeconds,
|
|
83
|
+
timeoutMs
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function abortError(signal) {
|
|
88
|
+
const reason = signal?.reason;
|
|
89
|
+
return reason instanceof Error ? reason : new WriteRequestError("Derivative status wait was aborted");
|
|
90
|
+
}
|
|
91
|
+
function isDerivativeStatusSettled(status) {
|
|
92
|
+
if (status.status === "ready") return true;
|
|
93
|
+
return status.status === "failed" && status.retryAfterSeconds === null;
|
|
94
|
+
}
|
|
95
|
+
async function getDerivativeStatus(params) {
|
|
96
|
+
const derivedScope = requireDerivedScope(params.derivedScope);
|
|
97
|
+
const fetchFn = resolveFetch(params.fetch);
|
|
98
|
+
const baseUrl = normalizeBaseUrl(params.personalServerUrl);
|
|
99
|
+
const signer = resolveWriteSigner(params.signer, { account: params.account });
|
|
100
|
+
const headers = new Headers(params.headers);
|
|
101
|
+
headers.set(
|
|
102
|
+
"Authorization",
|
|
103
|
+
await buildWeb3SignedHeader({
|
|
104
|
+
signMessage: signer.signMessage,
|
|
105
|
+
aud: params.audience ?? baseUrl,
|
|
106
|
+
method: "GET",
|
|
107
|
+
uri: DERIVATIVE_STATUS_PATH,
|
|
108
|
+
grantId: params.grantId
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
let response;
|
|
112
|
+
try {
|
|
113
|
+
response = await fetchFn(
|
|
114
|
+
`${baseUrl}${derivativeStatusTarget(derivedScope)}`,
|
|
115
|
+
{
|
|
116
|
+
method: "GET",
|
|
117
|
+
headers,
|
|
118
|
+
...params.signal ? { signal: params.signal } : {}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
throw new WriteTransportError(
|
|
123
|
+
`Derivative status read failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
124
|
+
1,
|
|
125
|
+
err
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw await personalServerErrorFromQuestionResponse(
|
|
130
|
+
response
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
let body;
|
|
134
|
+
try {
|
|
135
|
+
body = await response.json();
|
|
136
|
+
} catch (err) {
|
|
137
|
+
throw new DerivativeQuestionRejectedError(
|
|
138
|
+
"Derivative status response is not JSON",
|
|
139
|
+
response.status,
|
|
140
|
+
null,
|
|
141
|
+
{ cause: err instanceof Error ? err.message : String(err) }
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const parsed = DerivativeStatusSchema.safeParse(body);
|
|
145
|
+
if (!parsed.success) {
|
|
146
|
+
throw new DerivativeQuestionRejectedError(
|
|
147
|
+
"Derivative status response is not a status view",
|
|
148
|
+
response.status,
|
|
149
|
+
null,
|
|
150
|
+
{ issues: parsed.error.issues }
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return parsed.data;
|
|
154
|
+
}
|
|
155
|
+
async function waitForDerivativeStatus(params) {
|
|
156
|
+
const timeoutMs = Math.max(
|
|
157
|
+
0,
|
|
158
|
+
params.timeoutMs ?? DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS
|
|
159
|
+
);
|
|
160
|
+
const pollIntervalMs = Math.max(
|
|
161
|
+
0,
|
|
162
|
+
params.pollIntervalMs ?? DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS
|
|
163
|
+
);
|
|
164
|
+
const deadline = Date.now() + timeoutMs;
|
|
165
|
+
for (; ; ) {
|
|
166
|
+
if (params.signal?.aborted) throw abortError(params.signal);
|
|
167
|
+
const latest = await getDerivativeStatus(params);
|
|
168
|
+
if (isDerivativeStatusSettled(latest)) return latest;
|
|
169
|
+
const remaining = deadline - Date.now();
|
|
170
|
+
if (remaining <= 0) throw timeoutError(latest, timeoutMs);
|
|
171
|
+
const waitMs = latest.retryAfterSeconds === null ? pollIntervalMs : latest.retryAfterSeconds * 1e3;
|
|
172
|
+
if (waitMs > remaining) {
|
|
173
|
+
throw timeoutError(latest, timeoutMs);
|
|
174
|
+
}
|
|
175
|
+
await sleep(waitMs, params.signal);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS,
|
|
180
|
+
DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS,
|
|
181
|
+
DERIVATIVE_ERROR_CODES,
|
|
182
|
+
DERIVATIVE_STATUS_PATH,
|
|
183
|
+
DerivativeErrorCodeSchema2 as DerivativeErrorCodeSchema,
|
|
184
|
+
DerivativeStatusSchema,
|
|
185
|
+
derivativeStatusTarget,
|
|
186
|
+
getDerivativeStatus,
|
|
187
|
+
isDerivativeStatusSettled,
|
|
188
|
+
waitForDerivativeStatus
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=derivative-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/derivative-status.ts"],"sourcesContent":["/**\n * Derivative status: the lifecycle of the question behind a derived scope,\n * as the party that READS the answer sees it.\n *\n * @remarks\n * A builder registers a question with {@link registerQuestion} and follows it\n * with {@link waitForQuestion}, both of which need a write session. The app\n * that only consumes the answer holds no write entry at all — a consent flow\n * grants it a bare read on the derived scope — so it cannot open one, and\n * `GET /v1/data/<derivedScope>` answers 404 for all three of \"computing right\n * now\", \"failed but retrying\" and \"failed for good\".\n *\n * `GET /v1/derivatives/status?derivedScope=<scope>` is that reader's view.\n * Authorization is the data read's (a live grant covering the derived scope,\n * or the owner), nothing is served and nothing is charged, and the view is\n * deliberately narrow: lifecycle, a coarse {@link DerivativeErrorCode} and\n * the next retry. The question text, the source scopes, the question id, the\n * registrar and the server's raw error string stay owner-only.\n *\n * Requires `personal-server-ts` with the status route; an older Personal\n * Server answers 404 for the route itself.\n *\n * @category Protocol\n */\n\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport {\n DerivativeQuestionRejectedError,\n DerivativeQuestionTimeoutError,\n WriteRequestError,\n WriteTransportError,\n type PersonalServerWriteError,\n} from \"../errors\";\nimport {\n DerivativeErrorCodeSchema,\n personalServerErrorFromQuestionResponse,\n QuestionStatusSchema,\n type QuestionStatus,\n} from \"./derivative-questions\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nexport {\n DERIVATIVE_ERROR_CODES,\n DerivativeErrorCodeSchema,\n type DerivativeErrorCode,\n} from \"./derivative-questions\";\n\n/** The reader-facing status route. */\nexport const DERIVATIVE_STATUS_PATH = \"/v1/derivatives/status\";\n\n/** How long {@link waitForDerivativeStatus} polls before giving up. */\nexport const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 120_000;\n\n/**\n * How long {@link waitForDerivativeStatus} waits between polls when the\n * server names no retry time of its own.\n */\nexport const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2_000;\n\nconst nullable = <T extends z.ZodTypeAny>(schema: T) =>\n schema\n .nullish()\n .transform((value: z.infer<T> | null | undefined) => value ?? null);\n\n/**\n * The status of the derived scope, not of one registration: when several\n * questions write the same scope, the server reports the most optimistic\n * true state, because serving data is registration-agnostic.\n */\nexport const DerivativeStatusSchema = z.object({\n derivedScope: z.string().min(1),\n status: QuestionStatusSchema,\n /** When the last compute finished, or `null` if none ever has. */\n lastComputedAt: nullable(z.string()),\n /** Local version of the derived record the last compute wrote. */\n derivedVersion: nullable(z.number()),\n derivedCollectedAt: nullable(z.string()),\n /** The failure class; `null` unless `status` is `failed`. */\n errorCode: nullable(DerivativeErrorCodeSchema),\n /**\n * Seconds until the Personal Server's next automatic retry, or `null` when\n * none is pending or running — the terminal signature. Poll on this cadence\n * rather than guessing one.\n */\n retryAfterSeconds: nullable(z.number()),\n});\n\n/** @see {@link DerivativeStatusSchema} */\nexport type DerivativeStatus = z.infer<typeof DerivativeStatusSchema>;\n\n/** What {@link getDerivativeStatus} needs to sign and send one read. */\nexport interface GetDerivativeStatusParams extends ResolveWriteSignerOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** The derived scope whose question to observe. */\n derivedScope: string;\n /**\n * A grant covering the derived scope, sent as the signed `grantId` claim.\n * Omit only when the signer is the Personal Server's owner, who is\n * authorized without one.\n */\n grantId?: string;\n /** Reader key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n /** Aborts the request in flight. */\n signal?: AbortSignal;\n}\n\n/** What {@link waitForDerivativeStatus} polls with. */\nexport interface WaitForDerivativeStatusParams extends GetDerivativeStatusParams {\n /** Give up after this long (default 120s). */\n timeoutMs?: number;\n /**\n * Wait between polls when the server names no retry time (default 2s).\n * A `retryAfterSeconds` from the server replaces this outright, longer or\n * shorter: it is when the next compute actually happens.\n */\n pollIntervalMs?: number;\n /**\n * Aborts the wait, and the request in flight with it: the signal is passed\n * to every poll, so an abort during a stalled request does not sit until\n * the transport gives up.\n */\n signal?: AbortSignal;\n}\n\n/**\n * The request target for a status read: the query carries the derived scope,\n * the signed `uri` does not.\n *\n * @remarks\n * Like every Web3Signed read (data, lineage), the Personal Server verifies\n * the signature over the PATH; per-scope authorization is enforced live\n * against the caller's grant on each request, so the query needs no\n * signature to be safe. Only the write path signs path AND query, where a\n * parameter decides what is written.\n */\nexport function derivativeStatusTarget(derivedScope: string): string {\n return `${DERIVATIVE_STATUS_PATH}?derivedScope=${encodeURIComponent(derivedScope)}`;\n}\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nfunction resolveFetch(fetchFn: typeof fetch | undefined): typeof fetch {\n const resolved = fetchFn ?? globalThis.fetch;\n if (resolved === undefined) {\n throw new WriteRequestError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction requireDerivedScope(derivedScope: string): string {\n if (typeof derivedScope !== \"string\" || derivedScope.length === 0) {\n // The server answers 400 DERIVATIVE_DERIVED_SCOPE_REQUIRED; refuse before\n // signing rather than spend a signature on a request that cannot pass.\n throw new WriteRequestError(\"derivedScope is required\");\n }\n return derivedScope;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction timeoutError(\n latest: DerivativeStatus,\n timeoutMs: number,\n): DerivativeQuestionTimeoutError {\n return new DerivativeQuestionTimeoutError(\n `Derived scope ${latest.derivedScope} was still ${latest.status} after ${timeoutMs}ms`,\n {\n derivedScope: latest.derivedScope,\n status: latest.status,\n errorCode: latest.errorCode,\n retryAfterSeconds: latest.retryAfterSeconds,\n timeoutMs,\n },\n );\n}\n\nfunction abortError(signal?: AbortSignal): Error {\n const reason = signal?.reason;\n return reason instanceof Error\n ? reason\n : new WriteRequestError(\"Derivative status wait was aborted\");\n}\n\n/**\n * Is this a state the reader can act on?\n *\n * @remarks\n * `ready` means the derived scope has an answer to read. A `failed` status\n * is settled only when no retry is pending: with `retryAfterSeconds` set the\n * Personal Server will compute again on its own, so the answer may still\n * arrive. `pending` and `stale` are always in flight.\n */\nexport function isDerivativeStatusSettled(status: DerivativeStatus): boolean {\n if (status.status === \"ready\") return true;\n return status.status === \"failed\" && status.retryAfterSeconds === null;\n}\n\n/**\n * Read the lifecycle of the question behind a derived scope.\n *\n * @remarks\n * Sends `GET /v1/derivatives/status?derivedScope=<scope>` with a Web3Signed\n * `Authorization` header carrying `grantId`, the same authentication a data\n * read uses. Nothing is charged: the route authorizes, it does not serve\n * data, so a priced grant raises no 402 here.\n *\n * @returns The status of the derived scope. When several registrations write\n * it, the most optimistic true state answers — `ready`, then `stale`, then\n * `pending`, then `failed` — because a duplicate that never wrote anything\n * must not report away an answer the scope has.\n * @throws {DerivativeQuestionNotFoundError} 404: the caller may read the\n * scope but no question stands behind it (and, on an older Personal\n * Server, the route itself is unknown).\n * @throws {WriteForbiddenError} 403: the grant does not cover the derived\n * scope. The check runs before any store lookup, so a caller cannot probe\n * which scopes have questions.\n * @throws {DerivativeQuestionRejectedError} On any other non-2xx answer or\n * an unparseable body.\n * @throws {WriteTransportError} When `fetch` itself failed.\n * @throws {WriteRequestError} On a missing `derivedScope` or no `fetch`.\n *\n * @example\n * ```typescript\n * const status = await getDerivativeStatus({\n * personalServerUrl: \"https://ps.example.com\",\n * derivedScope: \"coach.weekly\",\n * grantId,\n * signer,\n * });\n * if (status.status === \"ready\") {\n * const record = await readPersonalServerData({ ... });\n * } else if (status.retryAfterSeconds !== null) {\n * // Computing or retrying: come back then.\n * }\n * ```\n */\nexport async function getDerivativeStatus(\n params: GetDerivativeStatusParams,\n): Promise<DerivativeStatus> {\n const derivedScope = requireDerivedScope(params.derivedScope);\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.personalServerUrl);\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: params.audience ?? baseUrl,\n method: \"GET\",\n uri: DERIVATIVE_STATUS_PATH,\n grantId: params.grantId,\n }),\n );\n\n let response: Response;\n try {\n response = await fetchFn(\n `${baseUrl}${derivativeStatusTarget(derivedScope)}`,\n {\n method: \"GET\",\n headers,\n ...(params.signal ? { signal: params.signal } : {}),\n },\n );\n } catch (err) {\n throw new WriteTransportError(\n `Derivative status read failed: ${err instanceof Error ? err.message : String(err)}`,\n 1,\n err,\n );\n }\n if (!response.ok) {\n throw (await personalServerErrorFromQuestionResponse(\n response,\n )) as PersonalServerWriteError;\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new DerivativeQuestionRejectedError(\n \"Derivative status response is not JSON\",\n response.status,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n const parsed = DerivativeStatusSchema.safeParse(body);\n if (!parsed.success) {\n throw new DerivativeQuestionRejectedError(\n \"Derivative status response is not a status view\",\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n return parsed.data;\n}\n\n/**\n * Poll {@link getDerivativeStatus} until the derived scope has an answer or\n * has stopped trying to get one.\n *\n * @remarks\n * Returns as soon as {@link isDerivativeStatusSettled} holds: `ready`, or\n * `failed` with no retry pending. A failure the server will retry is not a\n * result, so the wait continues through it — on the server's own cadence,\n * because `retryAfterSeconds` is when the next compute actually happens and\n * polling faster only spends requests. A failed status is returned, not\n * thrown: the reader branches on `errorCode`.\n *\n * @returns The settled status.\n * @throws {DerivativeQuestionTimeoutError} When the budget ran out first.\n * The question keeps computing on the server; call again.\n *\n * @example\n * ```typescript\n * const status = await waitForDerivativeStatus({\n * personalServerUrl,\n * derivedScope: \"coach.weekly\",\n * grantId,\n * signer,\n * timeoutMs: 60_000,\n * });\n * if (status.status !== \"ready\") console.log(status.errorCode);\n * ```\n */\nexport async function waitForDerivativeStatus(\n params: WaitForDerivativeStatusParams,\n): Promise<DerivativeStatus> {\n const timeoutMs = Math.max(\n 0,\n params.timeoutMs ?? DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS,\n );\n const pollIntervalMs = Math.max(\n 0,\n params.pollIntervalMs ?? DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS,\n );\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (params.signal?.aborted) throw abortError(params.signal);\n const latest = await getDerivativeStatus(params);\n if (isDerivativeStatusSettled(latest)) return latest;\n const remaining = deadline - Date.now();\n if (remaining <= 0) throw timeoutError(latest, timeoutMs);\n // The server's scheduled retry decides the cadence outright, in both\n // directions: a caller's longer pollIntervalMs would sit past a retry\n // that has already produced an answer, and a shorter one would ask\n // before anything can have changed. pollIntervalMs is the cadence for\n // the case where the server named none.\n const waitMs =\n latest.retryAfterSeconds === null\n ? pollIntervalMs\n : latest.retryAfterSeconds * 1000;\n if (waitMs > remaining) {\n // The budget cannot cover the next cadence, so the poll after this\n // sleep would land before anything could have changed. Give up now\n // instead of spending a request to say the same thing.\n throw timeoutError(latest, timeoutMs);\n }\n await sleep(waitMs, params.signal);\n }\n}\n\n/** Statuses that mean a compute is in flight. @see {@link QuestionStatus} */\nexport type PendingDerivativeStatus = Extract<\n QuestionStatus,\n \"pending\" | \"stale\"\n>;\n"],"mappings":"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","names":["DerivativeErrorCodeSchema"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var identity_exports = {};
|
|
30
|
+
__export(identity_exports, {
|
|
31
|
+
ENCLAVE_IDENTITY_EVIDENCE_VERSION: () => ENCLAVE_IDENTITY_EVIDENCE_VERSION,
|
|
32
|
+
ENCLAVE_TRUST_ANCHORS: () => ENCLAVE_TRUST_ANCHORS,
|
|
33
|
+
ENCLAVE_WALLET_PURPOSE: () => ENCLAVE_WALLET_PURPOSE,
|
|
34
|
+
MASTER_SIGNATURE_DELIVERY_VERSION: () => MASTER_SIGNATURE_DELIVERY_VERSION,
|
|
35
|
+
SEALED_ENVELOPE_VERSION: () => SEALED_ENVELOPE_VERSION,
|
|
36
|
+
USER_PS_ID_DOMAIN: () => USER_PS_ID_DOMAIN,
|
|
37
|
+
appRootPreimage: () => appRootPreimage,
|
|
38
|
+
buildMasterSignatureDelivery: () => buildMasterSignatureDelivery,
|
|
39
|
+
encryptMasterSignatureDelivery: () => encryptMasterSignatureDelivery,
|
|
40
|
+
kmsIssuedPreimage: () => kmsIssuedPreimage,
|
|
41
|
+
userPsId: () => userPsId,
|
|
42
|
+
verifyEnclaveIdentityEvidence: () => verifyEnclaveIdentityEvidence
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(identity_exports);
|
|
45
|
+
var secp256k1 = __toESM(require("@noble/secp256k1"), 1);
|
|
46
|
+
var import_viem = require("viem");
|
|
47
|
+
var import_accounts = require("viem/accounts");
|
|
48
|
+
var import_interface = require("../crypto/ecies/interface");
|
|
49
|
+
var import_derive = require("../crypto/keys/derive");
|
|
50
|
+
const ENCLAVE_IDENTITY_EVIDENCE_VERSION = 1;
|
|
51
|
+
const USER_PS_ID_DOMAIN = "vana.ps-enclave.v1";
|
|
52
|
+
const ENCLAVE_WALLET_PURPOSE = "vana.ps-enclave.wallet.v1";
|
|
53
|
+
const MASTER_SIGNATURE_DELIVERY_VERSION = "vana.ps-enclave.delivery.v1";
|
|
54
|
+
const SEALED_ENVELOPE_VERSION = 1;
|
|
55
|
+
const VANA_MAINNET_CHAIN_ID = 1480;
|
|
56
|
+
const MOKSHA_CHAIN_ID = 14800;
|
|
57
|
+
const KMS_ISSUED_PREFIX = "dstack-kms-issued";
|
|
58
|
+
const PREIMAGE_SEPARATOR = ":";
|
|
59
|
+
const UNCOMPRESSED_PUBLIC_KEY_BYTES = 65;
|
|
60
|
+
const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04";
|
|
61
|
+
const ENCLAVE_TRUST_ANCHORS = {
|
|
62
|
+
// filled at fleet provisioning; verify fails closed while empty
|
|
63
|
+
[VANA_MAINNET_CHAIN_ID]: { kmsRootPubkey: "0x", appIds: [] },
|
|
64
|
+
// filled at fleet provisioning; verify fails closed while empty
|
|
65
|
+
[MOKSHA_CHAIN_ID]: { kmsRootPubkey: "0x", appIds: [] }
|
|
66
|
+
};
|
|
67
|
+
function userPsId(chainId, ownerAddress) {
|
|
68
|
+
const packed = (0, import_viem.encodePacked)(
|
|
69
|
+
["string", "uint256", "address"],
|
|
70
|
+
[USER_PS_ID_DOMAIN, BigInt(chainId), (0, import_viem.getAddress)(ownerAddress)]
|
|
71
|
+
);
|
|
72
|
+
return (0, import_viem.keccak256)(packed);
|
|
73
|
+
}
|
|
74
|
+
function appRootPreimage(purpose, publicKey) {
|
|
75
|
+
const keyHex = publicKey.slice(2).toLowerCase();
|
|
76
|
+
return (0, import_viem.keccak256)((0, import_viem.toBytes)(`${purpose}${PREIMAGE_SEPARATOR}${keyHex}`));
|
|
77
|
+
}
|
|
78
|
+
function kmsIssuedPreimage(appId, appRootPublicKey) {
|
|
79
|
+
const appIdHex = appId.slice(2).toLowerCase();
|
|
80
|
+
const prefix = (0, import_viem.toBytes)(
|
|
81
|
+
`${KMS_ISSUED_PREFIX}${PREIMAGE_SEPARATOR}${appIdHex}`
|
|
82
|
+
);
|
|
83
|
+
const compressed = secp256k1.ProjectivePoint.fromHex(
|
|
84
|
+
(0, import_viem.fromHex)(appRootPublicKey, "bytes")
|
|
85
|
+
).toRawBytes(true);
|
|
86
|
+
return (0, import_viem.keccak256)((0, import_viem.concat)([prefix, compressed]));
|
|
87
|
+
}
|
|
88
|
+
async function recoverChainKey(hash, signature, link) {
|
|
89
|
+
try {
|
|
90
|
+
return (await (0, import_viem.recoverPublicKey)({ hash, signature })).toLowerCase();
|
|
91
|
+
} catch {
|
|
92
|
+
throw new Error(`Invalid enclave signature chain link ${link}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function verifyEnclaveIdentityEvidence(evidence, anchors) {
|
|
96
|
+
if (evidence.v !== ENCLAVE_IDENTITY_EVIDENCE_VERSION) {
|
|
97
|
+
throw new Error("Unsupported enclave identity evidence version");
|
|
98
|
+
}
|
|
99
|
+
if (!Number.isInteger(evidence.epoch) || evidence.epoch < 1) {
|
|
100
|
+
throw new Error("Invalid enclave identity epoch");
|
|
101
|
+
}
|
|
102
|
+
const derivedAddress = (0, import_accounts.publicKeyToAddress)(evidence.publicKey);
|
|
103
|
+
if ((0, import_viem.getAddress)(derivedAddress) !== (0, import_viem.getAddress)(evidence.address)) {
|
|
104
|
+
throw new Error("Enclave public key does not match its address");
|
|
105
|
+
}
|
|
106
|
+
const appRootPublicKey = await recoverChainKey(
|
|
107
|
+
appRootPreimage(evidence.purpose, evidence.publicKey),
|
|
108
|
+
evidence.signatureChain[0],
|
|
109
|
+
0
|
|
110
|
+
);
|
|
111
|
+
const kmsRootPublicKey = await recoverChainKey(
|
|
112
|
+
kmsIssuedPreimage(evidence.appId, appRootPublicKey),
|
|
113
|
+
evidence.signatureChain[1],
|
|
114
|
+
1
|
|
115
|
+
);
|
|
116
|
+
if (anchors.kmsRootPubkey === "0x") {
|
|
117
|
+
throw new Error("KMS root trust anchor is not provisioned");
|
|
118
|
+
}
|
|
119
|
+
if (kmsRootPublicKey !== anchors.kmsRootPubkey.toLowerCase()) {
|
|
120
|
+
throw new Error("KMS root public key does not match the trust anchor");
|
|
121
|
+
}
|
|
122
|
+
if ((0, import_viem.keccak256)(kmsRootPublicKey) !== evidence.kmsRootFingerprint.toLowerCase()) {
|
|
123
|
+
throw new Error("KMS root fingerprint does not match the evidence");
|
|
124
|
+
}
|
|
125
|
+
const appId = evidence.appId.toLowerCase();
|
|
126
|
+
if (!anchors.appIds.some((allowedAppId) => allowedAppId.toLowerCase() === appId)) {
|
|
127
|
+
throw new Error("Enclave app ID is not trusted");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function buildMasterSignatureDelivery(evidence, masterSignature, now = Math.floor(Date.now() / 1e3)) {
|
|
131
|
+
(0, import_derive.deriveMasterKey)(masterSignature);
|
|
132
|
+
return {
|
|
133
|
+
v: MASTER_SIGNATURE_DELIVERY_VERSION,
|
|
134
|
+
userPsId: evidence.userPsId,
|
|
135
|
+
epoch: evidence.epoch,
|
|
136
|
+
enclaveAddress: evidence.address,
|
|
137
|
+
ownerAddress: evidence.ownerAddress,
|
|
138
|
+
masterSignature,
|
|
139
|
+
issuedAt: now
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function assertUncompressedKey(publicKey) {
|
|
143
|
+
const hex = publicKey.slice(2);
|
|
144
|
+
if (hex.length !== UNCOMPRESSED_PUBLIC_KEY_BYTES * 2 || !hex.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) || !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
145
|
+
throw new Error("Public key must be a 65-byte uncompressed secp256k1 key");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function encryptMasterSignatureDelivery(delivery, publicKey, ecies) {
|
|
149
|
+
assertUncompressedKey(publicKey);
|
|
150
|
+
const plaintext = (0, import_viem.toBytes)(JSON.stringify(delivery));
|
|
151
|
+
const encrypted = await ecies.encrypt((0, import_viem.fromHex)(publicKey, "bytes"), plaintext);
|
|
152
|
+
return `0x${(0, import_interface.serializeECIES)(encrypted)}`;
|
|
153
|
+
}
|
|
154
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
155
|
+
0 && (module.exports = {
|
|
156
|
+
ENCLAVE_IDENTITY_EVIDENCE_VERSION,
|
|
157
|
+
ENCLAVE_TRUST_ANCHORS,
|
|
158
|
+
ENCLAVE_WALLET_PURPOSE,
|
|
159
|
+
MASTER_SIGNATURE_DELIVERY_VERSION,
|
|
160
|
+
SEALED_ENVELOPE_VERSION,
|
|
161
|
+
USER_PS_ID_DOMAIN,
|
|
162
|
+
appRootPreimage,
|
|
163
|
+
buildMasterSignatureDelivery,
|
|
164
|
+
encryptMasterSignatureDelivery,
|
|
165
|
+
kmsIssuedPreimage,
|
|
166
|
+
userPsId,
|
|
167
|
+
verifyEnclaveIdentityEvidence
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=identity.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/identity.ts"],"sourcesContent":["/**\n * Enclave identity and consent primitives for Personal Servers.\n *\n * Owner/Web -> Gateway -> Agent(CVM): derive wallet and quote\n * Web verifies evidence; Account encrypts the EIP-191 master signature to the\n * enclave public key; Gateway relays it blind; Agent decrypts and seals it.\n *\n * @category Protocol\n */\n\nimport * as secp256k1 from \"@noble/secp256k1\";\nimport {\n concat,\n encodePacked,\n fromHex,\n getAddress,\n keccak256,\n recoverPublicKey,\n toBytes,\n type Address,\n type Hex,\n} from \"viem\";\nimport { publicKeyToAddress } from \"viem/accounts\";\nimport { serializeECIES, type ECIESProvider } from \"../crypto/ecies/interface\";\nimport { deriveMasterKey } from \"../crypto/keys/derive\";\nimport type { ServerRegistrationMessage } from \"./eip712\";\n\nexport const ENCLAVE_IDENTITY_EVIDENCE_VERSION = 1;\nexport const USER_PS_ID_DOMAIN = \"vana.ps-enclave.v1\";\nexport const ENCLAVE_WALLET_PURPOSE = \"vana.ps-enclave.wallet.v1\";\nexport const MASTER_SIGNATURE_DELIVERY_VERSION = \"vana.ps-enclave.delivery.v1\";\nexport const SEALED_ENVELOPE_VERSION = 1;\n\nconst VANA_MAINNET_CHAIN_ID = 1480;\nconst MOKSHA_CHAIN_ID = 14800;\nconst KMS_ISSUED_PREFIX = \"dstack-kms-issued\";\nconst PREIMAGE_SEPARATOR = \":\";\nconst UNCOMPRESSED_PUBLIC_KEY_BYTES = 65;\nconst UNCOMPRESSED_PUBLIC_KEY_PREFIX = \"04\";\n\nexport type UserPsId = Hex;\n\nexport interface EnclaveIdentityEvidence {\n v: typeof ENCLAVE_IDENTITY_EVIDENCE_VERSION;\n userPsId: UserPsId;\n chainId: number;\n ownerAddress: Address;\n epoch: number;\n address: Address;\n publicKey: Hex;\n appId: Hex;\n composeHash: Hex;\n osImageHash?: Hex;\n purpose: string;\n signatureChain: [Hex, Hex];\n quote: Hex;\n eventLog?: string;\n kmsRootFingerprint: Hex;\n}\n\nexport interface IdentityRequest {\n ownerAddress: Address;\n chainId: number;\n}\n\nexport type IdentityState = \"prepared\" | \"registered\" | \"sealed\" | \"retired\";\n\nexport interface IdentityResponse {\n identity: EnclaveIdentityEvidence;\n state: IdentityState;\n created: boolean;\n serverUrl: string;\n serverId?: Hex;\n serverStatus?:\n | \"pending\"\n | \"submitting\"\n | \"confirmed\"\n | \"finalized\"\n | \"failed\";\n sealed: boolean;\n}\n\nexport type IdentityRegistrationRequest =\n | { version: \"v2\"; message: ServerRegistrationMessage }\n | {\n version: \"v3\";\n message: ServerRegistrationMessage & { nonce: string; deadline: string };\n };\n\nexport interface IdentityRegistrationResponse {\n serverId: Hex;\n state: \"registered\";\n serverStatus: \"pending\";\n}\n\n/** Inner plaintext of the ECIES box. */\nexport interface MasterSignatureDelivery {\n v: typeof MASTER_SIGNATURE_DELIVERY_VERSION;\n userPsId: UserPsId;\n epoch: number;\n enclaveAddress: Address;\n ownerAddress: Address;\n masterSignature: Hex;\n issuedAt: number;\n}\n\nexport interface SealedSecretSubmission {\n userPsId: UserPsId;\n epoch: number;\n enclaveAddress: Address;\n ciphertext: Hex;\n}\n\nexport interface SealedSecretResponse {\n sealed: true;\n secretHash: Hex;\n sealedAt: string;\n}\n\nexport interface AesGcmBox {\n iv: string;\n ciphertext: string;\n tag: string;\n}\n\n/** Persisted enclave envelope; the Gateway treats it as opaque text. */\nexport interface SealedEnvelope extends AesGcmBox {\n v: typeof SEALED_ENVELOPE_VERSION;\n wrappedContentKey: AesGcmBox;\n}\n\nexport interface EnclaveTrustAnchors {\n kmsRootPubkey: Hex;\n appIds: Hex[];\n}\n\n/** Fleet-provisioned trust anchors keyed by Vana chain ID. */\nexport const ENCLAVE_TRUST_ANCHORS: Record<number, EnclaveTrustAnchors> = {\n // filled at fleet provisioning; verify fails closed while empty\n [VANA_MAINNET_CHAIN_ID]: { kmsRootPubkey: \"0x\", appIds: [] },\n // filled at fleet provisioning; verify fails closed while empty\n [MOKSHA_CHAIN_ID]: { kmsRootPubkey: \"0x\", appIds: [] },\n};\n\n/**\n * Derives the deterministic Personal Server ID used by enclave paths.\n *\n * @param chainId - Vana chain ID.\n * @param ownerAddress - Checksummed owner address.\n * @returns The path-compatible keccak256 digest.\n */\nexport function userPsId(chainId: number, ownerAddress: Address): UserPsId {\n const packed = encodePacked(\n [\"string\", \"uint256\", \"address\"],\n [USER_PS_ID_DOMAIN, BigInt(chainId), getAddress(ownerAddress)],\n );\n\n return keccak256(packed);\n}\n\n/**\n * Builds keccak256(purpose || \":\" || hex(pubkey)) for signature-chain link 0.\n *\n * ASSUMPTION: hex(pubkey) is lowercase, without 0x, for the 65-byte key.\n * TODO(verify-against-dstack-vector): pin with a captured CVM chain\n * (dstack kms/src/crypto.rs).\n */\nexport function appRootPreimage(purpose: string, publicKey: Hex): Hex {\n const keyHex = publicKey.slice(2).toLowerCase();\n return keccak256(toBytes(`${purpose}${PREIMAGE_SEPARATOR}${keyHex}`));\n}\n\n/**\n * Builds the dstack KMS issuance digest for signature-chain link 1.\n *\n * ASSUMPTION: appId is lowercase hex without 0x encoded as UTF-8; the app-root\n * key is appended as 33 raw SEC1-compressed bytes.\n * TODO(verify-against-dstack-vector): pin with a captured CVM chain.\n */\nexport function kmsIssuedPreimage(appId: Hex, appRootPublicKey: Hex): Hex {\n const appIdHex = appId.slice(2).toLowerCase();\n const prefix = toBytes(\n `${KMS_ISSUED_PREFIX}${PREIMAGE_SEPARATOR}${appIdHex}`,\n );\n const compressed = secp256k1.ProjectivePoint.fromHex(\n fromHex(appRootPublicKey, \"bytes\"),\n ).toRawBytes(true);\n\n return keccak256(concat([prefix, compressed]));\n}\n\nasync function recoverChainKey(\n hash: Hex,\n signature: Hex,\n link: number,\n): Promise<Hex> {\n try {\n return (await recoverPublicKey({ hash, signature })).toLowerCase() as Hex;\n } catch {\n throw new Error(`Invalid enclave signature chain link ${link}`);\n }\n}\n\n/**\n * Verifies the enclave wallet binding against fleet-provisioned trust anchors.\n *\n * @param evidence - Identity evidence returned by the enclave agent.\n * @param anchors - Pinned KMS root and allowed dstack app IDs.\n * @throws When any identity binding or trust check fails.\n */\nexport async function verifyEnclaveIdentityEvidence(\n evidence: EnclaveIdentityEvidence,\n anchors: EnclaveTrustAnchors,\n): Promise<void> {\n if (evidence.v !== ENCLAVE_IDENTITY_EVIDENCE_VERSION) {\n throw new Error(\"Unsupported enclave identity evidence version\");\n }\n\n if (!Number.isInteger(evidence.epoch) || evidence.epoch < 1) {\n throw new Error(\"Invalid enclave identity epoch\");\n }\n\n const derivedAddress = publicKeyToAddress(evidence.publicKey);\n\n if (getAddress(derivedAddress) !== getAddress(evidence.address)) {\n throw new Error(\"Enclave public key does not match its address\");\n }\n\n const appRootPublicKey = await recoverChainKey(\n appRootPreimage(evidence.purpose, evidence.publicKey),\n evidence.signatureChain[0],\n 0,\n );\n const kmsRootPublicKey = await recoverChainKey(\n kmsIssuedPreimage(evidence.appId, appRootPublicKey),\n evidence.signatureChain[1],\n 1,\n );\n\n if (anchors.kmsRootPubkey === \"0x\") {\n throw new Error(\"KMS root trust anchor is not provisioned\");\n }\n\n if (kmsRootPublicKey !== anchors.kmsRootPubkey.toLowerCase()) {\n throw new Error(\"KMS root public key does not match the trust anchor\");\n }\n\n if (\n keccak256(kmsRootPublicKey) !== evidence.kmsRootFingerprint.toLowerCase()\n ) {\n throw new Error(\"KMS root fingerprint does not match the evidence\");\n }\n\n const appId = evidence.appId.toLowerCase();\n if (\n !anchors.appIds.some((allowedAppId) => allowedAppId.toLowerCase() === appId)\n ) {\n throw new Error(\"Enclave app ID is not trusted\");\n }\n}\n\n/**\n * Builds the authenticated master-signature plaintext delivered to an enclave.\n *\n * @param evidence - Verified enclave identity evidence.\n * @param masterSignature - EIP-191 signature over the master-key message.\n * @param now - Issuance time in Unix seconds.\n * @returns The plaintext delivery object.\n */\nexport function buildMasterSignatureDelivery(\n evidence: EnclaveIdentityEvidence,\n masterSignature: Hex,\n now = Math.floor(Date.now() / 1000),\n): MasterSignatureDelivery {\n deriveMasterKey(masterSignature);\n\n return {\n v: MASTER_SIGNATURE_DELIVERY_VERSION,\n userPsId: evidence.userPsId,\n epoch: evidence.epoch,\n enclaveAddress: evidence.address,\n ownerAddress: evidence.ownerAddress,\n masterSignature,\n issuedAt: now,\n };\n}\n\nfunction assertUncompressedKey(publicKey: Hex): void {\n const hex = publicKey.slice(2);\n if (\n hex.length !== UNCOMPRESSED_PUBLIC_KEY_BYTES * 2 ||\n !hex.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) ||\n !/^[0-9a-fA-F]+$/.test(hex)\n ) {\n throw new Error(\"Public key must be a 65-byte uncompressed secp256k1 key\");\n }\n}\n\n/**\n * Encrypts a master-signature delivery in the SDK's ECIES wire format.\n *\n * @param delivery - Plaintext delivery object.\n * @param publicKey - Enclave 65-byte uncompressed public key.\n * @param ecies - Platform-specific ECIES provider.\n * @returns The 0x-prefixed serialized ciphertext.\n */\nexport async function encryptMasterSignatureDelivery(\n delivery: MasterSignatureDelivery,\n publicKey: Hex,\n ecies: ECIESProvider,\n): Promise<Hex> {\n assertUncompressedKey(publicKey);\n\n const plaintext = toBytes(JSON.stringify(delivery));\n const encrypted = await ecies.encrypt(fromHex(publicKey, \"bytes\"), plaintext);\n\n return `0x${serializeECIES(encrypted)}`;\n}\n"],"mappings":"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","names":[]}
|