@tinycloud/share-sdk 1.4.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,5 +1,5 @@
1
- import { ShareEnvelopeV3, ShareEnvelopeV2 } from '@tinycloud/share-envelope';
2
- import { a as ShareAuthorizationMethod, f as ShareAuthorizedContent, c as ShareAuthorizationAdapter } from './authorization-CmP1skO8.js';
1
+ import { ShareEnvelopeV2, ShareEnvelopeV3 } from '@tinycloud/share-envelope';
2
+ import { c as ShareAuthorizationAdapter, f as ShareAuthorizedContent, a as ShareAuthorizationMethod } from './authorization-CmP1skO8.js';
3
3
 
4
4
  type ShareErrorCode = "invalid-link" | "fetch-failed" | "max-bytes-exceeded" | "cid-mismatch" | "decrypt-failed" | "envelope-invalid" | "origin-mismatch" | "signature-invalid" | "capability-invalid" | "expired" | "unsupported-target" | "authorization-denied" | "content-integrity-failed";
5
5
  declare const SHARE_RESULT_VERSION: 1;
@@ -42,7 +42,19 @@ interface ShareFetchOptions {
42
42
  readonly now?: () => number;
43
43
  readonly maxSealedBlobBytes?: number;
44
44
  readonly maxContentBlobBytes?: number;
45
- readonly onResolvedAddressedEnvelope?: (envelope: ShareEnvelopeV3, cid: string) => void;
45
+ readonly onResolvedAddressedEnvelope?: (envelope: ShareEnvelopeV2 | ShareEnvelopeV3, cid: string) => void;
46
+ /** Legacy V2 registry transport; used only for existing compact V2 links. */
47
+ readonly registryBaseUrl?: string;
48
+ readonly fetchBlob?: (input: {
49
+ readonly origin: string;
50
+ readonly cid: string;
51
+ readonly signal?: AbortSignal;
52
+ }) => Promise<Uint8Array>;
53
+ readonly trustedSignerDid?: string;
54
+ readonly trustedPolicyAuthority?: SharePolicyAuthority;
55
+ readonly authorization?: ShareAuthorizationAdapter<ShareAuthorizedContent>;
56
+ readonly authorizationResumeToken?: string;
57
+ readonly authorizationProof?: unknown;
46
58
  }
47
59
  interface ShareMetadata {
48
60
  readonly protocol: "tinycloud-share";
@@ -77,8 +89,37 @@ interface ShareInspection {
77
89
  readonly link: {
78
90
  readonly origin: string;
79
91
  readonly cid: string;
80
- readonly kind: "policy";
92
+ readonly kind: "policy" | "compact";
93
+ };
94
+ }
95
+ interface SharePolicyEvidence {
96
+ readonly policyCid: string;
97
+ readonly signerDid: string;
98
+ readonly registrationCid: string;
99
+ readonly shareId: string;
100
+ readonly recipientMatcher: unknown;
101
+ readonly target: {
102
+ readonly origin: string;
103
+ readonly nodeAudience: string;
104
+ readonly spaceId: string;
81
105
  };
106
+ readonly resource: {
107
+ readonly kind: "exact" | "prefix";
108
+ readonly path: string;
109
+ };
110
+ readonly actions: readonly string[];
111
+ readonly contentSource: unknown;
112
+ readonly contentSourceDigest: string;
113
+ readonly delegationCid: string;
114
+ readonly authorityMaterialHandle: string;
115
+ readonly authorityMaterialDigest: string;
116
+ readonly expiresAt: string;
117
+ }
118
+ interface SharePolicyAuthority {
119
+ resolve(input: {
120
+ readonly policyCid: string;
121
+ readonly envelope: ShareEnvelopeV2;
122
+ }): Promise<SharePolicyEvidence | undefined>;
82
123
  }
83
124
  interface ShareReceiveAuthorizationRequired {
84
125
  readonly state: "authorization-required";
@@ -211,4 +252,4 @@ declare function createAddressedAuthorization(input: Omit<ShareRecipientClientOp
211
252
  }) => Promise<SharePresentationMaterial>;
212
253
  }): ShareAuthorizationAdapter<ShareAuthorizedContent>;
213
254
 
214
- export { DEFAULT_MAX_CONTENT_BLOB_BYTES as D, SHARE_RESULT_VERSION as S, DEFAULT_MAX_SEALED_BLOB_BYTES as a, type ShareErrorCode as b, type ShareErrorInfo as c, type ShareFetchOptions as d, type ShareInspection as e, type ShareMetadata as f, type ShareNodeTrust as g, type SharePolicyChallenge as h, type SharePolicyDelegationLink as i, type SharePolicySession as j, type SharePresentationMaterial as k, type ShareReceiveAuthorizationRequired as l, ShareReceiveError as m, type ShareReceiveOutcome as n, type ShareReceiveResult as o, ShareRecipientClient as p, type ShareRecipientClientOptions as q, createAddressedAuthorization as r, inspectShare as s, receiveShare as t, toShareErrorInfo as u };
255
+ export { DEFAULT_MAX_CONTENT_BLOB_BYTES as D, SHARE_RESULT_VERSION as S, DEFAULT_MAX_SEALED_BLOB_BYTES as a, type ShareErrorCode as b, type ShareErrorInfo as c, type ShareFetchOptions as d, type ShareInspection as e, type ShareMetadata as f, type ShareNodeTrust as g, type SharePolicyAuthority as h, type SharePolicyChallenge as i, type SharePolicyDelegationLink as j, type SharePolicyEvidence as k, type SharePolicySession as l, type SharePresentationMaterial as m, type ShareReceiveAuthorizationRequired as n, ShareReceiveError as o, type ShareReceiveOutcome as p, type ShareReceiveResult as q, ShareRecipientClient as r, type ShareRecipientClientOptions as s, createAddressedAuthorization as t, inspectShare as u, receiveShare as v, toShareErrorInfo as w };
@@ -1,5 +1,5 @@
1
- import { ShareEnvelopeV3, ShareEnvelopeV2 } from '@tinycloud/share-envelope';
2
- import { a as ShareAuthorizationMethod, f as ShareAuthorizedContent, c as ShareAuthorizationAdapter } from './authorization-CmP1skO8.cjs';
1
+ import { ShareEnvelopeV2, ShareEnvelopeV3 } from '@tinycloud/share-envelope';
2
+ import { c as ShareAuthorizationAdapter, f as ShareAuthorizedContent, a as ShareAuthorizationMethod } from './authorization-CmP1skO8.cjs';
3
3
 
4
4
  type ShareErrorCode = "invalid-link" | "fetch-failed" | "max-bytes-exceeded" | "cid-mismatch" | "decrypt-failed" | "envelope-invalid" | "origin-mismatch" | "signature-invalid" | "capability-invalid" | "expired" | "unsupported-target" | "authorization-denied" | "content-integrity-failed";
5
5
  declare const SHARE_RESULT_VERSION: 1;
@@ -42,7 +42,19 @@ interface ShareFetchOptions {
42
42
  readonly now?: () => number;
43
43
  readonly maxSealedBlobBytes?: number;
44
44
  readonly maxContentBlobBytes?: number;
45
- readonly onResolvedAddressedEnvelope?: (envelope: ShareEnvelopeV3, cid: string) => void;
45
+ readonly onResolvedAddressedEnvelope?: (envelope: ShareEnvelopeV2 | ShareEnvelopeV3, cid: string) => void;
46
+ /** Legacy V2 registry transport; used only for existing compact V2 links. */
47
+ readonly registryBaseUrl?: string;
48
+ readonly fetchBlob?: (input: {
49
+ readonly origin: string;
50
+ readonly cid: string;
51
+ readonly signal?: AbortSignal;
52
+ }) => Promise<Uint8Array>;
53
+ readonly trustedSignerDid?: string;
54
+ readonly trustedPolicyAuthority?: SharePolicyAuthority;
55
+ readonly authorization?: ShareAuthorizationAdapter<ShareAuthorizedContent>;
56
+ readonly authorizationResumeToken?: string;
57
+ readonly authorizationProof?: unknown;
46
58
  }
47
59
  interface ShareMetadata {
48
60
  readonly protocol: "tinycloud-share";
@@ -77,8 +89,37 @@ interface ShareInspection {
77
89
  readonly link: {
78
90
  readonly origin: string;
79
91
  readonly cid: string;
80
- readonly kind: "policy";
92
+ readonly kind: "policy" | "compact";
93
+ };
94
+ }
95
+ interface SharePolicyEvidence {
96
+ readonly policyCid: string;
97
+ readonly signerDid: string;
98
+ readonly registrationCid: string;
99
+ readonly shareId: string;
100
+ readonly recipientMatcher: unknown;
101
+ readonly target: {
102
+ readonly origin: string;
103
+ readonly nodeAudience: string;
104
+ readonly spaceId: string;
81
105
  };
106
+ readonly resource: {
107
+ readonly kind: "exact" | "prefix";
108
+ readonly path: string;
109
+ };
110
+ readonly actions: readonly string[];
111
+ readonly contentSource: unknown;
112
+ readonly contentSourceDigest: string;
113
+ readonly delegationCid: string;
114
+ readonly authorityMaterialHandle: string;
115
+ readonly authorityMaterialDigest: string;
116
+ readonly expiresAt: string;
117
+ }
118
+ interface SharePolicyAuthority {
119
+ resolve(input: {
120
+ readonly policyCid: string;
121
+ readonly envelope: ShareEnvelopeV2;
122
+ }): Promise<SharePolicyEvidence | undefined>;
82
123
  }
83
124
  interface ShareReceiveAuthorizationRequired {
84
125
  readonly state: "authorization-required";
@@ -211,4 +252,4 @@ declare function createAddressedAuthorization(input: Omit<ShareRecipientClientOp
211
252
  }) => Promise<SharePresentationMaterial>;
212
253
  }): ShareAuthorizationAdapter<ShareAuthorizedContent>;
213
254
 
214
- export { DEFAULT_MAX_CONTENT_BLOB_BYTES as D, SHARE_RESULT_VERSION as S, DEFAULT_MAX_SEALED_BLOB_BYTES as a, type ShareErrorCode as b, type ShareErrorInfo as c, type ShareFetchOptions as d, type ShareInspection as e, type ShareMetadata as f, type ShareNodeTrust as g, type SharePolicyChallenge as h, type SharePolicyDelegationLink as i, type SharePolicySession as j, type SharePresentationMaterial as k, type ShareReceiveAuthorizationRequired as l, ShareReceiveError as m, type ShareReceiveOutcome as n, type ShareReceiveResult as o, ShareRecipientClient as p, type ShareRecipientClientOptions as q, createAddressedAuthorization as r, inspectShare as s, receiveShare as t, toShareErrorInfo as u };
255
+ export { DEFAULT_MAX_CONTENT_BLOB_BYTES as D, SHARE_RESULT_VERSION as S, DEFAULT_MAX_SEALED_BLOB_BYTES as a, type ShareErrorCode as b, type ShareErrorInfo as c, type ShareFetchOptions as d, type ShareInspection as e, type ShareMetadata as f, type ShareNodeTrust as g, type SharePolicyAuthority as h, type SharePolicyChallenge as i, type SharePolicyDelegationLink as j, type SharePolicyEvidence as k, type SharePolicySession as l, type SharePresentationMaterial as m, type ShareReceiveAuthorizationRequired as n, ShareReceiveError as o, type ShareReceiveOutcome as p, type ShareReceiveResult as q, ShareRecipientClient as r, type ShareRecipientClientOptions as s, createAddressedAuthorization as t, inspectShare as u, receiveShare as v, toShareErrorInfo as w };
@@ -5256,6 +5256,23 @@ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) =>
5256
5256
  const network = separator < 0 ? "" : rest.slice(separator + 1);
5257
5257
  return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
5258
5258
  }, { message: "expected canonical TinyCloud encryption network" });
5259
+ var unifiedEncryptionResourceSchema = external_exports.string().refine((value) => {
5260
+ const prefix = "urn:tinycloud:encryption:";
5261
+ if (!value.startsWith(prefix)) return false;
5262
+ const match = /^(did:.+):([^/:?#\s]+)(?:\/(.+))?$/.exec(value.slice(prefix.length));
5263
+ if (match === null) return false;
5264
+ const path = match[3];
5265
+ return path === void 0 || path.split("/").every(
5266
+ (segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment)
5267
+ );
5268
+ }, { message: "expected a canonical TinyCloud encryption resource" });
5269
+ var unifiedEncryptionKeyPathSchema = external_exports.string().regex(/^share\/[^/]+$/).refine(
5270
+ (value) => {
5271
+ const segment = value.slice("share/".length);
5272
+ return segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment);
5273
+ },
5274
+ { message: "expected an exact share key path" }
5275
+ );
5259
5276
  var unifiedKvCapabilitySchema = external_exports.object({
5260
5277
  kind: external_exports.literal("kv"),
5261
5278
  resource: unifiedResourceSchema,
@@ -5269,7 +5286,7 @@ var unifiedKvCapabilitySchema = external_exports.object({
5269
5286
  }).strict();
5270
5287
  var unifiedEncryptionCapabilitySchema = external_exports.object({
5271
5288
  kind: external_exports.literal("encryption"),
5272
- resource: unifiedEncryptionNetworkSchema,
5289
+ resource: unifiedEncryptionResourceSchema,
5273
5290
  action: external_exports.literal("tinycloud.encryption/decrypt")
5274
5291
  }).strict();
5275
5292
  var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
@@ -5278,6 +5295,7 @@ var unifiedContentSourceSchema = external_exports.object({
5278
5295
  kvResource: unifiedResourceSchema,
5279
5296
  selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5280
5297
  encryptionNetwork: unifiedEncryptionNetworkSchema,
5298
+ encryptionKeyPath: unifiedEncryptionKeyPathSchema.optional(),
5281
5299
  encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5282
5300
  keyVersion: external_exports.number().int().positive(),
5283
5301
  mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
@@ -5395,22 +5413,71 @@ function validateV3Invariants(value, ctx) {
5395
5413
  if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5396
5414
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
5397
5415
  }
5398
- 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) {
5416
+ const encryptionKeyPath = value.contentSource.encryptionKeyPath;
5417
+ if (encryptionKeyPath !== void 0 && encryptionKeyPath !== `share/${value.contentSource.shareId}`) {
5418
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource", "encryptionKeyPath"], message: "share key path must name this share" });
5419
+ }
5420
+ 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) {
5399
5421
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
5400
5422
  }
5401
5423
  if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
5402
5424
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
5403
5425
  }
5404
5426
  const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
5405
- const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === value.encryptionNetwork && capability.action === "tinycloud.encryption/decrypt");
5427
+ const expectedEncryptionResource = encryptionKeyPath === void 0 ? value.encryptionNetwork : `${value.encryptionNetwork}/${encryptionKeyPath}`;
5428
+ const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === expectedEncryptionResource && capability.action === "tinycloud.encryption/decrypt");
5406
5429
  if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
5407
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
5430
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain the exact decrypt resource" });
5408
5431
  }
5409
5432
  }
5410
5433
  var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
5411
5434
  var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
5412
5435
  var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
5436
+ var SEALED_BLOB_VERSION = 1;
5413
5437
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
5438
+ var KEY_LENGTH = 32;
5439
+ var NONCE_LENGTH = 12;
5440
+ var TAG_LENGTH = 16;
5441
+ var HEADER_LENGTH = 1;
5442
+ function assertKey(key32) {
5443
+ if (key32.length !== KEY_LENGTH) {
5444
+ throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
5445
+ }
5446
+ }
5447
+ async function importAesKey(key32, usage) {
5448
+ assertKey(key32);
5449
+ return globalThis.crypto.subtle.importKey(
5450
+ "raw",
5451
+ key32,
5452
+ "AES-GCM",
5453
+ false,
5454
+ [usage]
5455
+ );
5456
+ }
5457
+ async function open(blob, key32) {
5458
+ if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
5459
+ throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
5460
+ }
5461
+ if (blob[0] !== SEALED_BLOB_VERSION) {
5462
+ throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
5463
+ }
5464
+ const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
5465
+ const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
5466
+ return decryptEnvelope(nonce, ciphertext, key32);
5467
+ }
5468
+ async function decryptEnvelope(nonce, ciphertext, key32) {
5469
+ if (nonce.length !== NONCE_LENGTH) {
5470
+ throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
5471
+ }
5472
+ const key = await importAesKey(key32, "decrypt");
5473
+ return new Uint8Array(
5474
+ await globalThis.crypto.subtle.decrypt(
5475
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5476
+ key,
5477
+ ciphertext
5478
+ )
5479
+ );
5480
+ }
5414
5481
  function isPlainRecord(value) {
5415
5482
  const proto = Object.getPrototypeOf(value);
5416
5483
  return proto === Object.prototype || proto === null;
@@ -5541,6 +5608,7 @@ function equal(left, right) {
5541
5608
  return left.length === right.length && left.every((byte, index) => byte === right[index]);
5542
5609
  }
5543
5610
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5611
+ var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
5544
5612
  var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
5545
5613
  var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
5546
5614
  var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
@@ -5548,6 +5616,14 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
5548
5616
  var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
5549
5617
  var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
5550
5618
  var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
5619
+ function signingBytesV2(unsigned) {
5620
+ const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
5621
+ const body = utf8Bytes(canonicalize(unsigned));
5622
+ const bytes = new Uint8Array(domain.length + body.length);
5623
+ bytes.set(domain);
5624
+ bytes.set(body, domain.length);
5625
+ return bytes;
5626
+ }
5551
5627
  function signingBytesV3(unsigned) {
5552
5628
  const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
5553
5629
  const body = utf8Bytes(canonicalize(unsigned));
@@ -5556,6 +5632,17 @@ function signingBytesV3(unsigned) {
5556
5632
  bytes.set(body, domain.length);
5557
5633
  return bytes;
5558
5634
  }
5635
+ function verifyEnvelopeV2SignatureOnly(envelope) {
5636
+ const parsed = shareEnvelopeV2Schema.parse(envelope);
5637
+ const { signature, ...unsigned } = parsed;
5638
+ return import_ed255193.ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5639
+ }
5640
+ async function verifyEnvelopeV2(envelope, options) {
5641
+ const parsed = shareEnvelopeV2Schema.parse(envelope);
5642
+ if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
5643
+ if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
5644
+ return true;
5645
+ }
5559
5646
  function verifyEnvelopeV3SignatureOnly(envelope) {
5560
5647
  const parsed = shareEnvelopeV3Schema.parse(envelope);
5561
5648
  const { signature, ...unsigned } = parsed;
@@ -5642,6 +5729,52 @@ function base32Lower(bytes) {
5642
5729
  if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
5643
5730
  return output;
5644
5731
  }
5732
+ var KEY_LENGTH2 = 32;
5733
+ function assertCanonicalCid(cidString) {
5734
+ const cid = CID.parse(cidString);
5735
+ if (cid.version !== 1 || cid.code !== code || cid.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
5736
+ cid.toString() !== cidString) {
5737
+ throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
5738
+ }
5739
+ }
5740
+ function parseShareUrl(url, options = {}) {
5741
+ const parsed = new URL(url);
5742
+ if (parsed.protocol !== "https:") {
5743
+ throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
5744
+ }
5745
+ if (parsed.username !== "" || parsed.password !== "") {
5746
+ throw new TypeError("share URL must not carry userinfo");
5747
+ }
5748
+ if (parsed.search !== "") {
5749
+ throw new TypeError("share URL must not have a query string");
5750
+ }
5751
+ if (options.expectedOrigin !== void 0) {
5752
+ if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
5753
+ throw new TypeError(
5754
+ `expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
5755
+ );
5756
+ }
5757
+ if (parsed.origin !== options.expectedOrigin) {
5758
+ throw new TypeError(
5759
+ `share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
5760
+ );
5761
+ }
5762
+ }
5763
+ const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
5764
+ if (!match || match[1] === void 0) {
5765
+ throw new TypeError(`not a share URL path: ${parsed.pathname}`);
5766
+ }
5767
+ const ciphertextCid = match[1];
5768
+ assertCanonicalCid(ciphertextCid);
5769
+ if (!parsed.hash.startsWith("#k=")) {
5770
+ throw new TypeError("share URL is missing the #k= key fragment");
5771
+ }
5772
+ const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
5773
+ if (key32.length !== KEY_LENGTH2) {
5774
+ throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
5775
+ }
5776
+ return { ciphertextCid, key32 };
5777
+ }
5645
5778
 
5646
5779
  // src/artifact-core.ts
5647
5780
  var ARTIFACT_ROOT = "artifacts";
@@ -5738,7 +5871,7 @@ var ShareReceiveError = class extends Error {
5738
5871
  }
5739
5872
  };
5740
5873
  function metadataFor(envelope, origin) {
5741
- const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
5874
+ const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
5742
5875
  return {
5743
5876
  protocol: "tinycloud-share",
5744
5877
  version: 1,
@@ -5753,7 +5886,7 @@ function metadataFor(envelope, origin) {
5753
5886
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
5754
5887
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
5755
5888
  },
5756
- ...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
5889
+ ...envelope.version === 3 && envelope.artifactPin !== void 0 ? { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } } : {}
5757
5890
  };
5758
5891
  }
5759
5892
  function receiveErrorFor(error) {
@@ -5851,10 +5984,123 @@ async function resolvePolicyShare(link, options) {
5851
5984
  options.onResolvedAddressedEnvelope?.(envelope, cid);
5852
5985
  return { envelope, origin: url.origin, cid };
5853
5986
  }
5987
+ async function resolveLegacyV2(link, options) {
5988
+ let parsed;
5989
+ try {
5990
+ parsed = parseShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5991
+ } catch {
5992
+ throw new ShareReceiveError("invalid-link", "legacy share link format is invalid");
5993
+ }
5994
+ try {
5995
+ options.signal?.throwIfAborted();
5996
+ const origin = new URL(link).origin;
5997
+ const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
5998
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope limit is invalid");
5999
+ let sealed;
6000
+ if (options.fetchBlob !== void 0) {
6001
+ try {
6002
+ sealed = await options.fetchBlob({ origin, cid: parsed.ciphertextCid, ...options.signal === void 0 ? {} : { signal: options.signal } });
6003
+ } catch {
6004
+ throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
6005
+ }
6006
+ } else {
6007
+ if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry is required");
6008
+ const response = await (options.fetch ?? globalThis.fetch)(`${options.registryBaseUrl.replace(/\/+$/, "")}/ipfs/${parsed.ciphertextCid}?format=raw`, {
6009
+ headers: { accept: "application/vnd.ipld.raw" },
6010
+ redirect: "error",
6011
+ ...options.signal === void 0 ? {} : { signal: options.signal }
6012
+ });
6013
+ if (!response.ok) throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
6014
+ if (Number(response.headers.get("content-length")) > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6015
+ const reader = response.body?.getReader();
6016
+ if (reader === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry returned no body");
6017
+ const chunks = [];
6018
+ let length2 = 0;
6019
+ for (; ; ) {
6020
+ const part = await reader.read();
6021
+ if (part.done) break;
6022
+ length2 += part.value.length;
6023
+ if (length2 > limit) {
6024
+ await reader.cancel();
6025
+ throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6026
+ }
6027
+ chunks.push(part.value);
6028
+ }
6029
+ sealed = new Uint8Array(length2);
6030
+ let offset = 0;
6031
+ for (const chunk of chunks) {
6032
+ sealed.set(chunk, offset);
6033
+ offset += chunk.length;
6034
+ }
6035
+ }
6036
+ options.signal?.throwIfAborted();
6037
+ if (!(sealed instanceof Uint8Array) || sealed.length > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6038
+ if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "legacy envelope CID does not match");
6039
+ let plaintext;
6040
+ try {
6041
+ plaintext = await open(sealed, parsed.key32);
6042
+ } catch {
6043
+ throw new ShareReceiveError("decrypt-failed", "legacy envelope could not be opened");
6044
+ }
6045
+ let envelope;
6046
+ try {
6047
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
6048
+ const value = JSON.parse(encoded);
6049
+ if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
6050
+ envelope = shareEnvelopeV2Schema.parse(value);
6051
+ } catch {
6052
+ throw new ShareReceiveError("envelope-invalid", "legacy envelope is invalid");
6053
+ }
6054
+ return { envelope, origin, cid: parsed.ciphertextCid };
6055
+ } finally {
6056
+ parsed.key32.fill(0);
6057
+ }
6058
+ }
6059
+ function evidenceMatchesV2(envelope, evidence) {
6060
+ const actions = new Set(evidence.actions.map((action) => action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : action));
6061
+ 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}/`);
6062
+ 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);
6063
+ }
6064
+ async function verifyLegacyV2(envelope, options) {
6065
+ try {
6066
+ if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
6067
+ } catch {
6068
+ throw new ShareReceiveError("signature-invalid", "legacy envelope signature is invalid");
6069
+ }
6070
+ const expiry = Date.parse(envelope.expiry);
6071
+ if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "legacy share expiry is invalid");
6072
+ if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "legacy share has expired", { expiresAt: envelope.expiry });
6073
+ if (envelope.authorizationTarget.kind === "policy") {
6074
+ if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "a legacy policy authority is required");
6075
+ let evidence;
6076
+ try {
6077
+ evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
6078
+ } catch {
6079
+ }
6080
+ if (evidence === void 0 || !evidenceMatchesV2(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "legacy policy evidence does not match");
6081
+ if (options.trustedSignerDid !== void 0 && options.trustedSignerDid !== evidence.signerDid) throw new ShareReceiveError("signature-invalid", "legacy signer is not trusted");
6082
+ } 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) {
6083
+ throw new ShareReceiveError("capability-invalid", "legacy recipient authority does not match");
6084
+ }
6085
+ }
5854
6086
  async function inspectShare(link, options = {}) {
6087
+ if (isLegacyV2Link(link)) {
6088
+ const legacy = await resolveLegacyV2(link, options);
6089
+ await verifyLegacyV2(legacy.envelope, options);
6090
+ options.onResolvedAddressedEnvelope?.(legacy.envelope, legacy.cid);
6091
+ return { metadata: metadataFor(legacy.envelope, legacy.origin), link: { origin: legacy.origin, cid: legacy.cid, kind: "compact" } };
6092
+ }
5855
6093
  const resolved = await resolvePolicyShare(link, options);
5856
6094
  return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
5857
6095
  }
6096
+ function isLegacyV2Link(link) {
6097
+ try {
6098
+ const url = new URL(link);
6099
+ return /^\/s\/bafkrei[a-z2-7]{52}$/.test(url.pathname) && url.hash.startsWith("#k=");
6100
+ } catch {
6101
+ return false;
6102
+ }
6103
+ }
5858
6104
 
5859
6105
  // src/recipient.ts
5860
6106
  var import_ed255194 = require("@noble/curves/ed25519");
@@ -6050,8 +6296,8 @@ function parseV3InlineEncryptedEnvelope(bytes, expected) {
6050
6296
  }
6051
6297
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
6052
6298
  const record = value;
6053
- const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
6054
- 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"))) {
6299
+ const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
6300
+ 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"))) {
6055
6301
  throw new Error("encrypted content envelope binding is invalid");
6056
6302
  }
6057
6303
  return record;
@@ -6183,15 +6429,16 @@ var ShareRecipientClient = class {
6183
6429
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
6184
6430
  const receiverPublicKey = toBase64(import_ed255194.x25519.getPublicKey(receiverPrivateKey));
6185
6431
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
6186
- 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 };
6432
+ 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 };
6187
6433
  const bodyHash = hex2((0, import_sha2562.sha256)(new TextEncoder().encode(canonicalize(body))));
6188
6434
  const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
6189
6435
  const now = Math.floor(Date.now() / 1e3);
6436
+ const decryptResource = encrypted.keyPath === void 0 ? encrypted.networkId : `${encrypted.networkId}/${encrypted.keyPath}`;
6190
6437
  const invocation = await signCompactUcanAuthorization({
6191
6438
  issuerDid: this.options.holderDid,
6192
6439
  audienceDid: this.v3NodeAudience,
6193
- attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
6194
- facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
6440
+ attenuation: { [decryptResource]: { "tinycloud.encryption/decrypt": [{}] } },
6441
+ 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 }],
6195
6442
  proofs: [session.cid],
6196
6443
  notBefore: now,
6197
6444
  expiresAt: Math.min(now + 60, session.payload.exp),