@tinycloud/share-sdk 1.4.1-beta.0 → 1.4.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
- export { f as ShareMetadata, p as ShareRecipientClient, s as inspectShare } from './web-recipient-entry-CiUzdSQs.cjs';
1
+ export { f as ShareMetadata, r as ShareRecipientClient, u as inspectShare } from './web-recipient-entry-Cy1fXl_5.cjs';
2
2
  import '@tinycloud/share-envelope';
3
3
  import './authorization-CmP1skO8.cjs';
@@ -1,3 +1,3 @@
1
- export { f as ShareMetadata, p as ShareRecipientClient, s as inspectShare } from './web-recipient-entry-DVyTQBl0.js';
1
+ export { f as ShareMetadata, r as ShareRecipientClient, u as inspectShare } from './web-recipient-entry-CPntAP0H.js';
2
2
  import '@tinycloud/share-envelope';
3
3
  import './authorization-CmP1skO8.js';
@@ -5225,6 +5225,23 @@ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) =>
5225
5225
  const network = separator < 0 ? "" : rest.slice(separator + 1);
5226
5226
  return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
5227
5227
  }, { message: "expected canonical TinyCloud encryption network" });
5228
+ var unifiedEncryptionResourceSchema = external_exports.string().refine((value) => {
5229
+ const prefix = "urn:tinycloud:encryption:";
5230
+ if (!value.startsWith(prefix)) return false;
5231
+ const match = /^(did:.+):([^/:?#\s]+)(?:\/(.+))?$/.exec(value.slice(prefix.length));
5232
+ if (match === null) return false;
5233
+ const path = match[3];
5234
+ return path === void 0 || path.split("/").every(
5235
+ (segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment)
5236
+ );
5237
+ }, { message: "expected a canonical TinyCloud encryption resource" });
5238
+ var unifiedEncryptionKeyPathSchema = external_exports.string().regex(/^share\/[^/]+$/).refine(
5239
+ (value) => {
5240
+ const segment = value.slice("share/".length);
5241
+ return segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment);
5242
+ },
5243
+ { message: "expected an exact share key path" }
5244
+ );
5228
5245
  var unifiedKvCapabilitySchema = external_exports.object({
5229
5246
  kind: external_exports.literal("kv"),
5230
5247
  resource: unifiedResourceSchema,
@@ -5238,7 +5255,7 @@ var unifiedKvCapabilitySchema = external_exports.object({
5238
5255
  }).strict();
5239
5256
  var unifiedEncryptionCapabilitySchema = external_exports.object({
5240
5257
  kind: external_exports.literal("encryption"),
5241
- resource: unifiedEncryptionNetworkSchema,
5258
+ resource: unifiedEncryptionResourceSchema,
5242
5259
  action: external_exports.literal("tinycloud.encryption/decrypt")
5243
5260
  }).strict();
5244
5261
  var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
@@ -5247,6 +5264,7 @@ var unifiedContentSourceSchema = external_exports.object({
5247
5264
  kvResource: unifiedResourceSchema,
5248
5265
  selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5249
5266
  encryptionNetwork: unifiedEncryptionNetworkSchema,
5267
+ encryptionKeyPath: unifiedEncryptionKeyPathSchema.optional(),
5250
5268
  encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5251
5269
  keyVersion: external_exports.number().int().positive(),
5252
5270
  mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
@@ -5364,22 +5382,71 @@ function validateV3Invariants(value, ctx) {
5364
5382
  if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5365
5383
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
5366
5384
  }
5367
- 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) {
5385
+ const encryptionKeyPath = value.contentSource.encryptionKeyPath;
5386
+ if (encryptionKeyPath !== void 0 && encryptionKeyPath !== `share/${value.contentSource.shareId}`) {
5387
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource", "encryptionKeyPath"], message: "share key path must name this share" });
5388
+ }
5389
+ 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.encryptionKeyPath !== encryptionKeyPath || 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) {
5368
5390
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
5369
5391
  }
5370
5392
  if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
5371
5393
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
5372
5394
  }
5373
5395
  const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
5374
- const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === value.encryptionNetwork && capability.action === "tinycloud.encryption/decrypt");
5396
+ const expectedEncryptionResource = encryptionKeyPath === void 0 ? value.encryptionNetwork : `${value.encryptionNetwork}/${encryptionKeyPath}`;
5397
+ const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === expectedEncryptionResource && capability.action === "tinycloud.encryption/decrypt");
5375
5398
  if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
5376
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
5399
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain the exact decrypt resource" });
5377
5400
  }
5378
5401
  }
5379
5402
  var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
5380
5403
  var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
5381
5404
  var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
5405
+ var SEALED_BLOB_VERSION = 1;
5382
5406
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
5407
+ var KEY_LENGTH = 32;
5408
+ var NONCE_LENGTH = 12;
5409
+ var TAG_LENGTH = 16;
5410
+ var HEADER_LENGTH = 1;
5411
+ function assertKey(key32) {
5412
+ if (key32.length !== KEY_LENGTH) {
5413
+ throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
5414
+ }
5415
+ }
5416
+ async function importAesKey(key32, usage) {
5417
+ assertKey(key32);
5418
+ return globalThis.crypto.subtle.importKey(
5419
+ "raw",
5420
+ key32,
5421
+ "AES-GCM",
5422
+ false,
5423
+ [usage]
5424
+ );
5425
+ }
5426
+ async function open(blob, key32) {
5427
+ if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
5428
+ throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
5429
+ }
5430
+ if (blob[0] !== SEALED_BLOB_VERSION) {
5431
+ throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
5432
+ }
5433
+ const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
5434
+ const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
5435
+ return decryptEnvelope(nonce, ciphertext, key32);
5436
+ }
5437
+ async function decryptEnvelope(nonce, ciphertext, key32) {
5438
+ if (nonce.length !== NONCE_LENGTH) {
5439
+ throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
5440
+ }
5441
+ const key = await importAesKey(key32, "decrypt");
5442
+ return new Uint8Array(
5443
+ await globalThis.crypto.subtle.decrypt(
5444
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5445
+ key,
5446
+ ciphertext
5447
+ )
5448
+ );
5449
+ }
5383
5450
  function isPlainRecord(value) {
5384
5451
  const proto = Object.getPrototypeOf(value);
5385
5452
  return proto === Object.prototype || proto === null;
@@ -5510,6 +5577,7 @@ function equal(left, right) {
5510
5577
  return left.length === right.length && left.every((byte, index) => byte === right[index]);
5511
5578
  }
5512
5579
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5580
+ var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
5513
5581
  var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
5514
5582
  var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
5515
5583
  var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
@@ -5517,6 +5585,14 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
5517
5585
  var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
5518
5586
  var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
5519
5587
  var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
5588
+ function signingBytesV2(unsigned) {
5589
+ const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
5590
+ const body = utf8Bytes(canonicalize(unsigned));
5591
+ const bytes = new Uint8Array(domain.length + body.length);
5592
+ bytes.set(domain);
5593
+ bytes.set(body, domain.length);
5594
+ return bytes;
5595
+ }
5520
5596
  function signingBytesV3(unsigned) {
5521
5597
  const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
5522
5598
  const body = utf8Bytes(canonicalize(unsigned));
@@ -5525,6 +5601,17 @@ function signingBytesV3(unsigned) {
5525
5601
  bytes.set(body, domain.length);
5526
5602
  return bytes;
5527
5603
  }
5604
+ function verifyEnvelopeV2SignatureOnly(envelope) {
5605
+ const parsed = shareEnvelopeV2Schema.parse(envelope);
5606
+ const { signature, ...unsigned } = parsed;
5607
+ return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5608
+ }
5609
+ async function verifyEnvelopeV2(envelope, options) {
5610
+ const parsed = shareEnvelopeV2Schema.parse(envelope);
5611
+ if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
5612
+ if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
5613
+ return true;
5614
+ }
5528
5615
  function verifyEnvelopeV3SignatureOnly(envelope) {
5529
5616
  const parsed = shareEnvelopeV3Schema.parse(envelope);
5530
5617
  const { signature, ...unsigned } = parsed;
@@ -5611,6 +5698,52 @@ function base32Lower(bytes) {
5611
5698
  if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
5612
5699
  return output;
5613
5700
  }
5701
+ var KEY_LENGTH2 = 32;
5702
+ function assertCanonicalCid(cidString) {
5703
+ const cid = CID.parse(cidString);
5704
+ if (cid.version !== 1 || cid.code !== code || cid.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
5705
+ cid.toString() !== cidString) {
5706
+ throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
5707
+ }
5708
+ }
5709
+ function parseShareUrl(url, options = {}) {
5710
+ const parsed = new URL(url);
5711
+ if (parsed.protocol !== "https:") {
5712
+ throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
5713
+ }
5714
+ if (parsed.username !== "" || parsed.password !== "") {
5715
+ throw new TypeError("share URL must not carry userinfo");
5716
+ }
5717
+ if (parsed.search !== "") {
5718
+ throw new TypeError("share URL must not have a query string");
5719
+ }
5720
+ if (options.expectedOrigin !== void 0) {
5721
+ if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
5722
+ throw new TypeError(
5723
+ `expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
5724
+ );
5725
+ }
5726
+ if (parsed.origin !== options.expectedOrigin) {
5727
+ throw new TypeError(
5728
+ `share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
5729
+ );
5730
+ }
5731
+ }
5732
+ const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
5733
+ if (!match || match[1] === void 0) {
5734
+ throw new TypeError(`not a share URL path: ${parsed.pathname}`);
5735
+ }
5736
+ const ciphertextCid = match[1];
5737
+ assertCanonicalCid(ciphertextCid);
5738
+ if (!parsed.hash.startsWith("#k=")) {
5739
+ throw new TypeError("share URL is missing the #k= key fragment");
5740
+ }
5741
+ const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
5742
+ if (key32.length !== KEY_LENGTH2) {
5743
+ throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
5744
+ }
5745
+ return { ciphertextCid, key32 };
5746
+ }
5614
5747
 
5615
5748
  // src/artifact-core.ts
5616
5749
  var ARTIFACT_ROOT = "artifacts";
@@ -5707,7 +5840,7 @@ var ShareReceiveError = class extends Error {
5707
5840
  }
5708
5841
  };
5709
5842
  function metadataFor(envelope, origin) {
5710
- const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
5843
+ const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
5711
5844
  return {
5712
5845
  protocol: "tinycloud-share",
5713
5846
  version: 1,
@@ -5722,7 +5855,7 @@ function metadataFor(envelope, origin) {
5722
5855
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
5723
5856
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
5724
5857
  },
5725
- ...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
5858
+ ...envelope.version === 3 && envelope.artifactPin !== void 0 ? { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } } : {}
5726
5859
  };
5727
5860
  }
5728
5861
  function receiveErrorFor(error) {
@@ -5820,10 +5953,123 @@ async function resolvePolicyShare(link, options) {
5820
5953
  options.onResolvedAddressedEnvelope?.(envelope, cid);
5821
5954
  return { envelope, origin: url.origin, cid };
5822
5955
  }
5956
+ async function resolveLegacyV2(link, options) {
5957
+ let parsed;
5958
+ try {
5959
+ parsed = parseShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5960
+ } catch {
5961
+ throw new ShareReceiveError("invalid-link", "legacy share link format is invalid");
5962
+ }
5963
+ try {
5964
+ options.signal?.throwIfAborted();
5965
+ const origin = new URL(link).origin;
5966
+ const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
5967
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope limit is invalid");
5968
+ let sealed;
5969
+ if (options.fetchBlob !== void 0) {
5970
+ try {
5971
+ sealed = await options.fetchBlob({ origin, cid: parsed.ciphertextCid, ...options.signal === void 0 ? {} : { signal: options.signal } });
5972
+ } catch {
5973
+ throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
5974
+ }
5975
+ } else {
5976
+ if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry is required");
5977
+ const response = await (options.fetch ?? globalThis.fetch)(`${options.registryBaseUrl.replace(/\/+$/, "")}/ipfs/${parsed.ciphertextCid}?format=raw`, {
5978
+ headers: { accept: "application/vnd.ipld.raw" },
5979
+ redirect: "error",
5980
+ ...options.signal === void 0 ? {} : { signal: options.signal }
5981
+ });
5982
+ if (!response.ok) throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
5983
+ if (Number(response.headers.get("content-length")) > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
5984
+ const reader = response.body?.getReader();
5985
+ if (reader === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry returned no body");
5986
+ const chunks = [];
5987
+ let length2 = 0;
5988
+ for (; ; ) {
5989
+ const part = await reader.read();
5990
+ if (part.done) break;
5991
+ length2 += part.value.length;
5992
+ if (length2 > limit) {
5993
+ await reader.cancel();
5994
+ throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
5995
+ }
5996
+ chunks.push(part.value);
5997
+ }
5998
+ sealed = new Uint8Array(length2);
5999
+ let offset = 0;
6000
+ for (const chunk of chunks) {
6001
+ sealed.set(chunk, offset);
6002
+ offset += chunk.length;
6003
+ }
6004
+ }
6005
+ options.signal?.throwIfAborted();
6006
+ if (!(sealed instanceof Uint8Array) || sealed.length > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6007
+ if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "legacy envelope CID does not match");
6008
+ let plaintext;
6009
+ try {
6010
+ plaintext = await open(sealed, parsed.key32);
6011
+ } catch {
6012
+ throw new ShareReceiveError("decrypt-failed", "legacy envelope could not be opened");
6013
+ }
6014
+ let envelope;
6015
+ try {
6016
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
6017
+ const value = JSON.parse(encoded);
6018
+ if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
6019
+ envelope = shareEnvelopeV2Schema.parse(value);
6020
+ } catch {
6021
+ throw new ShareReceiveError("envelope-invalid", "legacy envelope is invalid");
6022
+ }
6023
+ return { envelope, origin, cid: parsed.ciphertextCid };
6024
+ } finally {
6025
+ parsed.key32.fill(0);
6026
+ }
6027
+ }
6028
+ function evidenceMatchesV2(envelope, evidence) {
6029
+ const actions = new Set(evidence.actions.map((action) => action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : action));
6030
+ const resourceCovers = evidence.resource.kind === "exact" ? envelope.resource.kind === "exact" && evidence.resource.path === envelope.resource.path : envelope.resource.path === evidence.resource.path || envelope.resource.path.startsWith(evidence.resource.path.endsWith("/") ? evidence.resource.path : `${evidence.resource.path}/`);
6031
+ return envelope.authorizationTarget.kind === "policy" && evidence.policyCid === envelope.authorizationTarget.policyCid && evidence.signerDid === envelope.signature.signerDid && evidence.registrationCid.length > 0 && evidence.shareId === envelope.shareId && canonicalize(evidence.recipientMatcher) === canonicalize(envelope.recipientMatcher) && canonicalize(evidence.target) === canonicalize(envelope.target) && resourceCovers && envelope.actions.every((action) => actions.has(action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : "tinycloud.kv/put")) && canonicalize(evidence.contentSource) === canonicalize(envelope.contentSource) && evidence.contentSourceDigest === envelope.contentSourceDigest && evidence.delegationCid === envelope.delegationCid && evidence.authorityMaterialHandle === envelope.authorityMaterialHandle && evidence.authorityMaterialDigest === envelope.authorityMaterialDigest && evidence.expiresAt === envelope.expiry && Number.isFinite(Date.parse(evidence.expiresAt)) && Date.parse(evidence.expiresAt) === Date.parse(envelope.expiry);
6032
+ }
6033
+ async function verifyLegacyV2(envelope, options) {
6034
+ try {
6035
+ if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
6036
+ } catch {
6037
+ throw new ShareReceiveError("signature-invalid", "legacy envelope signature is invalid");
6038
+ }
6039
+ const expiry = Date.parse(envelope.expiry);
6040
+ if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "legacy share expiry is invalid");
6041
+ if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "legacy share has expired", { expiresAt: envelope.expiry });
6042
+ if (envelope.authorizationTarget.kind === "policy") {
6043
+ if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "a legacy policy authority is required");
6044
+ let evidence;
6045
+ try {
6046
+ evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
6047
+ } catch {
6048
+ }
6049
+ if (evidence === void 0 || !evidenceMatchesV2(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "legacy policy evidence does not match");
6050
+ if (options.trustedSignerDid !== void 0 && options.trustedSignerDid !== evidence.signerDid) throw new ShareReceiveError("signature-invalid", "legacy signer is not trusted");
6051
+ } else if (options.trustedSignerDid === void 0 || options.trustedSignerDid !== envelope.signature.signerDid || envelope.authorizationTarget.kind === "recipientDid" && envelope.recipientMatcher.kind === "recipientDid" && envelope.authorizationTarget.did !== envelope.recipientMatcher.value) {
6052
+ throw new ShareReceiveError("capability-invalid", "legacy recipient authority does not match");
6053
+ }
6054
+ }
5823
6055
  async function inspectShare(link, options = {}) {
6056
+ if (isLegacyV2Link(link)) {
6057
+ const legacy = await resolveLegacyV2(link, options);
6058
+ await verifyLegacyV2(legacy.envelope, options);
6059
+ options.onResolvedAddressedEnvelope?.(legacy.envelope, legacy.cid);
6060
+ return { metadata: metadataFor(legacy.envelope, legacy.origin), link: { origin: legacy.origin, cid: legacy.cid, kind: "compact" } };
6061
+ }
5824
6062
  const resolved = await resolvePolicyShare(link, options);
5825
6063
  return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
5826
6064
  }
6065
+ function isLegacyV2Link(link) {
6066
+ try {
6067
+ const url = new URL(link);
6068
+ return /^\/s\/bafkrei[a-z2-7]{52}$/.test(url.pathname) && url.hash.startsWith("#k=");
6069
+ } catch {
6070
+ return false;
6071
+ }
6072
+ }
5827
6073
 
5828
6074
  // src/recipient.ts
5829
6075
  import { ed25519 as ed255194, x25519 } from "@noble/curves/ed25519";
@@ -6019,8 +6265,8 @@ function parseV3InlineEncryptedEnvelope(bytes, expected) {
6019
6265
  }
6020
6266
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
6021
6267
  const record = value;
6022
- const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
6023
- if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== 1 || record.networkId !== expected.encryptionNetwork || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== expected.contentSource.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || record.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.metadata !== void 0 && (record.metadata === null || typeof record.metadata !== "object" || Array.isArray(record.metadata) || Object.values(record.metadata).some((entry) => typeof entry !== "string"))) {
6268
+ const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
6269
+ if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== 1 || record.networkId !== expected.encryptionNetwork || record.keyPath !== expected.contentSource.encryptionKeyPath || record.keyPath !== void 0 && record.keyPath !== `share/${expected.shareId}` || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== expected.contentSource.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || record.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.metadata !== void 0 && (record.metadata === null || typeof record.metadata !== "object" || Array.isArray(record.metadata) || Object.values(record.metadata).some((entry) => typeof entry !== "string"))) {
6024
6270
  throw new Error("encrypted content envelope binding is invalid");
6025
6271
  }
6026
6272
  return record;
@@ -6152,15 +6398,16 @@ var ShareRecipientClient = class {
6152
6398
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
6153
6399
  const receiverPublicKey = toBase64(x25519.getPublicKey(receiverPrivateKey));
6154
6400
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
6155
- 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 };
6401
+ const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: this.v3NodeAudience, networkId: encrypted.networkId, ...encrypted.keyPath === void 0 ? {} : { keyPath: encrypted.keyPath }, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
6156
6402
  const bodyHash = hex2(sha2563(new TextEncoder().encode(canonicalize(body))));
6157
6403
  const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
6158
6404
  const now = Math.floor(Date.now() / 1e3);
6405
+ const decryptResource = encrypted.keyPath === void 0 ? encrypted.networkId : `${encrypted.networkId}/${encrypted.keyPath}`;
6159
6406
  const invocation = await signCompactUcanAuthorization({
6160
6407
  issuerDid: this.options.holderDid,
6161
6408
  audienceDid: this.v3NodeAudience,
6162
- attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
6163
- facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
6409
+ attenuation: { [decryptResource]: { "tinycloud.encryption/decrypt": [{}] } },
6410
+ facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, ...encrypted.keyPath === void 0 ? {} : { keyPath: encrypted.keyPath }, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
6164
6411
  proofs: [session.cid],
6165
6412
  notBefore: now,
6166
6413
  expiresAt: Math.min(now + 60, session.payload.exp),