@tinycloud/node-sdk 3.9.0-beta.3 → 3.9.0-beta.5

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.
@@ -24,14 +24,57 @@ __export(share_receive_exports, {
24
24
  requestAgentShareEmailProof: () => requestAgentShareEmailProof
25
25
  });
26
26
  module.exports = __toCommonJS(share_receive_exports);
27
- var import_sdk_core5 = require("@tinycloud/sdk-core");
27
+ var import_sdk_core7 = require("@tinycloud/sdk-core");
28
28
  var import_share_sdk2 = require("@tinycloud/share-sdk");
29
29
 
30
30
  // src/credentials/email-proof.ts
31
- var import_sdk_core3 = require("@tinycloud/sdk-core");
31
+ var import_sdk_core5 = require("@tinycloud/sdk-core");
32
+ var import_held_credentials = require("@tinycloud/sdk-core/held-credentials");
32
33
 
33
34
  // src/credentials/interpreter.ts
35
+ var import_sdk_core3 = require("@tinycloud/sdk-core");
36
+
37
+ // src/credentials/held-binding.ts
38
+ var import_sdk_core2 = require("@tinycloud/sdk-core");
39
+
40
+ // src/credentials/pins.ts
34
41
  var import_sdk_core = require("@tinycloud/sdk-core");
42
+ var EMAIL_PROOF_PINS = Object.freeze({
43
+ witnessOrigin: "https://witness.credentials.org",
44
+ issuerDid: "did:web:issuer.credentials.org",
45
+ issuerKid: "did:web:issuer.credentials.org#controller",
46
+ issuerX: "eEJI4xEobto4HtQ7Pg9R1vBOwcpfGRTlXDG5QfqLnGQ",
47
+ descriptorDigest: "1tg-qphmKBVtNwzVg9xyz-xxqt_xtMXAsQyXw46m8S0"
48
+ });
49
+ var HELD_ACCOUNT_BINDING_PINS = Object.freeze({
50
+ type: import_sdk_core.ACCOUNT_HOLDER_BINDING_DOMAIN,
51
+ protocol: "tinycloud.credentials/acquisition/v1",
52
+ profile: "tinycloud.email-held/v1",
53
+ profileVersion: 1,
54
+ descriptorDigest: "dc3_DuvzipXwYkTfjKqTaK1c3VS0YSPnaBsYSQriC0w",
55
+ issuer: EMAIL_PROOF_PINS.issuerDid,
56
+ issuerKid: EMAIL_PROOF_PINS.issuerKid,
57
+ audience: "tinycloud://credentials",
58
+ openerOrigin: "https://localhost",
59
+ completionOrigin: "https://localhost",
60
+ completionContext: "sdk-acquisition",
61
+ credentialLifetimeSeconds: 31536e3
62
+ });
63
+
64
+ // src/credentials/held-binding.ts
65
+ var REQUEST_TTL_MS = 6e5;
66
+ var substituted = () => new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was substituted");
67
+ async function assertHeldBinding(binding, expected) {
68
+ const { descriptor } = expected;
69
+ if (descriptor.holderBinding.alg !== "EIP191" || binding.type !== descriptor.holderBinding.domain || binding.requestId !== expected.requestId || binding.descriptorDigest !== expected.descriptorDigest || binding.requirementDigest !== expected.requirementDigest || binding.holderDid !== expected.holderDid || binding.issuer !== descriptor.issuer.did || binding.issuerKid !== descriptor.issuer.kid || binding.profile !== descriptor.profile || binding.openerOrigin !== expected.openerOrigin || binding.completionOrigin !== expected.openerOrigin || binding.audience !== HELD_ACCOUNT_BINDING_PINS.audience || binding.completionContext !== HELD_ACCOUNT_BINDING_PINS.completionContext || // The static fields, against the one frozen object the request body and the account signer also use (A2.4).
70
+ binding.type !== HELD_ACCOUNT_BINDING_PINS.type || binding.protocol !== HELD_ACCOUNT_BINDING_PINS.protocol || binding.profile !== HELD_ACCOUNT_BINDING_PINS.profile || binding.profileVersion !== HELD_ACCOUNT_BINDING_PINS.profileVersion || binding.descriptorDigest !== HELD_ACCOUNT_BINDING_PINS.descriptorDigest || binding.issuer !== HELD_ACCOUNT_BINDING_PINS.issuer || binding.issuerKid !== HELD_ACCOUNT_BINDING_PINS.issuerKid || binding.openerOrigin !== HELD_ACCOUNT_BINDING_PINS.openerOrigin || binding.completionOrigin !== HELD_ACCOUNT_BINDING_PINS.completionOrigin || binding.credentialLifetimeSeconds !== HELD_ACCOUNT_BINDING_PINS.credentialLifetimeSeconds) throw substituted();
71
+ if (expected.challengeNonce === void 0 || binding.challengeNonce !== expected.challengeNonce) throw substituted();
72
+ if (binding.normalizedClaimsDigest !== await (0, import_sdk_core2.canonicalDigest)(expected.requirement.claims)) throw substituted();
73
+ const expires = Date.parse(binding.expiresAt);
74
+ if (expected.serverExpiresAt === void 0 || expires !== Date.parse(expected.serverExpiresAt) || Date.parse(binding.issuedAt) !== expires - REQUEST_TTL_MS) throw substituted();
75
+ }
76
+
77
+ // src/credentials/interpreter.ts
35
78
  var delay = (ms, signal) => new Promise((resolve, reject) => {
36
79
  const timer = setTimeout(resolve, ms);
37
80
  signal?.addEventListener("abort", () => {
@@ -40,18 +83,36 @@ var delay = (ms, signal) => new Promise((resolve, reject) => {
40
83
  }, { once: true });
41
84
  });
42
85
  async function interpretCredentialFlow(input) {
86
+ const held = input.descriptor.subjectRelationship === "account_is_subject";
87
+ if (held && input.allowAccountSubject !== true) throw new import_sdk_core3.CredentialError("UNSUPPORTED_PROFILE", "The held credential profile requires an explicit opt-in");
43
88
  const steps = new Map(input.descriptor.steps.map((step) => [step.type, step]));
44
89
  const completed = /* @__PURE__ */ new Set();
45
90
  let lastTransition = "";
91
+ const checkBinding = async (value) => {
92
+ const binding = (0, import_sdk_core3.validateCredentialHolderBinding)(value);
93
+ if (binding.requestId !== input.requestId || binding.descriptorDigest !== input.descriptorDigest || binding.requirementDigest !== input.requirementDigest || binding.holderDid !== input.holderDid || binding.issuer !== input.descriptor.issuer.did || binding.issuerKid !== input.descriptor.issuer.kid || binding.profile !== input.descriptor.profile || binding.openerOrigin !== input.openerOrigin || binding.completionOrigin !== input.openerOrigin || binding.audience !== "tinycloud://credentials") throw new import_sdk_core3.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was substituted");
94
+ const alg = input.descriptor.holderBinding.alg;
95
+ if (binding.type !== input.descriptor.holderBinding.domain || alg === "EIP191" && !input.transport.challengeNonce) throw new import_sdk_core3.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was substituted");
96
+ if (alg === "EIP191") await assertHeldBinding(binding, { descriptor: input.descriptor, requirement: input.requirement, requestId: input.requestId, descriptorDigest: input.descriptorDigest, requirementDigest: input.requirementDigest, holderDid: input.holderDid, openerOrigin: input.openerOrigin, challengeNonce: input.transport.challengeNonce(input.requestId), serverExpiresAt: input.expiresAt });
97
+ return binding;
98
+ };
99
+ const ensureHeldBinding = async () => {
100
+ if (!held || input.transport.signedBinding?.(input.requestId) !== void 0) return;
101
+ await checkBinding(await input.transport.holderBinding(input.requestId, input.verifier, input.signal));
102
+ };
46
103
  for (; ; ) {
47
104
  input.signal?.throwIfAborted();
48
105
  const state = await input.transport.state(input.requestId, input.verifier, input.signal);
49
- if (state.requestId !== input.requestId) throw new import_sdk_core.CredentialError("REQUEST_SUBSTITUTED", "Credential request identity changed");
50
- if (state.state === "expired") throw new import_sdk_core.CredentialError("REQUEST_EXPIRED", "Credential request expired", { state: state.state, correlationId: state.correlationId });
51
- if (state.state === "canceled") throw new import_sdk_core.CredentialError("CANCELED", "Credential request was canceled", { state: state.state, correlationId: state.correlationId });
52
- if (state.state === "issuer_unready") throw new import_sdk_core.CredentialError("ISSUER_UNREADY", "Credential issuer is not ready", { retryAfterMs: state.retryAfterMs, state: state.state, correlationId: state.correlationId });
53
- if (state.state === "complete") return;
106
+ if (state.requestId !== input.requestId) throw new import_sdk_core3.CredentialError("REQUEST_SUBSTITUTED", "Credential request identity changed");
107
+ if (state.state === "expired") throw new import_sdk_core3.CredentialError("REQUEST_EXPIRED", "Credential request expired", { state: state.state, correlationId: state.correlationId });
108
+ if (state.state === "canceled") throw new import_sdk_core3.CredentialError("CANCELED", "Credential request was canceled", { state: state.state, correlationId: state.correlationId });
109
+ if (state.state === "issuer_unready") throw new import_sdk_core3.CredentialError("ISSUER_UNREADY", "Credential issuer is not ready", { retryAfterMs: state.retryAfterMs, state: state.state, correlationId: state.correlationId });
110
+ if (state.state === "complete") {
111
+ await ensureHeldBinding();
112
+ return;
113
+ }
54
114
  if (state.state === "ready_to_issue" || state.state === "issued") {
115
+ await ensureHeldBinding();
55
116
  if (state.state === "ready_to_issue" && !completed.has("issue")) {
56
117
  await input.transport.issue(input.requestId, input.verifier, input.signal);
57
118
  completed.add("issue");
@@ -64,7 +125,7 @@ async function interpretCredentialFlow(input) {
64
125
  continue;
65
126
  }
66
127
  const declared = steps.get(next.type);
67
- if (next.id !== next.type || !declared || declared.type !== next.type || declared.version !== next.version) throw new import_sdk_core.CredentialError("REQUEST_SUBSTITUTED", "Server selected an undeclared credential step");
128
+ if (next.id !== next.type || !declared || declared.type !== next.type || declared.version !== next.version) throw new import_sdk_core3.CredentialError("REQUEST_SUBSTITUTED", "Server selected an undeclared credential step");
68
129
  if (completed.has(next.id) || state.transitionId === lastTransition) {
69
130
  await (input.onWait?.() ?? delay(state.retryAfterMs ?? 50, input.signal));
70
131
  continue;
@@ -72,21 +133,22 @@ async function interpretCredentialFlow(input) {
72
133
  lastTransition = state.transitionId;
73
134
  if (next.type === "holder_signature") {
74
135
  input.onProgress?.({ state: "signing", stepId: next.id, correlationId: state.correlationId });
75
- const binding = (0, import_sdk_core.validateCredentialHolderBinding)(await input.transport.holderBinding(input.requestId, input.verifier, input.signal));
76
- if (binding.requestId !== input.requestId || binding.descriptorDigest !== input.descriptorDigest || binding.requirementDigest !== input.requirementDigest || binding.holderDid !== input.holderDid || binding.issuer !== input.descriptor.issuer.did || binding.issuerKid !== input.descriptor.issuer.kid || binding.profile !== input.descriptor.profile || binding.openerOrigin !== input.openerOrigin || binding.completionOrigin !== input.openerOrigin || binding.audience !== "tinycloud://credentials") throw new import_sdk_core.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was substituted");
77
- const bytes = (0, import_sdk_core.holderBindingSigningBytes)(binding);
136
+ const binding = await checkBinding(await input.transport.holderBinding(input.requestId, input.verifier, input.signal));
137
+ const alg = input.descriptor.holderBinding.alg;
138
+ if (alg === "EIP191" !== (input.signing.holderKey?.alg === "EIP191")) throw new import_sdk_core3.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was substituted");
139
+ const bytes = (0, import_sdk_core3.holderBindingSigningBytes)(binding);
78
140
  let signature = await input.signing.autoSign?.(binding, bytes, input.signal);
79
141
  if (signature === void 0) {
80
- if (!input.signing.requestApproval) throw new import_sdk_core.CredentialError("SIGNATURE_REJECTED", "OpenKey approval is required");
142
+ if (!input.signing.requestApproval) throw new import_sdk_core3.CredentialError("SIGNATURE_REJECTED", "OpenKey approval is required");
81
143
  try {
82
144
  signature = await input.signing.requestApproval(binding, bytes, input.signal);
83
145
  } catch (cause) {
84
- if (cause instanceof import_sdk_core.CredentialError) throw cause;
85
- throw new import_sdk_core.CredentialError("SIGNATURE_REJECTED", "OpenKey approval was rejected", { cause });
146
+ if (cause instanceof import_sdk_core3.CredentialError) throw cause;
147
+ throw new import_sdk_core3.CredentialError("SIGNATURE_REJECTED", "OpenKey approval was rejected", { cause });
86
148
  }
87
149
  }
88
- if (!(signature instanceof Uint8Array) || signature.length === 0) throw new import_sdk_core.CredentialError("SIGNATURE_REJECTED", "OpenKey signature is invalid");
89
- await input.transport.submitHolderSignature(input.requestId, input.verifier, (0, import_sdk_core.encodeBase64Url)(signature), input.signal, input.signing.holderKey);
150
+ if (!(signature instanceof Uint8Array) || signature.length === 0) throw new import_sdk_core3.CredentialError("SIGNATURE_REJECTED", "OpenKey signature is invalid");
151
+ await input.transport.submitHolderSignature(input.requestId, input.verifier, alg === "EIP191" ? `0x${Array.from(signature, (byte) => byte.toString(16).padStart(2, "0")).join("")}` : (0, import_sdk_core3.encodeBase64Url)(signature), input.signal, input.signing.holderKey);
90
152
  } else {
91
153
  input.onProgress?.({ state: next.type === "collect_input" ? "collecting" : "proving", stepId: next.id, correlationId: state.correlationId });
92
154
  const handler = input.handlers?.[next.type];
@@ -120,9 +182,9 @@ async function interpretCredentialFlow(input) {
120
182
  await input.transport.submitStep(input.requestId, input.verifier, next.id, proof, input.signal);
121
183
  break;
122
184
  } catch (cause) {
123
- if (!(cause instanceof import_sdk_core.CredentialError) || cause.code !== "PROOF_REJECTED") throw cause;
185
+ if (!(cause instanceof import_sdk_core3.CredentialError) || cause.code !== "PROOF_REJECTED") throw cause;
124
186
  const attemptsRemaining = input.descriptor.lifecycle.maxProofAttempts - attempt;
125
- if (next.type !== "mailbox_otp" || attemptsRemaining <= 0) throw new import_sdk_core.CredentialError("VERIFICATION_FAILED", "The proof was not accepted", { state: attemptsRemaining <= 0 ? "proof_attempts_exhausted" : "proof_rejected", cause });
187
+ if (next.type !== "mailbox_otp" || attemptsRemaining <= 0) throw new import_sdk_core3.CredentialError("VERIFICATION_FAILED", "The proof was not accepted", { state: attemptsRemaining <= 0 ? "proof_attempts_exhausted" : "proof_rejected", cause });
126
188
  feedback = { kind: "rejected", attemptsRemaining };
127
189
  }
128
190
  }
@@ -133,27 +195,27 @@ async function interpretCredentialFlow(input) {
133
195
  }
134
196
 
135
197
  // src/credentials/transport.ts
136
- var import_sdk_core2 = require("@tinycloud/sdk-core");
198
+ var import_sdk_core4 = require("@tinycloud/sdk-core");
137
199
  function object(value, label) {
138
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", `${label} is invalid`);
200
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", `${label} is invalid`);
139
201
  return value;
140
202
  }
141
203
  function opaque(value, label) {
142
- if (typeof value !== "string" || !/^[A-Za-z0-9_-]{16,128}$/.test(value)) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", `${label} is invalid`);
204
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]{16,128}$/.test(value)) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", `${label} is invalid`);
143
205
  return value;
144
206
  }
145
207
  function timestamp(value, label) {
146
- if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", `${label} is invalid`);
208
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", `${label} is invalid`);
147
209
  return new Date(value).toISOString();
148
210
  }
149
211
  function jwtPayload(credential) {
150
212
  const part = credential.split("~", 1)[0]?.split(".")[1];
151
- if (!part) throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "Issued SD-JWT is invalid");
213
+ if (!part) throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "Issued SD-JWT is invalid");
152
214
  try {
153
- return object(JSON.parse(new TextDecoder().decode((0, import_sdk_core2.decodeBase64Url)(part))), "SD-JWT payload");
215
+ return object(JSON.parse(new TextDecoder().decode((0, import_sdk_core4.decodeBase64Url)(part))), "SD-JWT payload");
154
216
  } catch (cause) {
155
- if (cause instanceof import_sdk_core2.CredentialError) throw cause;
156
- throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "Issued SD-JWT is invalid", { cause });
217
+ if (cause instanceof import_sdk_core4.CredentialError) throw cause;
218
+ throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "Issued SD-JWT is invalid", { cause });
157
219
  }
158
220
  }
159
221
  var SERVER_CODES = /* @__PURE__ */ new Set(["REQUEST_EXPIRED", "ISSUER_UNREADY", "UNSUPPORTED_PROFILE", "UNSUPPORTED_VERSION", "SIGNATURE_REJECTED"]);
@@ -162,12 +224,13 @@ function serverError(value, response, honorServerRecoverable) {
162
224
  const body = value;
163
225
  const keys = Object.keys(body).sort();
164
226
  if (keys.join(",") !== "code,correlationId,recoverable,state,type" || body.type !== "tinycloud.credentials/error/v1" || typeof body.code !== "string" || typeof body.recoverable !== "boolean" || typeof body.state !== "string" || typeof body.correlationId !== "string") return void 0;
165
- if (body.code === "PROOF_REJECTED") return body.recoverable ? new import_sdk_core2.CredentialError("PROOF_REJECTED", "The proof was not accepted", { state: body.state, correlationId: body.correlationId }) : new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "The proof attempt limit was reached", { state: "proof_attempts_exhausted", correlationId: body.correlationId });
166
- if (body.code === "RATE_LIMITED") return new import_sdk_core2.CredentialError("ISSUER_UNREADY", "Too many verification codes were requested", { state: "rate_limited", correlationId: body.correlationId, retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0, recoverable: honorServerRecoverable ? body.recoverable : void 0 });
167
- if (body.code === "CHALLENGE_EXPIRED") return new import_sdk_core2.CredentialError("REQUEST_EXPIRED", "The credential challenge expired", { state: body.state, correlationId: body.correlationId, recoverable: honorServerRecoverable ? body.recoverable : void 0 });
227
+ if (body.code === "PROOF_REJECTED") return body.recoverable ? new import_sdk_core4.CredentialError("PROOF_REJECTED", "The proof was not accepted", { state: body.state, correlationId: body.correlationId }) : new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "The proof attempt limit was reached", { state: "proof_attempts_exhausted", correlationId: body.correlationId });
228
+ if (body.code === "RATE_LIMITED") return new import_sdk_core4.CredentialError("ISSUER_UNREADY", "Too many verification codes were requested", { state: "rate_limited", correlationId: body.correlationId, retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0, recoverable: honorServerRecoverable ? body.recoverable : void 0 });
229
+ if (body.code === "CHALLENGE_EXPIRED") return new import_sdk_core4.CredentialError("REQUEST_EXPIRED", "The credential challenge expired", { state: body.state, correlationId: body.correlationId, recoverable: honorServerRecoverable ? body.recoverable : void 0 });
230
+ if (body.code === "DESCRIPTOR_MISMATCH") body.code = "UNSUPPORTED_PROFILE";
168
231
  if (!SERVER_CODES.has(body.code)) return void 0;
169
232
  const code = body.code;
170
- return new import_sdk_core2.CredentialError(code, `OpenCredentials rejected the request: ${code}`, {
233
+ return new import_sdk_core4.CredentialError(code, `OpenCredentials rejected the request: ${code}`, {
171
234
  state: body.state,
172
235
  correlationId: body.correlationId,
173
236
  retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0,
@@ -175,15 +238,20 @@ function serverError(value, response, honorServerRecoverable) {
175
238
  });
176
239
  }
177
240
  var OpenCredentialsHttpTransport = class {
241
+ /**
242
+ * `allowAccountSubject` is passed only by the email-proof functions, after the per-call held opt-in was accepted. Without it an account-bound
243
+ * (held) descriptor is refused here, before any request (TC-2254 A2.5).
244
+ */
178
245
  constructor(descriptor, fetchFn = globalThis.fetch.bind(globalThis), options = {}) {
179
246
  this.descriptor = descriptor;
180
247
  this.fetchFn = fetchFn;
181
248
  this.options = options;
182
249
  this.nonces = /* @__PURE__ */ new Map();
183
250
  this.bindings = /* @__PURE__ */ new Map();
251
+ if (descriptor.subjectRelationship === "account_is_subject" && options.allowAccountSubject !== true) throw new import_sdk_core4.CredentialError("UNSUPPORTED_PROFILE", "The held credential profile requires an explicit opt-in");
184
252
  }
185
253
  url(endpoint, id) {
186
- return new URL((0, import_sdk_core2.credentialEndpointPath)(endpoint, id), this.descriptor.issuer.origin).href;
254
+ return new URL((0, import_sdk_core4.credentialEndpointPath)(endpoint, id), this.descriptor.issuer.origin).href;
187
255
  }
188
256
  async request(endpoint, input) {
189
257
  let response;
@@ -191,8 +259,8 @@ var OpenCredentialsHttpTransport = class {
191
259
  const init = { method: input.method ?? "GET", credentials: "omit", cache: "no-store", redirect: "error", referrerPolicy: "no-referrer", signal: input.signal, headers: { accept: "application/json", ...input.body === void 0 ? {} : { "content-type": "application/json" }, ...input.verifier === void 0 ? {} : { authorization: `Bearer ${input.verifier}` } }, ...input.body === void 0 ? {} : { body: JSON.stringify(input.body) } };
192
260
  response = await this.fetchFn(this.url(endpoint, input.id), init);
193
261
  } catch (cause) {
194
- if (cause instanceof DOMException && cause.name === "AbortError") throw new import_sdk_core2.CredentialError("CANCELED", "Credential acquisition was canceled", { cause });
195
- throw new import_sdk_core2.CredentialError("OFFLINE", "OpenCredentials is unavailable", { cause });
262
+ if (cause instanceof DOMException && cause.name === "AbortError") throw new import_sdk_core4.CredentialError("CANCELED", "Credential acquisition was canceled", { cause });
263
+ throw new import_sdk_core4.CredentialError("OFFLINE", "OpenCredentials is unavailable", { cause });
196
264
  }
197
265
  if (!response.ok) {
198
266
  let parsed;
@@ -203,95 +271,112 @@ var OpenCredentialsHttpTransport = class {
203
271
  }
204
272
  const typed = serverError(parsed, response, this.options.honorServerRecoverable === true);
205
273
  if (typed) throw typed;
206
- if (response.status === 410) throw new import_sdk_core2.CredentialError("REQUEST_EXPIRED", "Credential request expired");
207
- if (response.status === 409) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential request was replayed or substituted");
208
- if (response.status === 503) throw new import_sdk_core2.CredentialError("ISSUER_UNREADY", "Credential issuer is not ready", { retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0 });
209
- throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "OpenCredentials rejected the request");
274
+ if (response.status === 410) throw new import_sdk_core4.CredentialError("REQUEST_EXPIRED", "Credential request expired");
275
+ if (response.status === 409) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential request was replayed or substituted");
276
+ if (response.status === 503) throw new import_sdk_core4.CredentialError("ISSUER_UNREADY", "Credential issuer is not ready", { retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0 });
277
+ throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "OpenCredentials rejected the request");
210
278
  }
211
279
  if (response.status === 204) return void 0;
212
280
  try {
213
281
  return await response.json();
214
282
  } catch (cause) {
215
- throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "OpenCredentials response is invalid", { cause });
283
+ throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "OpenCredentials response is invalid", { cause });
216
284
  }
217
285
  }
218
286
  async create(input) {
219
- const body = object(await this.request("request", { method: "POST", body: { protocol: input.descriptor.protocol, profile: input.descriptor.profile, profileVersion: input.descriptor.profileVersion, descriptorDigest: input.descriptorDigest, requirementDigest: input.requirementDigest, holderDid: input.holderDid, inputs: input.inputs ?? input.requirement.claims, audience: "tinycloud://credentials", openerOrigin: input.openerOrigin, completionOrigin: input.openerOrigin, completionContext: "sdk-acquisition", completionVerifierChallenge: input.completionVerifierChallenge }, signal: input.signal }), "create response");
220
- if (body.type !== "tinycloud.credentials/acquisition-request/v1" || body.protocol !== input.descriptor.protocol || body.next !== "challenge" || body.endpoint !== "challenge") throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "create response is invalid");
287
+ const body = object(await this.request("request", { method: "POST", body: { protocol: input.descriptor.protocol, profile: input.descriptor.profile, profileVersion: input.descriptor.profileVersion, descriptorDigest: input.descriptorDigest, requirementDigest: input.requirementDigest, holderDid: input.holderDid, inputs: input.inputs ?? input.requirement.claims, audience: HELD_ACCOUNT_BINDING_PINS.audience, openerOrigin: input.openerOrigin, completionOrigin: input.openerOrigin, completionContext: HELD_ACCOUNT_BINDING_PINS.completionContext, completionVerifierChallenge: input.completionVerifierChallenge }, signal: input.signal }), "create response");
288
+ if (body.type !== "tinycloud.credentials/acquisition-request/v1" || body.protocol !== input.descriptor.protocol || body.next !== "challenge" || body.endpoint !== "challenge") throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "create response is invalid");
221
289
  const requestId = opaque(body.requestId, "requestId");
222
290
  return { requestId, locator: requestId, expiresAt: timestamp(body.expiresAt, "expiresAt"), correlationId: requestId };
223
291
  }
292
+ /** The nonce stored from R3 or R2 for this request. Held acquisition compares the signed binding with it. */
293
+ challengeNonce(id) {
294
+ return this.nonces.get(id);
295
+ }
296
+ /** The binding fetched and validated for this request, as the account signed it. */
297
+ signedBinding(id) {
298
+ return this.bindings.get(id);
299
+ }
224
300
  proofStep() {
225
301
  return this.descriptor.steps.some((step) => step.type === "mailbox_otp") ? "mailbox_otp" : "collect_input";
226
302
  }
227
303
  async state(id, verifier, signal) {
228
304
  const raw = object(await this.request("state", { id, verifier, signal }), "state response");
229
- if (raw.type !== "tinycloud.credentials/acquisition-state/v1" || raw.requestId !== id || raw.profile !== this.descriptor.profile || raw.profileVersion !== 1) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential state response is substituted");
305
+ if (raw.type !== "tinycloud.credentials/acquisition-state/v1" || raw.requestId !== id || raw.profile !== this.descriptor.profile || raw.profileVersion !== 1) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential state response is substituted");
230
306
  const base = { type: "OpenCredentialsAcquisitionState", version: 1, requestId: id, transitionId: `${String(raw.state)}:${String(raw.resultPreviouslyRead ?? false)}`, correlationId: id };
231
307
  if (raw.state === "challenge_required") {
232
308
  const type = this.proofStep();
233
309
  return { ...base, state: "pending", nextStep: { id: type, type, version: 1, constraints: { challengeRequired: true } } };
234
310
  }
311
+ if (this.descriptor.subjectRelationship === "account_is_subject" && (raw.state === "proof_required" || raw.state === "holder_binding_required" || raw.state === "holder_signature_required" || raw.state === "ready_to_issue" || raw.state === "issued")) {
312
+ const nonce = raw.challengeNonce;
313
+ const prior = this.nonces.get(id);
314
+ if (typeof nonce !== "string" || nonce === "" || prior !== void 0 && prior !== nonce) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge nonce is missing or changed");
315
+ this.nonces.set(id, opaque(nonce, "challengeNonce"));
316
+ }
235
317
  if (raw.state === "proof_required") {
236
318
  const type = this.proofStep();
237
- this.nonces.set(id, opaque(raw.challengeNonce, "challengeNonce"));
319
+ if (this.descriptor.subjectRelationship !== "account_is_subject") this.nonces.set(id, opaque(raw.challengeNonce, "challengeNonce"));
238
320
  return { ...base, state: "pending", nextStep: { id: type, type, version: 1, constraints: {} } };
239
321
  }
240
322
  if (raw.state === "holder_binding_required" || raw.state === "holder_signature_required") return { ...base, state: "pending", nextStep: { id: "holder_signature", type: "holder_signature", version: 1, constraints: {} } };
241
323
  if (raw.state === "ready_to_issue") return { ...base, state: "ready_to_issue" };
242
324
  if (raw.state === "issued") return { ...base, state: "complete" };
243
- throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential state is unsupported");
325
+ throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential state is unsupported");
244
326
  }
245
327
  async beginStep(id, verifier, stepId, signal) {
246
328
  const step = this.proofStep();
247
- if (stepId !== step) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge step is substituted");
329
+ if (stepId !== step) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge step is substituted");
248
330
  const challenge = object(await this.request("challenge", { id, verifier, method: "POST", body: { step, stepVersion: 1 }, signal }), "challenge response");
249
- if (challenge.type !== "tinycloud.credentials/challenge/v1" || challenge.step !== step || challenge.stepVersion !== 1) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge is substituted");
331
+ if (challenge.type !== "tinycloud.credentials/challenge/v1" || challenge.step !== step || challenge.stepVersion !== 1) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge is substituted");
250
332
  this.nonces.set(id, opaque(challenge.challengeNonce, "challengeNonce"));
251
333
  }
252
334
  async submitStep(id, verifier, stepId, proof, signal) {
253
335
  const step = this.proofStep();
254
- if (stepId !== step) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential proof step is substituted");
336
+ if (stepId !== step) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential proof step is substituted");
255
337
  const nonce = this.nonces.get(id);
256
- if (!nonce) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge is missing");
338
+ if (!nonce) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Credential challenge is missing");
257
339
  await this.request("proof", { id, verifier, method: "POST", body: { step, stepVersion: 1, challengeNonce: nonce, proof }, signal });
258
340
  }
259
341
  async holderBinding(id, verifier, signal) {
260
342
  const raw = object(await this.request("holder_binding", { id, verifier, signal }), "holder binding response");
261
- if (raw.type !== "tinycloud.credentials/holder-binding-request/v1" || raw.signingDomain !== "tinycloud.credentials/holder-binding/v1" || raw.alg !== "EdDSA" || typeof raw.canonical !== "string") throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Holder binding response is invalid");
262
- const binding = (0, import_sdk_core2.validateCredentialHolderBinding)(raw.binding);
263
- if ((0, import_sdk_core2.encodeBase64Url)((0, import_sdk_core2.holderBindingCanonicalBytes)(binding)) !== raw.canonical) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Holder binding canonical bytes were substituted");
343
+ if (raw.type !== "tinycloud.credentials/holder-binding-request/v1" || raw.signingDomain !== this.descriptor.holderBinding.domain || raw.alg !== this.descriptor.holderBinding.alg || typeof raw.canonical !== "string") throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Holder binding response is invalid");
344
+ const binding = (0, import_sdk_core4.validateCredentialHolderBinding)(raw.binding);
345
+ if (binding.type !== raw.signingDomain) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Holder binding type does not match its signing domain");
346
+ if ((0, import_sdk_core4.encodeBase64Url)((0, import_sdk_core4.holderBindingCanonicalBytes)(binding)) !== raw.canonical) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Holder binding canonical bytes were substituted");
264
347
  this.bindings.set(id, binding);
265
348
  return binding;
266
349
  }
267
350
  async submitHolderSignature(id, verifier, signature, signal, key) {
268
351
  const binding = this.bindings.get(id);
269
- if (!binding) throw new import_sdk_core2.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was not retrieved");
352
+ if (!binding) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "Holder binding was not retrieved");
353
+ if (this.descriptor.holderBinding.alg === "EIP191" && !key) throw new import_sdk_core4.CredentialError("REQUEST_SUBSTITUTED", "An account holder must name its key and algorithm");
354
+ const alg = key?.alg ?? "EdDSA";
270
355
  const kid = key?.kid ?? `${binding.holderDid}#${binding.holderDid.replace("did:key:", "")}`;
271
- await this.request("holder_signature", { id, verifier, method: "POST", body: { alg: key?.alg ?? "EdDSA", kid, signature }, signal });
356
+ await this.request("holder_signature", { id, verifier, method: "POST", body: { alg, kid, signature }, signal });
272
357
  }
273
358
  async issue(id, verifier, signal) {
274
359
  await this.request("issue", { id, verifier, method: "POST", signal });
275
360
  }
276
361
  async result(id, verifier, signal) {
277
362
  const raw = object(await this.request("result", { id, verifier, signal }), "result response");
278
- if (raw.type !== "tinycloud.credentials/result/v1" || raw.protocol !== this.descriptor.protocol || raw.profile !== this.descriptor.profile || raw.profileVersion !== 1 || raw.format !== "vc+sd-jwt" || raw.descriptorDigest !== await (0, import_sdk_core2.canonicalDigest)(this.descriptor) || raw.issuer !== this.descriptor.issuer.did || raw.issuerKid !== this.descriptor.issuer.kid || typeof raw.credential !== "string" || await (0, import_sdk_core2.sha256Base64Url)(raw.credential) !== raw.credentialDigest) throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "Issued result metadata is invalid");
363
+ if (raw.type !== "tinycloud.credentials/result/v1" || raw.protocol !== this.descriptor.protocol || raw.profile !== this.descriptor.profile || raw.profileVersion !== 1 || raw.format !== "vc+sd-jwt" || raw.descriptorDigest !== await (0, import_sdk_core4.canonicalDigest)(this.descriptor) || raw.issuer !== this.descriptor.issuer.did || raw.issuerKid !== this.descriptor.issuer.kid || typeof raw.credential !== "string" || await (0, import_sdk_core4.sha256Base64Url)(raw.credential) !== raw.credentialDigest) throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "Issued result metadata is invalid");
279
364
  const holder = object(raw.holderBinding, "result holder binding");
280
- if (raw.subject !== holder.did || holder.signingDomain !== "tinycloud.credentials/holder-binding/v1") throw new import_sdk_core2.CredentialError("HOLDER_MISMATCH", "Issued holder binding is invalid");
365
+ if (raw.subject !== holder.did || holder.signingDomain !== this.descriptor.holderBinding.domain) throw new import_sdk_core4.CredentialError("HOLDER_MISMATCH", "Issued holder binding is invalid");
281
366
  const claimsRaw = object(raw.claims, "result claims");
282
367
  const claims = {};
283
368
  for (const [name, value] of Object.entries(claimsRaw)) {
284
- if (typeof value !== "string") throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "Issued claims are invalid");
369
+ if (typeof value !== "string") throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "Issued claims are invalid");
285
370
  claims[name] = value;
286
371
  }
287
372
  const validity = object(raw.validity, "result validity");
288
373
  const payload = jwtPayload(raw.credential);
289
- if (typeof payload.jti !== "string") throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "Credential id is invalid");
374
+ if (typeof payload.jti !== "string") throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "Credential id is invalid");
290
375
  const issuedAt = timestamp(validity.issuedAt, "issuedAt");
291
376
  const expiresAt = timestamp(validity.expiresAt, "expiresAt");
292
377
  const status = object(raw.status, "result status");
293
- if (status.type !== "none" || status.freshnessSeconds !== this.descriptor.status.freshnessSeconds) throw new import_sdk_core2.CredentialError("VERIFICATION_FAILED", "Credential status is invalid");
294
- return { type: "OpenCredentialsIssuedCredential", version: 1, protocol: this.descriptor.protocol, profile: { id: this.descriptor.profile, version: 1 }, credentialType: { id: this.descriptor.format.vct, version: 1 }, schema: this.descriptor.format.vct, format: "vc+sd-jwt", issuerDid: raw.issuer, issuerKid: raw.issuerKid, subjectDid: raw.subject, holderDid: raw.subject, claims, claimsDigest: await (0, import_sdk_core2.canonicalDigest)(claims), descriptorDigest: raw.descriptorDigest, credentialId: payload.jti, issuedAt, notBefore: issuedAt, expiresAt, status: { method: "none", freshnessSeconds: status.freshnessSeconds }, credential: raw.credential };
378
+ if (status.type !== "none" || status.freshnessSeconds !== this.descriptor.status.freshnessSeconds) throw new import_sdk_core4.CredentialError("VERIFICATION_FAILED", "Credential status is invalid");
379
+ return { type: "OpenCredentialsIssuedCredential", version: 1, protocol: this.descriptor.protocol, profile: { id: this.descriptor.profile, version: 1 }, credentialType: { id: this.descriptor.format.vct, version: 1 }, schema: this.descriptor.format.vct, format: "vc+sd-jwt", issuerDid: raw.issuer, issuerKid: raw.issuerKid, subjectDid: raw.subject, holderDid: raw.subject, claims, claimsDigest: await (0, import_sdk_core4.canonicalDigest)(claims), descriptorDigest: raw.descriptorDigest, credentialId: payload.jti, issuedAt, notBefore: issuedAt, expiresAt, status: { method: "none", freshnessSeconds: status.freshnessSeconds }, credential: raw.credential };
295
380
  }
296
381
  async issuerMetadata(signal) {
297
382
  return await this.request("issuer_metadata", { signal });
@@ -302,13 +387,6 @@ var OpenCredentialsHttpTransport = class {
302
387
  };
303
388
 
304
389
  // src/credentials/email-proof.ts
305
- var EMAIL_PROOF_PINS = Object.freeze({
306
- witnessOrigin: "https://witness.credentials.org",
307
- issuerDid: "did:web:issuer.credentials.org",
308
- issuerKid: "did:web:issuer.credentials.org#controller",
309
- issuerX: "eEJI4xEobto4HtQ7Pg9R1vBOwcpfGRTlXDG5QfqLnGQ",
310
- descriptorDigest: "1tg-qphmKBVtNwzVg9xyz-xxqt_xtMXAsQyXw46m8S0"
311
- });
312
390
  var EMAIL_PROOF_CREDENTIAL_TYPE = { id: "opencredentials.email/v1", version: 1 };
313
391
  var EMAIL_PROOF_PENDING_MAX_MS = 15 * 6e4;
314
392
  var DISCOVERY_URL = `${EMAIL_PROOF_PINS.witnessOrigin}/.well-known/opencredentials`;
@@ -324,10 +402,16 @@ var EMAIL_PROOF_ISSUER_KEYS = [
324
402
  var DEFAULT_EMAIL_PROOF_PROFILE = "tinycloud.email-proof/v1";
325
403
  var PROFILE_PINS = {
326
404
  "tinycloud.email-proof/v1": { descriptorDigest: EMAIL_PROOF_PINS.descriptorDigest, requiredStep: "mailbox_otp", enabled: true },
327
- // Held profile: disabled until the held wire path ships. Requests refuse with PROFILE_UNAVAILABLE before any network call.
328
- "tinycloud.email-held/v1": { descriptorDigest: "dc3_DuvzipXwYkTfjKqTaK1c3VS0YSPnaBsYSQriC0w", requiredStep: "mailbox_otp", enabled: false }
405
+ // Held profile: disabled by default. A call enables it only with the per-call opt-in `allowHeldProfile: true` (see `profilePin`).
406
+ // Without it, requests refuse with PROFILE_UNAVAILABLE before any network call.
407
+ "tinycloud.email-held/v1": { descriptorDigest: HELD_ACCOUNT_BINDING_PINS.descriptorDigest, requiredStep: "mailbox_otp", enabled: false }
329
408
  };
330
- var OPENER_ORIGIN = "https://localhost";
409
+ var OPENER_ORIGIN = HELD_ACCOUNT_BINDING_PINS.openerOrigin;
410
+ var HELD_PROFILE = "tinycloud.email-held/v1";
411
+ var HELD_KID_FRAGMENT = "blockchainAccountId";
412
+ var HELD_MAX_AGE_SECONDS = 31536e3;
413
+ var HELD_REQUEST_TTL_MS = 6e5;
414
+ var CLOCK_SKEW_BOUND_MS = 3e5;
331
415
  var STEP_TIMEOUT_MS = 6e4;
332
416
  var HOLDER_DID_PREFIX = "did:key:";
333
417
  var HOLDER_DID_PATTERN = /^did:key:z6Mk[^#]+$/;
@@ -347,6 +431,7 @@ var RECOVERABILITY = {
347
431
  CREDENTIAL_INVALID: false,
348
432
  SESSION_EXPIRED: false,
349
433
  SESSION_INVALID: false,
434
+ CLIENT_CLOCK_SKEW: false,
350
435
  CANCELED: false,
351
436
  FAILED: false
352
437
  };
@@ -363,9 +448,11 @@ var MESSAGES = {
363
448
  CREDENTIAL_INVALID: "Issued credential failed verification.",
364
449
  SESSION_EXPIRED: "Email verification request expired. Request a new code.",
365
450
  SESSION_INVALID: "Email verification request is no longer valid. Request a new code.",
451
+ CLIENT_CLOCK_SKEW: "Your device clock is more than 5 minutes behind. Correct it and try again.",
366
452
  CANCELED: "Email verification was canceled.",
367
453
  FAILED: "Email verification failed."
368
454
  };
455
+ var LOCAL_PROFILE_UNAVAILABLE = "This SDK build doesn't enable the email verification profile.";
369
456
  function rateLimitMessage(retryAfterMs) {
370
457
  const head = "Too many codes were requested for this address.";
371
458
  if (retryAfterMs === void 0 || !Number.isFinite(retryAfterMs) || retryAfterMs <= 0) return `${head} Try again in about an hour.`;
@@ -373,8 +460,9 @@ function rateLimitMessage(retryAfterMs) {
373
460
  return `${head} Try again in ${minutes} ${minutes === 1 ? "minute" : "minutes"}.`;
374
461
  }
375
462
  var EmailProofError = class extends Error {
376
- constructor(step, code, retryAfterMs, recoverable = RECOVERABILITY[code]) {
377
- super(code === "RATE_LIMITED" ? rateLimitMessage(retryAfterMs) : MESSAGES[code]);
463
+ /** `message` overrides the code's text for one refusal whose cause the shared text would misdescribe. It never carries request data. */
464
+ constructor(step, code, retryAfterMs, recoverable = RECOVERABILITY[code], message) {
465
+ super(message ?? (code === "RATE_LIMITED" ? rateLimitMessage(retryAfterMs) : MESSAGES[code]));
378
466
  this.name = "EmailProofError";
379
467
  this.step = step;
380
468
  this.code = code;
@@ -394,7 +482,8 @@ function restoreEmailProofPending(fields) {
394
482
  requirementDigest: fields.requirementDigest,
395
483
  openerOrigin: fields.openerOrigin,
396
484
  createdAt: fields.createdAt,
397
- expiresAt: fields.expiresAt
485
+ expiresAt: fields.expiresAt,
486
+ ...fields.serverExpiresAt === void 0 ? {} : { serverExpiresAt: fields.serverExpiresAt }
398
487
  };
399
488
  const pending = { ...shown };
400
489
  hide(pending, "verifier", fields.verifier);
@@ -408,6 +497,9 @@ function hide(target, key, value) {
408
497
  function pendingDeadlineMs(pending) {
409
498
  return Math.min(Date.parse(pending.expiresAt), Date.parse(pending.createdAt) + EMAIL_PROOF_PENDING_MAX_MS);
410
499
  }
500
+ function isTicketEmailProofResult(result) {
501
+ return result.envelope.profile.id !== HELD_PROFILE;
502
+ }
411
503
  var CodeRequested = class extends Error {
412
504
  };
413
505
  var pauseForCode = async () => {
@@ -415,17 +507,19 @@ var pauseForCode = async () => {
415
507
  };
416
508
  async function requestEmailProofCode(input) {
417
509
  const profile = input.profile ?? DEFAULT_EMAIL_PROOF_PROFILE;
418
- profilePin(profile, "request");
419
- const address = normalizeAddress(input.address);
420
- assertHolder(input.holder, "request");
510
+ profilePin(profile, "request", input.allowHeldProfile);
511
+ const held = profile === HELD_PROFILE;
512
+ if (held) assertHolder(input.holder, "request", profile);
513
+ const address = normalizeAddress(input.address, profile);
514
+ if (!held) assertHolder(input.holder, "request", profile);
421
515
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
422
516
  const signal = input.signal ?? AbortSignal.timeout(STEP_TIMEOUT_MS);
423
517
  const requirement = emailRequirement(address, profile);
424
- const requirementDigest = await (0, import_sdk_core3.credentialRequirementDigest)(requirement);
425
- const descriptor = await witnessDescriptor(profile, requirement, { fetch: input.fetch, signal }, "request");
426
- const descriptorDigest = await (0, import_sdk_core3.canonicalDigest)(descriptor);
518
+ const requirementDigest = await (0, import_sdk_core5.credentialRequirementDigest)(requirement);
519
+ const descriptor = await witnessDescriptor(profile, requirement, { fetch: input.fetch, signal }, "request", input.allowHeldProfile);
520
+ const descriptorDigest = await (0, import_sdk_core5.canonicalDigest)(descriptor);
427
521
  const verifier = randomVerifier();
428
- const transport = new OpenCredentialsHttpTransport(descriptor, input.fetch, { honorServerRecoverable: true });
522
+ const transport = new OpenCredentialsHttpTransport(descriptor, input.fetch, { honorServerRecoverable: true, ...held ? { allowAccountSubject: true } : {} });
429
523
  const createdAtMs = now().getTime();
430
524
  let created;
431
525
  try {
@@ -436,7 +530,7 @@ async function requestEmailProofCode(input) {
436
530
  requirementDigest,
437
531
  holderDid: input.holder.did,
438
532
  openerOrigin: OPENER_ORIGIN,
439
- completionVerifierChallenge: await (0, import_sdk_core3.sha256Base64Url)(verifier),
533
+ completionVerifierChallenge: await (0, import_sdk_core5.sha256Base64Url)(verifier),
440
534
  signal
441
535
  });
442
536
  } catch (error) {
@@ -444,6 +538,8 @@ async function requestEmailProofCode(input) {
444
538
  }
445
539
  const serverExpiry = Date.parse(created.expiresAt);
446
540
  if (!Number.isFinite(serverExpiry)) throw new EmailProofError("request", "FAILED");
541
+ const serverNowMs = serverExpiry - HELD_REQUEST_TTL_MS;
542
+ if (held && serverNowMs - now().getTime() > CLOCK_SKEW_BOUND_MS) throw new EmailProofError("request", "CLIENT_CLOCK_SKEW");
447
543
  const pending = restoreEmailProofPending({
448
544
  type: "TinyCloudEmailProofPending",
449
545
  version: 1,
@@ -456,11 +552,14 @@ async function requestEmailProofCode(input) {
456
552
  requirementDigest,
457
553
  openerOrigin: OPENER_ORIGIN,
458
554
  createdAt: new Date(createdAtMs).toISOString(),
459
- expiresAt: new Date(Math.min(serverExpiry, createdAtMs + EMAIL_PROOF_PENDING_MAX_MS)).toISOString()
555
+ // Held: the deadline is measured on the client clock (a fast client must not lose window), the witness enforces its own 600 s.
556
+ expiresAt: new Date(held ? createdAtMs + Math.min(serverExpiry - serverNowMs, EMAIL_PROOF_PENDING_MAX_MS) : Math.min(serverExpiry, createdAtMs + EMAIL_PROOF_PENDING_MAX_MS)).toISOString(),
557
+ ...held ? { serverExpiresAt: created.expiresAt } : {}
460
558
  });
461
559
  try {
462
560
  await interpretCredentialFlow({
463
561
  descriptor,
562
+ ...held ? { allowAccountSubject: true } : {},
464
563
  requirement,
465
564
  requestId: created.requestId,
466
565
  verifier,
@@ -468,8 +567,9 @@ async function requestEmailProofCode(input) {
468
567
  descriptorDigest,
469
568
  requirementDigest,
470
569
  openerOrigin: OPENER_ORIGIN,
570
+ ...held ? { expiresAt: created.expiresAt } : {},
471
571
  transport,
472
- signing: holderSigning(input.holder),
572
+ signing: holderSigning(input.holder, held),
473
573
  handlers: { mailbox_otp: pauseForCode },
474
574
  signal
475
575
  });
@@ -486,22 +586,26 @@ async function completeWithIssuerKeys(input, issuerKeys) {
486
586
  const { pending } = input;
487
587
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
488
588
  if (!isPending(pending)) throw new EmailProofError("session", "SESSION_INVALID");
589
+ profilePin(pending.profile, "session", input.allowHeldProfile);
590
+ const held = pending.profile === HELD_PROFILE;
591
+ if (held && !Number.isFinite(Date.parse(pending.serverExpiresAt ?? ""))) throw new EmailProofError("session", "SESSION_INVALID");
489
592
  if (pendingDeadlineMs(pending) <= now().getTime()) throw new EmailProofError("session", "SESSION_EXPIRED");
490
593
  if (!/^\d{8}$/.test(input.code)) throw new EmailProofError("code", "CODE_INVALID");
491
- assertHolder(input.holder, "code");
594
+ assertHolder(input.holder, "code", pending.profile);
492
595
  if (input.holder.did !== pending.holderDid) throw new EmailProofError("session", "SESSION_INVALID");
493
596
  const signal = input.signal ?? AbortSignal.timeout(STEP_TIMEOUT_MS);
494
597
  const requirement = emailRequirement(pending.address, pending.profile);
495
- if (await (0, import_sdk_core3.credentialRequirementDigest)(requirement) !== pending.requirementDigest) throw new EmailProofError("session", "SESSION_INVALID");
496
- const descriptor = await witnessDescriptor(pending.profile, requirement, { fetch: input.fetch, signal }, "session");
497
- const descriptorDigest = await (0, import_sdk_core3.canonicalDigest)(descriptor);
598
+ if (await (0, import_sdk_core5.credentialRequirementDigest)(requirement) !== pending.requirementDigest) throw new EmailProofError("session", "SESSION_INVALID");
599
+ const descriptor = await witnessDescriptor(pending.profile, requirement, { fetch: input.fetch, signal }, "session", input.allowHeldProfile);
600
+ const descriptorDigest = await (0, import_sdk_core5.canonicalDigest)(descriptor);
498
601
  if (descriptorDigest !== pending.descriptorDigest) throw new EmailProofError("session", "SESSION_INVALID");
499
- const transport = new OpenCredentialsHttpTransport(descriptor, input.fetch, { honorServerRecoverable: true });
602
+ const transport = new OpenCredentialsHttpTransport(descriptor, input.fetch, { honorServerRecoverable: true, ...held ? { allowAccountSubject: true } : {} });
500
603
  try {
501
604
  const state = await transport.state(pending.requestId, pending.verifier, signal);
502
605
  if (state.nextStep?.constraints.challengeRequired === true) throw new EmailProofError("session", "SESSION_INVALID");
503
606
  await interpretCredentialFlow({
504
607
  descriptor,
608
+ ...held ? { allowAccountSubject: true } : {},
505
609
  requirement,
506
610
  requestId: pending.requestId,
507
611
  verifier: pending.verifier,
@@ -509,8 +613,9 @@ async function completeWithIssuerKeys(input, issuerKeys) {
509
613
  descriptorDigest,
510
614
  requirementDigest: pending.requirementDigest,
511
615
  openerOrigin: pending.openerOrigin,
616
+ ...held ? { expiresAt: pending.serverExpiresAt } : {},
512
617
  transport,
513
- signing: holderSigning(input.holder),
618
+ signing: holderSigning(input.holder, held),
514
619
  handlers: { mailbox_otp: async () => ({ otp: input.code }) },
515
620
  signal
516
621
  });
@@ -519,7 +624,11 @@ async function completeWithIssuerKeys(input, issuerKeys) {
519
624
  }
520
625
  try {
521
626
  const envelope = await transport.result(pending.requestId, pending.verifier, signal);
522
- const verified = await (0, import_sdk_core3.verifyIssuedCredential)({
627
+ if (held) {
628
+ const verified2 = await verifyHeldResult({ envelope, pending, descriptor, descriptorDigest, transport, issuerKeys, now });
629
+ return { envelope, verified: verified2, credentialId: verified2.credentialId, issuedAt: verified2.issuedAt.toISOString(), expiresAt: verified2.expiresAt.toISOString(), descriptor };
630
+ }
631
+ const verified = await (0, import_sdk_core5.verifyIssuedCredential)({
523
632
  envelope,
524
633
  descriptor,
525
634
  descriptorDigest,
@@ -535,27 +644,51 @@ async function completeWithIssuerKeys(input, issuerKeys) {
535
644
  throw mapError(error, "credential", signal);
536
645
  }
537
646
  }
538
- function normalizeAddress(address) {
647
+ function normalizeAddress(address, profile) {
648
+ if (profile === HELD_PROFILE) return normalizeHeldAddress(address);
539
649
  const value = address.trim();
540
650
  if (value.length > 254 || !EMAIL_PATTERN.test(value)) throw new EmailProofError("request", "ADDRESS_INVALID");
541
651
  return value;
542
652
  }
653
+ var HELD_LOCAL_ATOM = /^[a-z0-9!#$%&'*+\-/=?^_`{|}~]+(\.[a-z0-9!#$%&'*+\-/=?^_`{|}~]+)*$/;
654
+ var HELD_DOMAIN_LABEL = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
655
+ function normalizeHeldAddress(address) {
656
+ const invalid = () => new EmailProofError("request", "ADDRESS_INVALID");
657
+ const trimmed = address.trim();
658
+ if (/[^\x00-\x7f]/.test(trimmed)) throw invalid();
659
+ const value = trimmed.toLowerCase();
660
+ const parts = value.split("@");
661
+ if (value.length < 3 || value.length > 254 || parts.length !== 2 || /[^\x21-\x7e]/.test(value)) throw invalid();
662
+ const [local, domain] = parts;
663
+ if (local.length > 64 || !HELD_LOCAL_ATOM.test(local) || local.startsWith("+")) throw invalid();
664
+ if (domain.length > 253 || !domain.includes(".") || !domain.split(".").every((label) => label.length <= 63 && HELD_DOMAIN_LABEL.test(label))) throw invalid();
665
+ return value;
666
+ }
543
667
  function holderKid(did) {
544
668
  return `${did}#${did.slice(HOLDER_DID_PREFIX.length)}`;
545
669
  }
546
- function assertHolder(holder, step) {
670
+ function assertHeldHolder(holder, step) {
671
+ const parsed = (0, import_held_credentials.parsePkhDid)(holder.did);
672
+ const canonical = parsed !== null && parsed.chainId === "1" ? `did:pkh:eip155:1:${(0, import_held_credentials.toChecksumAddress)(parsed.address)}` : void 0;
673
+ if (canonical === void 0 || holder.did !== canonical || holder.alg !== "EIP191" || holder.kid !== `${canonical}#${HELD_KID_FRAGMENT}`) throw new EmailProofError(step, "HOLDER_INVALID");
674
+ }
675
+ function assertHolder(holder, step, profile) {
676
+ if (profile === HELD_PROFILE) return assertHeldHolder(holder, step);
547
677
  if (!HOLDER_DID_PATTERN.test(holder.did) || holder.alg !== "EdDSA" || holder.kid !== holderKid(holder.did)) throw new EmailProofError(step, "HOLDER_INVALID");
548
678
  }
549
- function profilePin(profile, step) {
679
+ function profilePin(profile, step, allowHeld) {
550
680
  const entry = Object.prototype.hasOwnProperty.call(PROFILE_PINS, profile) ? PROFILE_PINS[profile] : void 0;
551
- if (!entry || !entry.enabled) throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
681
+ if (!entry || !entry.enabled && !(allowHeld === true && profile === HELD_PROFILE)) {
682
+ throw new EmailProofError(step, "PROFILE_UNAVAILABLE", void 0, void 0, LOCAL_PROFILE_UNAVAILABLE);
683
+ }
552
684
  return entry;
553
685
  }
554
686
  function emailRequirement(address, profile) {
555
- return (0, import_sdk_core3.createEmailCredentialRequirement)({ email: address, profile: { id: profile, version: 1 }, credentialType: EMAIL_PROOF_CREDENTIAL_TYPE });
687
+ if (profile === HELD_PROFILE) return (0, import_sdk_core5.validateCredentialRequirement)({ type: "TinyCloudCredentialRequirement", version: 1, profile: { id: profile, version: 1 }, credentialType: EMAIL_PROOF_CREDENTIAL_TYPE, claims: { email: address }, maxAgeSeconds: HELD_MAX_AGE_SECONDS });
688
+ return (0, import_sdk_core5.createEmailCredentialRequirement)({ email: address, profile: { id: profile, version: 1 }, credentialType: EMAIL_PROOF_CREDENTIAL_TYPE });
556
689
  }
557
- async function witnessDescriptor(profile, requirement, options, step) {
558
- const pin = profilePin(profile, step);
690
+ async function witnessDescriptor(profile, requirement, options, step, allowHeld) {
691
+ const pin = profilePin(profile, step, allowHeld);
559
692
  let descriptor;
560
693
  try {
561
694
  descriptor = await discoverCatalogDescriptor(requirement, DISCOVERY_URL, { fetch: options.fetch, signal: options.signal });
@@ -565,7 +698,7 @@ async function witnessDescriptor(profile, requirement, options, step) {
565
698
  if (descriptor.issuer.origin !== EMAIL_PROOF_PINS.witnessOrigin || descriptor.issuer.did !== EMAIL_PROOF_PINS.issuerDid || !descriptor.steps.some((candidate) => candidate.type === pin.requiredStep)) {
566
699
  throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
567
700
  }
568
- if (await (0, import_sdk_core3.canonicalDigest)(descriptor) !== pin.descriptorDigest) throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
701
+ if (await (0, import_sdk_core5.canonicalDigest)(descriptor) !== pin.descriptorDigest) throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
569
702
  return descriptor;
570
703
  }
571
704
  async function discoverCatalogDescriptor(requirement, discoveryUrl, options = {}) {
@@ -573,30 +706,30 @@ async function discoverCatalogDescriptor(requirement, discoveryUrl, options = {}
573
706
  try {
574
707
  url = new URL(discoveryUrl);
575
708
  } catch {
576
- throw new import_sdk_core3.CredentialError("UNSUPPORTED_PROFILE", "Discovery URL is invalid");
709
+ throw new import_sdk_core5.CredentialError("UNSUPPORTED_PROFILE", "Discovery URL is invalid");
577
710
  }
578
- if (url.protocol !== "https:" || !["/.well-known/opencredentials", "/v1/credential-types"].includes(url.pathname) || url.search || url.hash) throw new import_sdk_core3.CredentialError("UNSUPPORTED_PROFILE", "Discovery URL is not an allowed catalog endpoint");
711
+ if (url.protocol !== "https:" || !["/.well-known/opencredentials", "/v1/credential-types"].includes(url.pathname) || url.search || url.hash) throw new import_sdk_core5.CredentialError("UNSUPPORTED_PROFILE", "Discovery URL is not an allowed catalog endpoint");
579
712
  const init = { credentials: "omit", cache: "no-store", redirect: "error", referrerPolicy: "no-referrer", signal: options.signal };
580
713
  let response;
581
714
  try {
582
715
  response = await (options.fetch ?? globalThis.fetch.bind(globalThis))(url, init);
583
716
  } catch (cause) {
584
- throw new import_sdk_core3.CredentialError("OFFLINE", "Credential discovery is unavailable", { cause });
717
+ throw new import_sdk_core5.CredentialError("OFFLINE", "Credential discovery is unavailable", { cause });
585
718
  }
586
- if (!response.ok) throw new import_sdk_core3.CredentialError(response.status === 503 ? "ISSUER_UNREADY" : "OFFLINE", "Credential discovery is unavailable");
719
+ if (!response.ok) throw new import_sdk_core5.CredentialError(response.status === 503 ? "ISSUER_UNREADY" : "OFFLINE", "Credential discovery is unavailable");
587
720
  const catalog = await response.json();
588
- if (catalog.type !== "tinycloud.credentials/catalog/v1" || catalog.protocol !== "tinycloud.credentials/acquisition/v1" || catalog.catalogVersion !== 1 || !Array.isArray(catalog.profiles)) throw new import_sdk_core3.CredentialError("DESCRIPTOR_INVALID", "Credential catalog is invalid");
721
+ if (catalog.type !== "tinycloud.credentials/catalog/v1" || catalog.protocol !== "tinycloud.credentials/acquisition/v1" || catalog.catalogVersion !== 1 || !Array.isArray(catalog.profiles)) throw new import_sdk_core5.CredentialError("DESCRIPTOR_INVALID", "Credential catalog is invalid");
589
722
  for (const entryValue of catalog.profiles) {
590
723
  if (typeof entryValue !== "object" || entryValue === null || Array.isArray(entryValue)) continue;
591
724
  const entry = entryValue;
592
725
  if (entry.supported !== true || entry.enabled !== true || entry.readiness !== "ready" && entry.readiness !== "degraded") continue;
593
726
  try {
594
- const candidate = (0, import_sdk_core3.validateCredentialFlowDescriptor)(entry.descriptor);
595
- if ((0, import_sdk_core3.descriptorSatisfiesRequirement)(candidate, requirement) && entry.descriptorDigest === await (0, import_sdk_core3.canonicalDigest)(candidate)) return candidate;
727
+ const candidate = (0, import_sdk_core5.validateCredentialFlowDescriptor)(entry.descriptor);
728
+ if ((0, import_sdk_core5.descriptorSatisfiesRequirement)(candidate, requirement) && entry.descriptorDigest === await (0, import_sdk_core5.canonicalDigest)(candidate)) return candidate;
596
729
  } catch {
597
730
  }
598
731
  }
599
- throw new import_sdk_core3.CredentialError("UNSUPPORTED_PROFILE", "No available credential profile satisfies the requirement");
732
+ throw new import_sdk_core5.CredentialError("UNSUPPORTED_PROFILE", "No available credential profile satisfies the requirement");
600
733
  }
601
734
  function discoveryFailure(error, signal, step) {
602
735
  if (signal.aborted && signal.reason?.name !== "TimeoutError") return new EmailProofError(step, "CANCELED");
@@ -605,11 +738,55 @@ function discoveryFailure(error, signal, step) {
605
738
  function pinnedIssuerMetadata(keys) {
606
739
  return { type: "OpenCredentialsIssuerMetadata", version: 1, origin: EMAIL_PROOF_PINS.witnessOrigin, issuerDid: EMAIL_PROOF_PINS.issuerDid, keys, cache: { maxAgeSeconds: 3600, etag: "pinned" } };
607
740
  }
608
- function holderSigning(holder) {
609
- return { autoSign: async (_binding, bytes) => holder.sign(bytes), holderKey: { alg: "EdDSA", kid: holder.kid } };
741
+ async function verifyHeldResult(input) {
742
+ const { envelope, pending, descriptor } = input;
743
+ const refuse = (message) => new import_sdk_core5.CredentialError("VERIFICATION_FAILED", message);
744
+ const iatMs = Date.parse(envelope.issuedAt);
745
+ const clientMs = input.now().getTime();
746
+ if (!Number.isFinite(iatMs) || iatMs - clientMs > CLOCK_SKEW_BOUND_MS) throw refuse("client clock is more than 5 minutes behind the issuer");
747
+ const key = input.issuerKeys.find((candidate) => candidate.kid === EMAIL_PROOF_PINS.issuerKid);
748
+ if (!key) throw refuse("no pinned issuer key");
749
+ const issuerSet = (0, import_held_credentials.parseIssuerSet)(
750
+ { format: "tinycloud.credentials-issuers/v1", version: 1, issuers: [{ issuerDid: EMAIL_PROOF_PINS.issuerDid, kid: EMAIL_PROOF_PINS.issuerKid, alg: "EdDSA", publicKey: key.jwk.x, vcts: [EMAIL_PROOF_CREDENTIAL_TYPE.id], profiles: [HELD_PROFILE], notBefore: "2020-01-01T00:00:00Z", retiredAt: null, notAfter: null }] },
751
+ { didMethods: { issuer: ["web"] } }
752
+ );
753
+ const verified = (0, import_held_credentials.verifyHeldEmailCredential)(envelope.credential, {
754
+ now: new Date(Math.max(clientMs, iatMs)),
755
+ owner: pending.holderDid,
756
+ didMethods: { issuer: ["web"] },
757
+ acceptedIssuers: [{ issuerDid: EMAIL_PROOF_PINS.issuerDid, kid: EMAIL_PROOF_PINS.issuerKid }],
758
+ issuerSet
759
+ });
760
+ const signed = input.transport.signedBinding(pending.requestId);
761
+ const claims = Object.keys(envelope.claims).sort().join();
762
+ const toSecond = (ms) => Math.floor(ms / 1e3);
763
+ if (
764
+ // (i) the holder, as the one EIP-55 string
765
+ envelope.subjectDid !== pending.holderDid || envelope.holderDid !== pending.holderDid || // (ii) exactly {email, emailDomain}, equal to the credential's, and the requested mailbox
766
+ claims !== "email,emailDomain" || envelope.claims.email !== verified.claims.email || envelope.claims.emailDomain !== verified.claims.emailDomain || envelope.claims.email !== pending.address || // (iii) validity to the second
767
+ toSecond(Date.parse(envelope.issuedAt)) !== toSecond(verified.issuedAt.getTime()) || toSecond(Date.parse(envelope.expiresAt)) !== toSecond(verified.expiresAt.getTime()) || // (v) the pins
768
+ envelope.descriptorDigest !== input.descriptorDigest || envelope.profile.id !== HELD_PROFILE || envelope.profile.version !== 1 || envelope.issuerDid !== EMAIL_PROOF_PINS.issuerDid || envelope.issuerKid !== EMAIL_PROOF_PINS.issuerKid || envelope.format !== "vc+sd-jwt" || envelope.status.method !== "none" || envelope.status.freshnessSeconds !== descriptor.status.freshnessSeconds
769
+ ) throw refuse("issued credential does not match the request");
770
+ if (signed?.type !== descriptor.holderBinding.domain || !("credentialLifetimeSeconds" in signed)) throw refuse("signed binding is missing");
771
+ if (verified.credentialId !== signed.jti || (verified.expiresAt.getTime() - verified.issuedAt.getTime()) / 1e3 !== signed.credentialLifetimeSeconds) throw refuse("credential does not match the binding the account signed");
772
+ return verified;
773
+ }
774
+ function holderSigning(holder, held) {
775
+ return {
776
+ autoSign: async (_binding, bytes) => {
777
+ try {
778
+ return await holder.sign(bytes);
779
+ } catch (error) {
780
+ const aborted = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
781
+ if (!held || error instanceof EmailProofError || aborted) throw error;
782
+ throw new EmailProofError("credential", "SIGNATURE_REJECTED");
783
+ }
784
+ },
785
+ holderKey: { alg: holder.alg, kid: holder.kid }
786
+ };
610
787
  }
611
788
  function randomVerifier() {
612
- return (0, import_sdk_core3.encodeBase64Url)(crypto.getRandomValues(new Uint8Array(32)));
789
+ return (0, import_sdk_core5.encodeBase64Url)(crypto.getRandomValues(new Uint8Array(32)));
613
790
  }
614
791
  function isPending(value) {
615
792
  if (typeof value !== "object" || value === null) return false;
@@ -619,7 +796,7 @@ function isPending(value) {
619
796
  function mapError(value, step, signal) {
620
797
  if (value instanceof EmailProofError) return value;
621
798
  if (value instanceof DOMException && value.name === "TimeoutError") return new EmailProofError(step, "NETWORK");
622
- const error = (0, import_sdk_core3.credentialError)(value);
799
+ const error = (0, import_sdk_core5.credentialError)(value);
623
800
  const details = error.details;
624
801
  switch (error.code) {
625
802
  case "ISSUER_UNREADY":
@@ -628,6 +805,7 @@ function mapError(value, step, signal) {
628
805
  if (signal?.aborted && signal.reason?.name !== "TimeoutError") return new EmailProofError(step, "CANCELED");
629
806
  return new EmailProofError(step, "NETWORK");
630
807
  case "UNSUPPORTED_PROFILE":
808
+ case "UNSUPPORTED_VERSION":
631
809
  case "DESCRIPTOR_INVALID":
632
810
  return new EmailProofError(step, "PROFILE_UNAVAILABLE");
633
811
  case "PROOF_REJECTED":
@@ -653,18 +831,19 @@ function mapError(value, step, signal) {
653
831
  }
654
832
 
655
833
  // src/share-receive-access.ts
656
- var import_sdk_core4 = require("@tinycloud/sdk-core");
834
+ var import_sdk_core6 = require("@tinycloud/sdk-core");
657
835
  var import_share_sdk = require("@tinycloud/share-sdk");
658
836
  async function openVerifiedAgentShare(pending, result, requirement, options) {
659
837
  const { envelope } = pending;
660
- if (!(0, import_sdk_core4.credentialMatchesRequirement)(requirement, result.verified, options.holder.did, new Date(options.now?.() ?? Date.now()))) {
838
+ if (!isTicketEmailProofResult(result)) throw new import_share_sdk.ShareReceiveError("authorization-denied", "email credential does not match this share");
839
+ if (!(0, import_sdk_core6.credentialMatchesRequirement)(requirement, result.verified, options.holder.did, new Date(options.now?.() ?? Date.now()))) {
661
840
  throw new import_share_sdk.ShareReceiveError("authorization-denied", "email credential does not match this share");
662
841
  }
663
842
  if (envelope.target.nodeAudience !== envelope.attestedEnforcerBinding.nodeAudience) {
664
843
  throw new import_share_sdk.ShareReceiveError("envelope-invalid", "share Node binding is invalid");
665
844
  }
666
845
  try {
667
- await (0, import_sdk_core4.verifyOwnerNodeBinding)({
846
+ await (0, import_sdk_core6.verifyOwnerNodeBinding)({
668
847
  registryUrl: options.registryOrigin,
669
848
  ownerDid: envelope.policy.ownerDid,
670
849
  nodeOrigin: envelope.target.origin,
@@ -677,7 +856,7 @@ async function openVerifiedAgentShare(pending, result, requirement, options) {
677
856
  }
678
857
  let admitted;
679
858
  try {
680
- admitted = await (0, import_sdk_core4.admitPolicyCredentialV4)({
859
+ admitted = await (0, import_sdk_core6.admitPolicyCredentialV4)({
681
860
  policy: envelope.policy,
682
861
  policyCid: envelope.policyCid,
683
862
  policyRootCid: envelope.policyRoot.cid,
@@ -731,7 +910,7 @@ function requirementFor(envelope) {
731
910
  throw new import_share_sdk2.ShareReceiveError("unsupported-target", "this receive path accepts exact-email shares only");
732
911
  }
733
912
  const policy = envelope.policy;
734
- return (0, import_sdk_core5.createEmailCredentialRequirement)({
913
+ return (0, import_sdk_core7.createEmailCredentialRequirement)({
735
914
  email: envelope.recipientMatcher.value,
736
915
  profile: policy.credentialRequirement.profile,
737
916
  credentialType: policy.credentialRequirement.credentialType
@@ -750,7 +929,7 @@ async function requestAgentShareEmailProof(link, options) {
750
929
  })).metadata;
751
930
  if (envelope === void 0) throw new import_share_sdk2.ShareReceiveError("envelope-invalid", "share envelope is invalid");
752
931
  const requirement = requirementFor(envelope);
753
- if (await (0, import_sdk_core5.credentialRequirementDigest)(requirement) !== envelope.policy.credentialRequirement.requirementDigest) {
932
+ if (await (0, import_sdk_core7.credentialRequirementDigest)(requirement) !== envelope.policy.credentialRequirement.requirementDigest) {
754
933
  throw new import_share_sdk2.ShareReceiveError("envelope-invalid", "share credential requirement is invalid");
755
934
  }
756
935
  const proof = await requestEmailProofCode({ address: requirement.claims.email, holder: options.holder, fetch: options.fetch, signal: options.signal });
@@ -760,7 +939,7 @@ async function completeAgentShareReceive(pending, code, options) {
760
939
  const { envelope } = pending;
761
940
  active(envelope, options.now?.() ?? Date.now());
762
941
  const requirement = requirementFor(envelope);
763
- if (await (0, import_sdk_core5.credentialRequirementDigest)(requirement) !== envelope.policy.credentialRequirement.requirementDigest) {
942
+ if (await (0, import_sdk_core7.credentialRequirementDigest)(requirement) !== envelope.policy.credentialRequirement.requirementDigest) {
764
943
  throw new import_share_sdk2.ShareReceiveError("envelope-invalid", "share credential requirement is invalid");
765
944
  }
766
945
  if (pending.proof.address !== requirement.claims.email || pending.proof.holderDid !== options.holder.did) {