@tinycloud/share-sdk 0.2.0 → 0.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5227,9 +5227,12 @@ function validateV2Invariants(value, ctx) {
5227
5227
  var unsignedShareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.superRefine(validateV2Invariants);
5228
5228
  var shareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV2Invariants);
5229
5229
  var unifiedResourceSchema = external_exports.string().refine((value) => {
5230
- const match = /^tinycloud:\/\/([^/]+)\/kv\/(.+)$/.exec(value);
5231
- if (match === null || /[:?#%]/.test(match[1])) return false;
5232
- const path = match[2];
5230
+ const marker = value.indexOf("/kv/");
5231
+ if (marker < 1) return false;
5232
+ const space = value.slice(0, marker);
5233
+ const legacy = space.startsWith("tinycloud://");
5234
+ if (legacy ? /[:/?#%]/.test(space.slice("tinycloud://".length)) : !space.startsWith("tinycloud:") || /[/?#%]/.test(space)) return false;
5235
+ const path = value.slice(marker + 4);
5233
5236
  return !path.startsWith("/") && !path.endsWith("/") && !path.includes("//") && path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
5234
5237
  }, { message: "expected canonical TinyCloud KV resource" });
5235
5238
  var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) => {
@@ -5267,7 +5270,7 @@ var unifiedContentSourceSchema = external_exports.object({
5267
5270
  mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
5268
5271
  initialCiphertextDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/).optional()
5269
5272
  }).strict();
5270
- var unifiedPolicySchema = external_exports.object({
5273
+ var unifiedPolicyV1Schema = external_exports.object({
5271
5274
  schema: external_exports.literal("xyz.tinycloud.policy/policy/v1"),
5272
5275
  policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
5273
5276
  ownerDid: external_exports.string().min(1),
@@ -5277,6 +5280,31 @@ var unifiedPolicySchema = external_exports.object({
5277
5280
  capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
5278
5281
  signature: external_exports.object({ suite: external_exports.string().min(1), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
5279
5282
  }).strict();
5283
+ var policyCredentialRequirementV1Schema = external_exports.object({
5284
+ type: external_exports.literal("TinyCloudPolicyCredentialRequirement"),
5285
+ version: external_exports.literal(1),
5286
+ requirementDigest: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/),
5287
+ descriptorDigest: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/),
5288
+ issuerDid: external_exports.string().min(1),
5289
+ issuerKid: external_exports.string().min(1),
5290
+ profile: external_exports.object({ id: external_exports.string().min(1), version: external_exports.literal(1) }).strict(),
5291
+ credentialType: external_exports.object({ id: external_exports.string().min(1), version: external_exports.literal(1) }).strict()
5292
+ }).strict();
5293
+ var unifiedPolicyV2Schema = external_exports.object({
5294
+ schema: external_exports.literal("xyz.tinycloud.policy/policy/v2"),
5295
+ policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
5296
+ ownerDid: external_exports.string().min(1),
5297
+ createdAt: external_exports.string().datetime({ offset: true }),
5298
+ expiresAt: external_exports.string().datetime({ offset: true }).optional(),
5299
+ contentSource: unifiedContentSourceSchema,
5300
+ capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
5301
+ credentialRequirement: policyCredentialRequirementV1Schema,
5302
+ signature: external_exports.object({ suite: external_exports.literal("Ed25519"), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
5303
+ }).strict();
5304
+ var unifiedPolicySchema = external_exports.discriminatedUnion("schema", [
5305
+ unifiedPolicyV1Schema,
5306
+ unifiedPolicyV2Schema
5307
+ ]);
5280
5308
  var unifiedRootSchema = external_exports.object({
5281
5309
  cid: external_exports.string().min(1),
5282
5310
  authorization: external_exports.string().regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
@@ -5331,7 +5359,7 @@ function validateV3Invariants(value, ctx) {
5331
5359
  if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
5332
5360
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
5333
5361
  }
5334
- if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.target.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5362
+ if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5335
5363
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
5336
5364
  }
5337
5365
  if (value.policy.contentSource.shareId !== value.contentSource.shareId || value.policy.contentSource.kvResource !== value.contentSource.kvResource || value.policy.contentSource.selector !== value.contentSource.selector || value.policy.contentSource.encryptionNetwork !== value.encryptionNetwork || value.policy.contentSource.encryptedSymmetricKeyDigestHex !== value.contentSource.encryptedSymmetricKeyDigestHex || value.policy.contentSource.keyVersion !== value.contentSource.keyVersion || value.policy.contentSource.mode !== value.contentSource.mode || value.policy.contentSource.initialCiphertextDigestHex !== value.contentSource.initialCiphertextDigestHex) {
@@ -5767,6 +5795,13 @@ function equal(left, right) {
5767
5795
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5768
5796
  var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
5769
5797
  var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
5798
+ var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
5799
+ var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
5800
+ var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
5801
+ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
5802
+ var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
5803
+ var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
5804
+ var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
5770
5805
  function signingBytes(unsigned) {
5771
5806
  const domain = utf8Bytes(ENVELOPE_SIGNATURE_DOMAIN);
5772
5807
  const body = utf8Bytes(canonicalize(unsigned));
@@ -5783,6 +5818,14 @@ function signingBytesV2(unsigned) {
5783
5818
  bytes3.set(body, domain.length);
5784
5819
  return bytes3;
5785
5820
  }
5821
+ function signingBytesV3(unsigned) {
5822
+ const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
5823
+ const body = utf8Bytes(canonicalize(unsigned));
5824
+ const bytes3 = new Uint8Array(domain.length + body.length);
5825
+ bytes3.set(domain);
5826
+ bytes3.set(body, domain.length);
5827
+ return bytes3;
5828
+ }
5786
5829
  function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
5787
5830
  const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
5788
5831
  const publicKey = ed255193.getPublicKey(ed25519PrivKey);
@@ -5830,6 +5873,92 @@ async function verifyEnvelopeV2(envelope, options) {
5830
5873
  if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
5831
5874
  return true;
5832
5875
  }
5876
+ function verifyEnvelopeV3SignatureOnly(envelope) {
5877
+ const parsed = shareEnvelopeV3Schema.parse(envelope);
5878
+ const { signature, ...unsigned } = parsed;
5879
+ return ed255193.verify(fromBase64Url(signature.value), sha2562(signingBytesV3(unsigned)), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5880
+ }
5881
+ async function verifyEnvelopeV3(envelope, options) {
5882
+ const parsed = shareEnvelopeV3Schema.parse(envelope);
5883
+ if (parsed.signature.signerDid !== options.expectedSignerDid || parsed.signature.signerDid !== parsed.policy.ownerDid || !verifyEnvelopeV3SignatureOnly(parsed)) return false;
5884
+ const { policy } = parsed;
5885
+ const unsignedPolicy = { ...policy };
5886
+ delete unsignedPolicy.policyId;
5887
+ delete unsignedPolicy.signature;
5888
+ const policySignatureDomain = policy.schema === "xyz.tinycloud.policy/policy/v2" ? POLICY_V2_SIGNATURE_DOMAIN : POLICY_V1_SIGNATURE_DOMAIN;
5889
+ const policyDigest = sha2562(new TextEncoder().encode(`${policySignatureDomain}${canonicalize(unsignedPolicy)}`));
5890
+ const policySignature = policy.signature;
5891
+ if (policySignature.suite !== "Ed25519" || policySignature.signerDid !== policy.ownerDid) return false;
5892
+ let policyPublicKey;
5893
+ let policySignatureBytes;
5894
+ try {
5895
+ policyPublicKey = ed25519PublicKeyFromDidKey(policy.ownerDid);
5896
+ policySignatureBytes = fromBase64Url(policySignature.value);
5897
+ } catch {
5898
+ return false;
5899
+ }
5900
+ if (policySignatureBytes.length !== 64 || !ed255193.verify(policySignatureBytes, policyDigest, policyPublicKey, ED25519_VERIFY_OPTS2)) return false;
5901
+ const policyIdDigest = policyDigest;
5902
+ if (policy.policyId !== `pol_${base32Lower(policyIdDigest)}`) return false;
5903
+ const policyBytes = new TextEncoder().encode(canonicalize(policy));
5904
+ if (await computeCid(policyBytes) !== parsed.policyCid) return false;
5905
+ const sourceDigest = sha2562(new TextEncoder().encode(`${CONTENT_SOURCE_V1_DOMAIN}${canonicalize(policy.contentSource)}`));
5906
+ if (hex(sourceDigest) !== parsed.contentSourceDigestHex) return false;
5907
+ const sortedCapabilities = [...policy.capabilityCeiling].sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
5908
+ const capabilityCeilingHashHex = hex(sha2562(new TextEncoder().encode(`${POLICY_CAPABILITY_V1_DOMAIN}${canonicalize(sortedCapabilities)}`)));
5909
+ const nativeProjection = sortedCapabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : { service: "tinycloud.kv", space: capability.resource.slice(0, capability.resource.indexOf("/kv/")), path: capability.resource.split("/kv/")[1], actions: [...capability.actions], caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource } }).sort((left, right) => canonicalize(left).localeCompare(canonicalize(right)));
5910
+ const nativeProjectionHashHex = hex(sha2562(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize(nativeProjection)}`)));
5911
+ const expectedAttenuation = Object.fromEntries(sortedCapabilities.map((capability) => capability.kind === "encryption" ? [capability.resource, { [capability.action]: [{}] }] : [capability.resource, Object.fromEntries(capability.actions.map((action) => [action, [{ kind: capability.selector, type: "xyz.tinycloud.resource/selector", value: capability.resource }]]))]));
5912
+ const kv = policy.capabilityCeiling.find((capability) => capability.kind === "kv");
5913
+ const expectedKvActions = parsed.actions.flatMap((action) => action === "read" ? ["tinycloud.kv/get", "tinycloud.kv/metadata"] : action === "list" ? ["tinycloud.kv/list"] : ["tinycloud.kv/put"]);
5914
+ const marker = parsed.contentSource.kvResource.indexOf("/kv/");
5915
+ const resourceSpace = marker < 1 ? "" : parsed.contentSource.kvResource.slice(0, marker);
5916
+ const resourcePath = marker < 1 ? "" : parsed.contentSource.kvResource.slice(marker + 4);
5917
+ if (parsed.shareId !== parsed.contentSource.shareId || resourceSpace !== parsed.target.spaceId || resourcePath !== parsed.resource.path.replace(/\/$/, "") || parsed.resource.kind !== parsed.contentSource.selector || kv?.kind !== "kv" || expectedKvActions.some((action) => !kv.actions.includes(action)) || parsed.encryptionNetwork !== parsed.contentSource.encryptionNetwork || parsed.contentSource.keyVersion <= 0 || policy.expiresAt !== void 0 && Date.parse(parsed.expiry) > Date.parse(policy.expiresAt)) return false;
5918
+ const binding = parsed.attestedEnforcerBinding;
5919
+ const { signature: bindingSignature, ...unsignedBinding } = binding;
5920
+ const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
5921
+ if (binding.enforcerDid !== parsed.target.nodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || bindingSignature.signerDid !== binding.nodeAudience || bindingSignature.suite !== "Ed25519" || Date.parse(binding.issuedAt) > Date.now() || Date.parse(binding.expiresAt) <= Date.now() || Date.parse(binding.expiresAt) < Date.parse(parsed.expiry)) return false;
5922
+ try {
5923
+ const digest4 = sha2562(new TextEncoder().encode(`${ATTESTED_ENFORCER_V2_DOMAIN}${canonicalize(unsignedBinding)}`));
5924
+ if (!ed255193.verify(fromBase64Url(bindingSignature.value), digest4, ed25519PublicKeyFromDidKey(binding.nodeAudience), ED25519_VERIFY_OPTS2)) return false;
5925
+ } catch {
5926
+ return false;
5927
+ }
5928
+ if (parsed.policyRoot.role !== "policy-authority" || parsed.enforcementRoot.role !== "policy-enforcement" || parsed.policyRoot.cid === parsed.enforcementRoot.cid) return false;
5929
+ try {
5930
+ const policyRoot = verifyCompactUcanAuthorization(parsed.policyRoot.authorization, parsed.policyRoot.cid);
5931
+ const enforcementRoot = verifyCompactUcanAuthorization(parsed.enforcementRoot.authorization, parsed.enforcementRoot.cid);
5932
+ const policyFact = policyRoot.payload.fct[0];
5933
+ const enforcementFact = enforcementRoot.payload.fct[0];
5934
+ const common = ["ownerDid", "policyId", "policyDigestHex", "policyCid", "contentSourceDigestHex", "capabilityCeilingHashHex", "nativeProjectionHashHex", "nodeAudience"];
5935
+ const policyKeys = [...common, "role", "mode"];
5936
+ const enforcementKeys = [...policyKeys, "enforcerDid"];
5937
+ if (Object.keys(policyFact).length !== policyKeys.length || policyKeys.some((key) => !(key in policyFact)) || Object.keys(enforcementFact).length !== enforcementKeys.length || enforcementKeys.some((key) => !(key in enforcementFact)) || policyRoot.payload.prf.length !== 0 || enforcementRoot.payload.prf.length !== 0 || policyRoot.payload.iss.split("#", 1)[0] !== policy.ownerDid || enforcementRoot.payload.iss.split("#", 1)[0] !== policy.ownerDid || policyRoot.payload.nbf !== enforcementRoot.payload.nbf || policyRoot.payload.exp !== enforcementRoot.payload.exp || canonicalize(policyRoot.payload.att) !== canonicalize(expectedAttenuation) || canonicalize(enforcementRoot.payload.att) !== canonicalize(expectedAttenuation) || policyFact.role !== "policy-authority" || policyFact.mode !== "policy-source" || "enforcerDid" in policyFact || enforcementFact.role !== "policy-enforcement" || enforcementFact.mode !== "conditional-mint" || policyRoot.payload.aud !== `did:tinycloud:policy:${hex(policyIdDigest)}` || enforcementRoot.payload.aud !== binding.enforcerDid || enforcementFact.enforcerDid !== binding.enforcerDid || common.some((key) => policyFact[key] !== enforcementFact[key]) || policyFact.ownerDid !== policy.ownerDid || policyFact.policyId !== policy.policyId || policyFact.policyDigestHex !== hex(policyIdDigest) || policyFact.policyCid !== parsed.policyCid || policyFact.contentSourceDigestHex !== parsed.contentSourceDigestHex || policyFact.capabilityCeilingHashHex !== capabilityCeilingHashHex || policyFact.nativeProjectionHashHex !== nativeProjectionHashHex || policyFact.nodeAudience !== binding.nodeAudience || policyRoot.payload.exp * 1e3 < Date.parse(parsed.expiry)) return false;
5938
+ } catch {
5939
+ return false;
5940
+ }
5941
+ return true;
5942
+ }
5943
+ function hex(bytes3) {
5944
+ return [...bytes3].map((byte) => byte.toString(16).padStart(2, "0")).join("");
5945
+ }
5946
+ function base32Lower(bytes3) {
5947
+ const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
5948
+ let output = "";
5949
+ let buffer = 0;
5950
+ let bits = 0;
5951
+ for (const byte of bytes3) {
5952
+ buffer = buffer << 8 | byte;
5953
+ bits += 8;
5954
+ while (bits >= 5) {
5955
+ output += alphabet[buffer >>> bits - 5 & 31];
5956
+ bits -= 5;
5957
+ }
5958
+ }
5959
+ if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
5960
+ return output;
5961
+ }
5833
5962
  var KEY_LENGTH2 = 32;
5834
5963
  var INLINE_PREFIX = "#v=2&p=";
5835
5964
  var MAX_INLINE_BYTES = 256 * 1024;
@@ -6014,8 +6143,12 @@ async function readResponseBytes(response, limit, tooLargeCode) {
6014
6143
  function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", stage = "envelope") {
6015
6144
  if (options.fetchBlob !== void 0) return async (input) => {
6016
6145
  try {
6017
- return boundedBytes(await options.fetchBlob(input), limit, tooLargeCode);
6146
+ options.signal?.throwIfAborted();
6147
+ const bytes3 = await options.fetchBlob({ ...input, ...options.signal === void 0 ? {} : { signal: options.signal } });
6148
+ options.signal?.throwIfAborted();
6149
+ return boundedBytes(bytes3, limit, tooLargeCode);
6018
6150
  } catch (error) {
6151
+ if (options.signal?.aborted) throw options.signal.reason ?? error;
6019
6152
  if (error instanceof ShareReceiveError) throw error;
6020
6153
  throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
6021
6154
  }
@@ -6025,17 +6158,26 @@ function registryFetcher(options, limit, tooLargeCode = "max-bytes-exceeded", st
6025
6158
  const base3 = options.registryBaseUrl.replace(/\/+$/, "");
6026
6159
  return async ({ cid: cid2 }) => {
6027
6160
  try {
6028
- const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, { headers: { accept: "application/vnd.ipld.raw" }, redirect: "error" });
6161
+ options.signal?.throwIfAborted();
6162
+ const response = await fetchFn(`${base3}/ipfs/${cid2}?format=raw`, {
6163
+ headers: { accept: "application/vnd.ipld.raw" },
6164
+ redirect: "error",
6165
+ ...options.signal === void 0 ? {} : { signal: options.signal }
6166
+ });
6167
+ options.signal?.throwIfAborted();
6029
6168
  if (!response.ok) throw new ShareReceiveError("fetch-failed", `registry returned ${response.status}`, { stage });
6030
- return await readResponseBytes(response, limit, tooLargeCode);
6169
+ const bytes3 = await readResponseBytes(response, limit, tooLargeCode);
6170
+ options.signal?.throwIfAborted();
6171
+ return bytes3;
6031
6172
  } catch (error) {
6173
+ if (options.signal?.aborted) throw options.signal.reason ?? error;
6032
6174
  if (error instanceof ShareReceiveError) throw error;
6033
6175
  throw new ShareReceiveError("fetch-failed", "registry unavailable", { stage });
6034
6176
  }
6035
6177
  };
6036
6178
  }
6037
6179
  function metadataFor(envelope, origin) {
6038
- if (envelope.version === 2) {
6180
+ if (envelope.version === 2 || envelope.version === 3) {
6039
6181
  const targetKind3 = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
6040
6182
  return {
6041
6183
  protocol: "tinycloud-share",
@@ -6051,7 +6193,7 @@ function metadataFor(envelope, origin) {
6051
6193
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
6052
6194
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
6053
6195
  },
6054
- ...envelope.content === void 0 ? {} : { content: { cid: envelope.content.cid } }
6196
+ ...envelope.version === 2 && envelope.content !== void 0 ? { content: { cid: envelope.content.cid } } : {}
6055
6197
  };
6056
6198
  }
6057
6199
  return {
@@ -6076,8 +6218,10 @@ function parseEnvelope(bytes3) {
6076
6218
  const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
6077
6219
  const value = JSON.parse(text);
6078
6220
  if (canonicalize(value) !== text) throw new Error("share envelope is not canonical JSON");
6079
- if (typeof value === "object" && value !== null && !Array.isArray(value) && value.version === 2) {
6080
- return shareEnvelopeV2Schema.parse(value);
6221
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
6222
+ const version = value.version;
6223
+ if (version === 3) return shareEnvelopeV3Schema.parse(value);
6224
+ if (version === 2) return shareEnvelopeV2Schema.parse(value);
6081
6225
  }
6082
6226
  return shareEnvelopeSchema.parse(value);
6083
6227
  } catch {
@@ -6149,6 +6293,19 @@ async function verifyV2Envelope(envelope, linkOrigin, options) {
6149
6293
  if (options.trustedSignerDid === void 0 || envelope.signature.signerDid !== options.trustedSignerDid) throw new ShareReceiveError("signature-invalid", "an addressed signer trust root is required");
6150
6294
  }
6151
6295
  }
6296
+ async function verifyV3Envelope(envelope, linkOrigin, options) {
6297
+ if (options.expectedOrigin !== void 0 && linkOrigin !== options.expectedOrigin) {
6298
+ throw new ShareReceiveError("origin-mismatch", "share link origin does not match the trusted origin");
6299
+ }
6300
+ try {
6301
+ if (!await verifyEnvelopeV3(envelope, { expectedSignerDid: envelope.policy.ownerDid })) throw new Error("signature");
6302
+ } catch {
6303
+ throw new ShareReceiveError("signature-invalid", "share signature is invalid");
6304
+ }
6305
+ const expiry = Date.parse(envelope.expiry);
6306
+ if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
6307
+ if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired", { expiresAt: envelope.expiry });
6308
+ }
6152
6309
  async function verifyV1PolicyEnvelope(envelope, linkOrigin, options) {
6153
6310
  if (envelope.authorizationTarget.kind !== "policy") throw new ShareReceiveError("unsupported-target", "share target is not an addressed policy", { reason: "policy-target" });
6154
6311
  if (envelope.target.origin !== linkOrigin || options.expectedOrigin !== void 0 && (linkOrigin !== options.expectedOrigin || envelope.target.origin !== options.expectedOrigin)) {
@@ -6180,12 +6337,18 @@ async function inspectShare(link, options = {}) {
6180
6337
  const resolved = await resolveShareEnvelope(link, options);
6181
6338
  if (resolved.envelope.version === 1 && resolved.envelope.authorizationTarget.kind === "policy") await verifyV1PolicyEnvelope(resolved.envelope, resolved.origin, options);
6182
6339
  else if (resolved.envelope.version === 1) await verifyResolved(resolved.envelope, resolved.origin, options);
6183
- else await verifyV2Envelope(resolved.envelope, resolved.origin, options);
6184
- if (resolved.envelope.version === 2 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6340
+ else if (resolved.envelope.version === 2) await verifyV2Envelope(resolved.envelope, resolved.origin, options);
6341
+ else await verifyV3Envelope(resolved.envelope, resolved.origin, options);
6342
+ if (resolved.envelope.version === 2 || resolved.envelope.version === 3 || resolved.envelope.authorizationTarget.kind === "policy") options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6185
6343
  return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: resolved.kind } };
6186
6344
  }
6187
6345
  async function receiveShare(link, options = {}) {
6188
6346
  const resolved = await resolveShareEnvelope(link, options);
6347
+ if (resolved.envelope.version === 3) {
6348
+ await verifyV3Envelope(resolved.envelope, resolved.origin, options);
6349
+ options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
6350
+ return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
6351
+ }
6189
6352
  if (resolved.envelope.version === 2) {
6190
6353
  await verifyV2Envelope(resolved.envelope, resolved.origin, options);
6191
6354
  options.onResolvedAddressedEnvelope?.(resolved.envelope, resolved.cid);
@@ -8728,11 +8891,80 @@ function uiAction(action) {
8728
8891
  function selectedAction(envelope) {
8729
8892
  return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
8730
8893
  }
8731
- function hex(value) {
8894
+ function policyAttenuationForV3(envelope) {
8895
+ const policy = object2(envelope.policy, "v3 policy");
8896
+ if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
8897
+ const attenuation = {};
8898
+ for (const raw of policy.capabilityCeiling) {
8899
+ const capability = object2(raw, "v3 policy capability");
8900
+ if (typeof capability.resource !== "string" || attenuation[capability.resource] !== void 0) throw new Error("v3 policy capability is invalid");
8901
+ if (capability.kind === "encryption") {
8902
+ if (capability.action !== "tinycloud.encryption/decrypt") throw new Error("v3 policy capability is invalid");
8903
+ attenuation[capability.resource] = { [capability.action]: [{}] };
8904
+ continue;
8905
+ }
8906
+ if (capability.kind !== "kv" || capability.selector !== "exact" && capability.selector !== "prefix" || !Array.isArray(capability.actions) || capability.actions.length === 0 || capability.actions.some((action) => typeof action !== "string")) throw new Error("v3 policy capability is invalid");
8907
+ attenuation[capability.resource] = Object.fromEntries(capability.actions.map((action) => [action, [{
8908
+ type: "xyz.tinycloud.resource/selector",
8909
+ kind: capability.selector,
8910
+ value: capability.resource
8911
+ }]]));
8912
+ }
8913
+ return attenuation;
8914
+ }
8915
+ var POLICY_SESSION_FACT_KEYS = [
8916
+ "profile",
8917
+ "ownerDid",
8918
+ "policyId",
8919
+ "policyDigestHex",
8920
+ "policyCid",
8921
+ "policyDelegationCid",
8922
+ "enforcementDelegationCid",
8923
+ "contentSourceDigestHex",
8924
+ "capabilityCeilingHashHex",
8925
+ "nativeProjectionHashHex",
8926
+ "enforcerDid",
8927
+ "nodeAudience",
8928
+ "recipientDid",
8929
+ "challengeId",
8930
+ "claimDigestHex",
8931
+ "claimJti",
8932
+ "vpDigestHex",
8933
+ "credentialEvidenceDigestHex",
8934
+ "decisionContextDigestHex",
8935
+ "issuanceAuditDigestHex",
8936
+ "remainingRedelegationDepth"
8937
+ ];
8938
+ var POLICY_SESSION_V4_AUDIT_FACT_KEYS = ["credentialIdAuditDigestHex", "presentationJtiAuditDigestHex"];
8939
+ var POLICY_SESSION_DIGEST_FACT_KEYS = [
8940
+ "policyDigestHex",
8941
+ "contentSourceDigestHex",
8942
+ "capabilityCeilingHashHex",
8943
+ "nativeProjectionHashHex",
8944
+ "claimDigestHex",
8945
+ "vpDigestHex",
8946
+ "credentialEvidenceDigestHex",
8947
+ "decisionContextDigestHex",
8948
+ "issuanceAuditDigestHex"
8949
+ ];
8950
+ var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
8951
+ function verifyV3PolicyAuthorization(input) {
8952
+ const compact = verifyCompactUcanAuthorization(input.authorization, input.cid);
8953
+ const fact = compact.payload.fct[0];
8954
+ const factKeys = Object.keys(fact);
8955
+ const legacyShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
8956
+ const v4Shape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
8957
+ const binding = object2(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8958
+ const policy = object2(input.envelope.policy, "v3 policy");
8959
+ const now = Math.floor(Date.now() / 1e3);
8960
+ if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || compact.payload.exp - compact.payload.nbf > 60 || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
8961
+ return compact;
8962
+ }
8963
+ function hex2(value) {
8732
8964
  return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8733
8965
  }
8734
8966
  function canonicalHashHex(value) {
8735
- return hex(sha2566(new TextEncoder().encode(canonicalize(value))));
8967
+ return hex2(sha2566(new TextEncoder().encode(canonicalize(value))));
8736
8968
  }
8737
8969
  async function aesGcmDecrypt(key, blob) {
8738
8970
  if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
@@ -8788,6 +9020,20 @@ var ShareRecipientClient = class {
8788
9020
  this.options = options;
8789
9021
  this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
8790
9022
  this.signer = options.sign;
9023
+ if (options.policyAuthorization !== void 0) {
9024
+ if (options.envelope.version !== 3) throw new Error("policy delegation fast path requires a v3 envelope");
9025
+ const compact = verifyV3PolicyAuthorization({ ...options.policyAuthorization, envelope: options.envelope, holderDid: options.holderDid });
9026
+ const nodeAudience = compact.payload.fct[0].nodeAudience;
9027
+ this.v3Authorization = options.policyAuthorization.authorization;
9028
+ this.v3NodeAudience = nodeAudience;
9029
+ this.nativeSigner = options.sign;
9030
+ this.session = {
9031
+ sessionId: options.policyAuthorization.cid,
9032
+ expiresAt: new Date(compact.payload.exp * 1e3).toISOString(),
9033
+ actions: options.envelope.actions,
9034
+ resource: options.envelope.resource
9035
+ };
9036
+ }
8791
9037
  }
8792
9038
  fetchFn;
8793
9039
  session;
@@ -8795,6 +9041,7 @@ var ShareRecipientClient = class {
8795
9041
  nativeSigner;
8796
9042
  holderProof;
8797
9043
  v3Authorization;
9044
+ v3NodeAudience;
8798
9045
  v3ContentKey;
8799
9046
  v3ContentEnvelope;
8800
9047
  async beginChallenge(envelope) {
@@ -8884,37 +9131,52 @@ var ShareRecipientClient = class {
8884
9131
  }
8885
9132
  async establishV3(envelope) {
8886
9133
  if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
9134
+ const attestedEnforcer = object2(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
9135
+ if (attestedEnforcer.enforcerDid !== envelope.target.nodeAudience || typeof attestedEnforcer.nodeAudience !== "string") throw new Error("v3 attested enforcer binding mismatch");
9136
+ try {
9137
+ ed25519PublicKeyFromDidKey(attestedEnforcer.nodeAudience);
9138
+ } catch {
9139
+ throw new Error("v3 attested Node audience is invalid");
9140
+ }
8887
9141
  const challengeResponse = await this.fetchFn(new URL("/share/v3/policy/challenges", this.options.nodeOrigin), {
8888
9142
  method: "POST",
8889
9143
  redirect: "error",
8890
9144
  headers: { accept: "application/json", "content-type": "application/json" },
8891
- body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling })
9145
+ body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling }),
9146
+ ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8892
9147
  });
8893
9148
  if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
8894
9149
  const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
8895
- if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid) throw new Error("v3 policy challenge binding mismatch");
9150
+ if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid || challenge.nodeAudience !== attestedEnforcer.nodeAudience || challenge.enforcerDid !== void 0 && challenge.enforcerDid !== attestedEnforcer.enforcerDid || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("v3 policy challenge binding mismatch");
8896
9151
  const material = await this.options.buildPresentation({
8897
9152
  challenge,
8898
9153
  envelope,
8899
9154
  policy: envelope.policy
8900
9155
  });
8901
- if (material.sign === void 0 || material.claim === void 0 || material.presentation === void 0) throw new Error("v3 ceremony requires a recipient signer, claim, and presentation");
9156
+ if (material.sign === void 0 || material.presentation === void 0) throw new Error("v3 ceremony requires a recipient signer and presentation");
9157
+ const accountless = material.presentation.schema === "xyz.tinycloud.policy/presentation/v4";
9158
+ if (accountless ? material.credentialEnvelope === void 0 || material.requirement === void 0 : material.claim === void 0) {
9159
+ throw new Error(accountless ? "v4 ceremony requires a verified credential and requirement" : "v3 ceremony requires a claim");
9160
+ }
9161
+ this.options.onStage?.("policy-admission");
8902
9162
  const delegationResponse = await this.fetchFn(new URL("/share/v3/policy/delegations", this.options.nodeOrigin), {
8903
9163
  method: "POST",
8904
9164
  redirect: "error",
8905
9165
  headers: { accept: "application/json", "content-type": "application/json" },
8906
- body: JSON.stringify({ policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation })
9166
+ body: JSON.stringify(accountless ? { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, requirement: material.requirement, credential: material.credentialEnvelope, presentation: material.presentation } : { policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation }),
9167
+ ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8907
9168
  });
8908
9169
  if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8909
9170
  const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
8910
9171
  if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
8911
- const compact = verifyCompactUcanAuthorization(delegation.authorization, delegation.sessionCid);
9172
+ const compact = verifyV3PolicyAuthorization({ authorization: delegation.authorization, cid: delegation.sessionCid, envelope, holderDid: this.options.holderDid });
8912
9173
  const fact = compact.payload.fct[0];
8913
- if (compact.payload.aud !== this.options.holderDid || fact.profile !== "policy-session-ucan/v1" || fact.policyCid !== envelope.policyCid || fact.recipientDid !== this.options.holderDid || fact.policyDelegationCid !== envelope.policyRoot.cid || fact.enforcementDelegationCid !== envelope.enforcementRoot.cid || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== envelope.policyRoot.cid || compact.payload.prf[1] !== envelope.enforcementRoot.cid || compact.payload.exp - compact.payload.nbf > 60) throw new Error("v3 policy delegation signed binding mismatch");
8914
- const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization } });
9174
+ this.options.onStage?.("delegation-import");
9175
+ const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization }, ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
8915
9176
  if (!imported.ok) throw new Error(`ordinary delegation import rejected (${imported.status})`);
8916
9177
  this.signer = material.sign;
8917
9178
  this.v3Authorization = delegation.authorization;
9179
+ this.v3NodeAudience = fact.nodeAudience;
8918
9180
  this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
8919
9181
  return this.session;
8920
9182
  }
@@ -8922,18 +9184,18 @@ var ShareRecipientClient = class {
8922
9184
  async decryptV3Content(bytes3) {
8923
9185
  const envelope = this.options.envelope;
8924
9186
  const signer = this.nativeSigner ?? this.signer;
8925
- if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
9187
+ if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
8926
9188
  const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
8927
9189
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
8928
9190
  const receiverPublicKey = toBase64Url(x25519.getPublicKey(receiverPrivateKey));
8929
9191
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
8930
- const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: envelope.target.nodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
8931
- const bodyHash = hex(sha2566(new TextEncoder().encode(canonicalize(body))));
9192
+ const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: this.v3NodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
9193
+ const bodyHash = hex2(sha2566(new TextEncoder().encode(canonicalize(body))));
8932
9194
  const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
8933
9195
  const now = Math.floor(Date.now() / 1e3);
8934
9196
  const invocation = await signCompactUcanAuthorization({
8935
9197
  issuerDid: this.options.holderDid,
8936
- audienceDid: envelope.target.nodeAudience,
9198
+ audienceDid: this.v3NodeAudience,
8937
9199
  attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
8938
9200
  facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
8939
9201
  proofs: [session.cid],
@@ -8943,11 +9205,12 @@ var ShareRecipientClient = class {
8943
9205
  sign: signer
8944
9206
  });
8945
9207
  try {
8946
- const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body) });
9208
+ this.options.onStage?.("decryption");
9209
+ const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body), ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
8947
9210
  if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
8948
9211
  const value = object2(await response.json(), "v3 decrypt response");
8949
9212
  const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
8950
- if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
9213
+ if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex2(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
8951
9214
  const unsigned = { ...value };
8952
9215
  delete unsigned.nodeSignature;
8953
9216
  const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
@@ -9017,7 +9280,7 @@ var ShareRecipientClient = class {
9017
9280
  }
9018
9281
  async nativeInvokeV3(request, envelope) {
9019
9282
  const signer = this.nativeSigner ?? this.signer;
9020
- if (this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
9283
+ if (this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
9021
9284
  const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
9022
9285
  if (session.payload.aud !== this.options.holderDid) throw new Error("v3 session recipient mismatch");
9023
9286
  const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
@@ -9031,7 +9294,7 @@ var ShareRecipientClient = class {
9031
9294
  if (path !== root && !(capability.selector === "prefix" && path.startsWith(`${root}/`))) throw new Error("v3 KV request is outside the signed selector");
9032
9295
  const resource = `${capability.resource.slice(0, split + marker.length)}${path}`;
9033
9296
  const now = Math.floor(Date.now() / 1e3);
9034
- const invocation = await signCompactUcanAuthorization({ issuerDid: this.options.holderDid, audienceDid: envelope.target.nodeAudience, attenuation: { [resource]: { [action]: [{ type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: resource }] } }, facts: [{ type: "tinycloud.policy.invocation/v1", policyCid: envelope.policyCid, sessionCid: session.cid }], proofs: [session.cid], notBefore: now, expiresAt: Math.min(now + 60, session.payload.exp), nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))), sign: signer });
9297
+ const invocation = await signCompactUcanAuthorization({ issuerDid: this.options.holderDid, audienceDid: this.v3NodeAudience, attenuation: { [resource]: { [action]: [{ type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: resource }] } }, facts: [{ type: "tinycloud.policy.invocation/v1", policyCid: envelope.policyCid, sessionCid: session.cid }], proofs: [session.cid], notBefore: now, expiresAt: Math.min(now + 60, session.payload.exp), nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))), sign: signer });
9035
9298
  const headers = new Headers({ accept: "application/json", Authorization: invocation.authorization });
9036
9299
  let body;
9037
9300
  if (action === "tinycloud.kv/put") {
@@ -9040,7 +9303,8 @@ var ShareRecipientClient = class {
9040
9303
  headers.set("content-type", request.contentType ?? "application/octet-stream");
9041
9304
  if (request.ifMatch !== void 0) headers.set("if-match", request.ifMatch);
9042
9305
  }
9043
- return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body } });
9306
+ this.options.onStage?.("invocation");
9307
+ return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body }, ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
9044
9308
  }
9045
9309
  };
9046
9310
  function createAddressedAuthorization(input) {