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

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.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as SharePolicyChallenge, i as SharePolicyDelegationLink, j as SharePolicySession, k as SharePresentationMaterial, l as ShareReceiveAuthorizationRequired, m as ShareReceiveError, n as ShareReceiveOutcome, o as ShareReceiveResult, p as ShareRecipientClient, q as ShareRecipientClientOptions, r as createAddressedAuthorization, s as inspectShare, t as receiveShare, u as toShareErrorInfo } from './web-recipient-entry-CiUzdSQs.cjs';
1
+ export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as SharePolicyAuthority, i as SharePolicyChallenge, j as SharePolicyDelegationLink, k as SharePolicyEvidence, l as SharePolicySession, m as SharePresentationMaterial, n as ShareReceiveAuthorizationRequired, o as ShareReceiveError, p as ShareReceiveOutcome, q as ShareReceiveResult, r as ShareRecipientClient, s as ShareRecipientClientOptions, t as createAddressedAuthorization, u as inspectShare, v as receiveShare, w as toShareErrorInfo } from './web-recipient-entry-Bn3yRB7g.cjs';
2
2
  export { C as CreateOwnerDelegationParams, h as DEFAULT_SHARE_LIFETIME_MS, D as DelegatedShareKey, M as MAX_CONTENT_BYTES, i as OWNER_SHARE_REGISTRATION_DOMAIN, j as OwnerDelegationPermission, g as OwnerDelegationReceipt, O as OwnerShareAction, k as OwnerShareDecryption, a as OwnerShareMatcher, l as OwnerSharePolicyRegistration, b as OwnerSharePolicyRegistrationReceipt, m as OwnerSharePolicyV2, c as PublishedShare, P as PublishedShareDeliveryMaterial, n as PublishedShareMetadata, R as RegisterOwnerSharePolicyParams, o as SHARE_CONTENT_LIMIT, p as SHARE_PUBLISH_RESULT_VERSION, f as SharePublishError, q as SharePublishErrorCode, e as SharePublishOptions, d as SharePublishTarget, S as SignedDelegation, r as canonicalOwnerSharePolicy, s as computeOwnerShareRegistrationCid, t as createDelegatedShareKey, u as createPolicyEnforcementDelegation, v as redactPublishedShare, w as restoreDelegatedShareKey, x as validateOwnerSharePolicyRegistration, y as validateOwnerSharePolicyRegistrationBytes } from './owner-policy-aPmXIR3e.cjs';
3
3
  import { ShareEnvelopeV2, ArtifactPin, UnsignedShareEnvelopeV3, ShareEnvelopeV3 } from '@tinycloud/share-envelope';
4
4
  export { CheckBearerDelegationOptions, DelegationCheckResult, canonicalize, checkBearerDelegation, fromBase64Url, requiredResourceUri, resourceUriCovers, toBase64Url } from '@tinycloud/share-envelope';
5
- import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-BP6nsgRq.cjs';
6
- export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-BP6nsgRq.cjs';
5
+ import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-BcvisG4a.cjs';
6
+ export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-BcvisG4a.cjs';
7
7
  import { S as SenderShareRecord, a as SenderShareRecordStorage } from './history-Bg25JIsi.cjs';
8
8
  export { E as EncryptedSenderShareHistory, b as EncryptedShareHistoryStorage, M as MemoryEncryptedShareHistoryStorage, c as MemorySenderShareKeyStorage, d as MemorySenderShareRecordStorage, e as SenderShareKeyStorage, f as SenderShareRecordProofs, g as SenderShareRevocationScope, h as SenderShareStore, i as historyRecordForPublishedShare } from './history-Bg25JIsi.cjs';
9
9
  import { b as ShareAuthorizationResult, c as ShareAuthorizationAdapter } from './authorization-CmP1skO8.cjs';
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as SharePolicyChallenge, i as SharePolicyDelegationLink, j as SharePolicySession, k as SharePresentationMaterial, l as ShareReceiveAuthorizationRequired, m as ShareReceiveError, n as ShareReceiveOutcome, o as ShareReceiveResult, p as ShareRecipientClient, q as ShareRecipientClientOptions, r as createAddressedAuthorization, s as inspectShare, t as receiveShare, u as toShareErrorInfo } from './web-recipient-entry-DVyTQBl0.js';
1
+ export { D as DEFAULT_MAX_CONTENT_BLOB_BYTES, a as DEFAULT_MAX_SEALED_BLOB_BYTES, S as SHARE_RESULT_VERSION, b as ShareErrorCode, c as ShareErrorInfo, d as ShareFetchOptions, e as ShareInspection, f as ShareMetadata, g as ShareNodeTrust, h as SharePolicyAuthority, i as SharePolicyChallenge, j as SharePolicyDelegationLink, k as SharePolicyEvidence, l as SharePolicySession, m as SharePresentationMaterial, n as ShareReceiveAuthorizationRequired, o as ShareReceiveError, p as ShareReceiveOutcome, q as ShareReceiveResult, r as ShareRecipientClient, s as ShareRecipientClientOptions, t as createAddressedAuthorization, u as inspectShare, v as receiveShare, w as toShareErrorInfo } from './web-recipient-entry-CTFoIH8l.js';
2
2
  export { C as CreateOwnerDelegationParams, h as DEFAULT_SHARE_LIFETIME_MS, D as DelegatedShareKey, M as MAX_CONTENT_BYTES, i as OWNER_SHARE_REGISTRATION_DOMAIN, j as OwnerDelegationPermission, g as OwnerDelegationReceipt, O as OwnerShareAction, k as OwnerShareDecryption, a as OwnerShareMatcher, l as OwnerSharePolicyRegistration, b as OwnerSharePolicyRegistrationReceipt, m as OwnerSharePolicyV2, c as PublishedShare, P as PublishedShareDeliveryMaterial, n as PublishedShareMetadata, R as RegisterOwnerSharePolicyParams, o as SHARE_CONTENT_LIMIT, p as SHARE_PUBLISH_RESULT_VERSION, f as SharePublishError, q as SharePublishErrorCode, e as SharePublishOptions, d as SharePublishTarget, S as SignedDelegation, r as canonicalOwnerSharePolicy, s as computeOwnerShareRegistrationCid, t as createDelegatedShareKey, u as createPolicyEnforcementDelegation, v as redactPublishedShare, w as restoreDelegatedShareKey, x as validateOwnerSharePolicyRegistration, y as validateOwnerSharePolicyRegistrationBytes } from './owner-policy-aPmXIR3e.js';
3
3
  import { ShareEnvelopeV2, ArtifactPin, UnsignedShareEnvelopeV3, ShareEnvelopeV3 } from '@tinycloud/share-envelope';
4
4
  export { CheckBearerDelegationOptions, DelegationCheckResult, canonicalize, checkBearerDelegation, fromBase64Url, requiredResourceUri, resourceUriCovers, toBase64Url } from '@tinycloud/share-envelope';
5
- import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-CpkwV-Au.js';
6
- export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-CpkwV-Au.js';
5
+ import { T as TargetPublishOutcome, c as TargetPublishInput, S as ShareTarget } from './addressed-publish-CwTTn9Jn.js';
6
+ export { d as ADDRESSED_SHARE_TOO_LARGE_MESSAGE, A as AddressedOwnerRootInput, a as AddressedOwnerRootReceipt, e as AddressedPolicyRegistrationInput, f as AddressedPolicyRegistrationReceipt, b as AddressedPublishAuthority, g as AddressedShareBoundInput, h as AddressedShareOverLimitError, i as AddressedSharePublishOptions, j as AddressedShareRequest, k as AddressedShareWrites, P as PreparedAddressedShare, l as TargetPublishAdapter, m as addressedCredentialRequirement, n as addressedShareBodyUpperBound, o as assertAddressedShareFits, p as normalizeShareTarget, q as prepareAddressedShare, r as publishAddressedShare, s as publishTargetShare, t as targetAuthorizationMethod } from './addressed-publish-CwTTn9Jn.js';
7
7
  import { S as SenderShareRecord, a as SenderShareRecordStorage } from './history-kwJxrZgq.js';
8
8
  export { E as EncryptedSenderShareHistory, b as EncryptedShareHistoryStorage, M as MemoryEncryptedShareHistoryStorage, c as MemorySenderShareKeyStorage, d as MemorySenderShareRecordStorage, e as SenderShareKeyStorage, f as SenderShareRecordProofs, g as SenderShareRevocationScope, h as SenderShareStore, i as historyRecordForPublishedShare } from './history-kwJxrZgq.js';
9
9
  import { b as ShareAuthorizationResult, c as ShareAuthorizationAdapter } from './authorization-CmP1skO8.js';
package/dist/index.js CHANGED
@@ -5261,6 +5261,23 @@ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) =>
5261
5261
  const network = separator < 0 ? "" : rest.slice(separator + 1);
5262
5262
  return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
5263
5263
  }, { message: "expected canonical TinyCloud encryption network" });
5264
+ var unifiedEncryptionResourceSchema = external_exports.string().refine((value) => {
5265
+ const prefix = "urn:tinycloud:encryption:";
5266
+ if (!value.startsWith(prefix)) return false;
5267
+ const match = /^(did:.+):([^/:?#\s]+)(?:\/(.+))?$/.exec(value.slice(prefix.length));
5268
+ if (match === null) return false;
5269
+ const path = match[3];
5270
+ return path === void 0 || path.split("/").every(
5271
+ (segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment)
5272
+ );
5273
+ }, { message: "expected a canonical TinyCloud encryption resource" });
5274
+ var unifiedEncryptionKeyPathSchema = external_exports.string().regex(/^share\/[^/]+$/).refine(
5275
+ (value) => {
5276
+ const segment = value.slice("share/".length);
5277
+ return segment !== "." && segment !== ".." && !/[\\:%?#\s]/.test(segment);
5278
+ },
5279
+ { message: "expected an exact share key path" }
5280
+ );
5264
5281
  var unifiedKvCapabilitySchema = external_exports.object({
5265
5282
  kind: external_exports.literal("kv"),
5266
5283
  resource: unifiedResourceSchema,
@@ -5274,7 +5291,7 @@ var unifiedKvCapabilitySchema = external_exports.object({
5274
5291
  }).strict();
5275
5292
  var unifiedEncryptionCapabilitySchema = external_exports.object({
5276
5293
  kind: external_exports.literal("encryption"),
5277
- resource: unifiedEncryptionNetworkSchema,
5294
+ resource: unifiedEncryptionResourceSchema,
5278
5295
  action: external_exports.literal("tinycloud.encryption/decrypt")
5279
5296
  }).strict();
5280
5297
  var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
@@ -5283,6 +5300,7 @@ var unifiedContentSourceSchema = external_exports.object({
5283
5300
  kvResource: unifiedResourceSchema,
5284
5301
  selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5285
5302
  encryptionNetwork: unifiedEncryptionNetworkSchema,
5303
+ encryptionKeyPath: unifiedEncryptionKeyPathSchema.optional(),
5286
5304
  encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5287
5305
  keyVersion: external_exports.number().int().positive(),
5288
5306
  mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
@@ -5400,16 +5418,21 @@ function validateV3Invariants(value, ctx) {
5400
5418
  if (value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.attestedEnforcerBinding.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5401
5419
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
5402
5420
  }
5403
- 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) {
5421
+ const encryptionKeyPath = value.contentSource.encryptionKeyPath;
5422
+ if (encryptionKeyPath !== void 0 && encryptionKeyPath !== `share/${value.contentSource.shareId}`) {
5423
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource", "encryptionKeyPath"], message: "share key path must name this share" });
5424
+ }
5425
+ 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) {
5404
5426
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
5405
5427
  }
5406
5428
  if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
5407
5429
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
5408
5430
  }
5409
5431
  const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
5410
- const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === value.encryptionNetwork && capability.action === "tinycloud.encryption/decrypt");
5432
+ const expectedEncryptionResource = encryptionKeyPath === void 0 ? value.encryptionNetwork : `${value.encryptionNetwork}/${encryptionKeyPath}`;
5433
+ const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === expectedEncryptionResource && capability.action === "tinycloud.encryption/decrypt");
5411
5434
  if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
5412
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
5435
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain the exact decrypt resource" });
5413
5436
  }
5414
5437
  }
5415
5438
  var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
@@ -5597,6 +5620,7 @@ var SEALED_BLOB_VERSION = 1;
5597
5620
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
5598
5621
  var KEY_LENGTH = 32;
5599
5622
  var NONCE_LENGTH = 12;
5623
+ var TAG_LENGTH = 16;
5600
5624
  var HEADER_LENGTH = 1;
5601
5625
  function assertKey(key32) {
5602
5626
  if (key32.length !== KEY_LENGTH) {
@@ -5624,6 +5648,17 @@ async function seal(plaintextBytes, key32) {
5624
5648
  blob.set(ciphertext, HEADER_LENGTH + nonce.length);
5625
5649
  return { blob, cid: await computeCid(blob) };
5626
5650
  }
5651
+ async function open(blob, key32) {
5652
+ if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
5653
+ throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
5654
+ }
5655
+ if (blob[0] !== SEALED_BLOB_VERSION) {
5656
+ throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
5657
+ }
5658
+ const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
5659
+ const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
5660
+ return decryptEnvelope(nonce, ciphertext, key32);
5661
+ }
5627
5662
  async function encryptEnvelope(plaintextBytes, key32) {
5628
5663
  const key = await importAesKey(key32, "encrypt");
5629
5664
  const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
@@ -5636,6 +5671,19 @@ async function encryptEnvelope(plaintextBytes, key32) {
5636
5671
  );
5637
5672
  return { nonce, ciphertext };
5638
5673
  }
5674
+ async function decryptEnvelope(nonce, ciphertext, key32) {
5675
+ if (nonce.length !== NONCE_LENGTH) {
5676
+ throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
5677
+ }
5678
+ const key = await importAesKey(key32, "decrypt");
5679
+ return new Uint8Array(
5680
+ await globalThis.crypto.subtle.decrypt(
5681
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5682
+ key,
5683
+ ciphertext
5684
+ )
5685
+ );
5686
+ }
5639
5687
  function isPlainRecord(value) {
5640
5688
  const proto = Object.getPrototypeOf(value);
5641
5689
  return proto === Object.prototype || proto === null;
@@ -5781,6 +5829,7 @@ function equal(left, right) {
5781
5829
  return left.length === right.length && left.every((byte, index) => byte === right[index]);
5782
5830
  }
5783
5831
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5832
+ var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
5784
5833
  var ENVELOPE_V3_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v3\0";
5785
5834
  var POLICY_V1_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v1\0";
5786
5835
  var POLICY_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
@@ -5788,6 +5837,14 @@ var CONTENT_SOURCE_V1_DOMAIN = "xyz.tinycloud.policy/ContentSource/v1\0";
5788
5837
  var POLICY_CAPABILITY_V1_DOMAIN = "xyz.tinycloud.policy/PolicyCapability/v1\0";
5789
5838
  var NATIVE_PROJECTION_V1_DOMAIN = "xyz.tinycloud.policy/NativeProjection/v1\0";
5790
5839
  var ATTESTED_ENFORCER_V2_DOMAIN = "xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0";
5840
+ function signingBytesV2(unsigned) {
5841
+ const domain = utf8Bytes(ENVELOPE_V2_SIGNATURE_DOMAIN);
5842
+ const body = utf8Bytes(canonicalize(unsigned));
5843
+ const bytes3 = new Uint8Array(domain.length + body.length);
5844
+ bytes3.set(domain);
5845
+ bytes3.set(body, domain.length);
5846
+ return bytes3;
5847
+ }
5791
5848
  function signingBytesV3(unsigned) {
5792
5849
  const domain = utf8Bytes(ENVELOPE_V3_SIGNATURE_DOMAIN);
5793
5850
  const body = utf8Bytes(canonicalize(unsigned));
@@ -5796,6 +5853,17 @@ function signingBytesV3(unsigned) {
5796
5853
  bytes3.set(body, domain.length);
5797
5854
  return bytes3;
5798
5855
  }
5856
+ function verifyEnvelopeV2SignatureOnly(envelope) {
5857
+ const parsed = shareEnvelopeV2Schema.parse(envelope);
5858
+ const { signature, ...unsigned } = parsed;
5859
+ return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5860
+ }
5861
+ async function verifyEnvelopeV2(envelope, options) {
5862
+ const parsed = shareEnvelopeV2Schema.parse(envelope);
5863
+ if (parsed.signature.signerDid !== options.expectedSignerDid || !verifyEnvelopeV2SignatureOnly(parsed)) return false;
5864
+ if (parsed.authorizationTarget.kind === "policy") return await computeCid(fromBase64Url(parsed.authorizationTarget.policyBytes)) === parsed.authorizationTarget.policyCid;
5865
+ return true;
5866
+ }
5799
5867
  function verifyEnvelopeV3SignatureOnly(envelope) {
5800
5868
  const parsed = shareEnvelopeV3Schema.parse(envelope);
5801
5869
  const { signature, ...unsigned } = parsed;
@@ -5882,6 +5950,52 @@ function base32Lower(bytes3) {
5882
5950
  if (bits > 0) output += alphabet[buffer << 5 - bits & 31];
5883
5951
  return output;
5884
5952
  }
5953
+ var KEY_LENGTH2 = 32;
5954
+ function assertCanonicalCid(cidString) {
5955
+ const cid2 = CID.parse(cidString);
5956
+ if (cid2.version !== 1 || cid2.code !== code || cid2.multihash.code !== 18 || // sha2-256 (0x12) only, at the link layer too
5957
+ cid2.toString() !== cidString) {
5958
+ throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
5959
+ }
5960
+ }
5961
+ function parseShareUrl(url, options = {}) {
5962
+ const parsed = new URL(url);
5963
+ if (parsed.protocol !== "https:") {
5964
+ throw new TypeError(`share URL must be https, got ${parsed.protocol}`);
5965
+ }
5966
+ if (parsed.username !== "" || parsed.password !== "") {
5967
+ throw new TypeError("share URL must not carry userinfo");
5968
+ }
5969
+ if (parsed.search !== "") {
5970
+ throw new TypeError("share URL must not have a query string");
5971
+ }
5972
+ if (options.expectedOrigin !== void 0) {
5973
+ if (!isCanonicalHttpsOrigin(options.expectedOrigin)) {
5974
+ throw new TypeError(
5975
+ `expectedOrigin must be a canonical https origin, got ${options.expectedOrigin}`
5976
+ );
5977
+ }
5978
+ if (parsed.origin !== options.expectedOrigin) {
5979
+ throw new TypeError(
5980
+ `share URL origin ${parsed.origin} does not match expected ${options.expectedOrigin}`
5981
+ );
5982
+ }
5983
+ }
5984
+ const match = /^\/s\/([a-z2-7]+)$/.exec(parsed.pathname);
5985
+ if (!match || match[1] === void 0) {
5986
+ throw new TypeError(`not a share URL path: ${parsed.pathname}`);
5987
+ }
5988
+ const ciphertextCid = match[1];
5989
+ assertCanonicalCid(ciphertextCid);
5990
+ if (!parsed.hash.startsWith("#k=")) {
5991
+ throw new TypeError("share URL is missing the #k= key fragment");
5992
+ }
5993
+ const key32 = fromBase64Url(parsed.hash.slice("#k=".length));
5994
+ if (key32.length !== KEY_LENGTH2) {
5995
+ throw new TypeError(`fragment key must be ${KEY_LENGTH2} bytes, got ${key32.length}`);
5996
+ }
5997
+ return { ciphertextCid, key32 };
5998
+ }
5885
5999
 
5886
6000
  // src/artifact-core.ts
5887
6001
  var ARTIFACT_ROOT = "artifacts";
@@ -5983,7 +6097,7 @@ function toShareErrorInfo(error) {
5983
6097
  return { protocol: "tinycloud-share", version: SHARE_RESULT_VERSION, error: { code: "fetch-failed" } };
5984
6098
  }
5985
6099
  function metadataFor(envelope, origin) {
5986
- const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : "emailDomain";
6100
+ const kind = envelope.recipientMatcher.kind === "recipientDid" ? "recipientDid" : envelope.recipientMatcher.kind === "exactEmail" ? "email" : envelope.recipientMatcher.kind === "emailDomain" ? "emailDomain" : "bearer";
5987
6101
  return {
5988
6102
  protocol: "tinycloud-share",
5989
6103
  version: 1,
@@ -5998,7 +6112,7 @@ function metadataFor(envelope, origin) {
5998
6112
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
5999
6113
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
6000
6114
  },
6001
- ...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
6115
+ ...envelope.version === 3 && envelope.artifactPin !== void 0 ? { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } } : {}
6002
6116
  };
6003
6117
  }
6004
6118
  function receiveErrorFor(error) {
@@ -6096,14 +6210,165 @@ async function resolvePolicyShare(link, options) {
6096
6210
  options.onResolvedAddressedEnvelope?.(envelope, cid2);
6097
6211
  return { envelope, origin: url.origin, cid: cid2 };
6098
6212
  }
6213
+ async function resolveLegacyV2(link, options) {
6214
+ let parsed;
6215
+ try {
6216
+ parsed = parseShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
6217
+ } catch {
6218
+ throw new ShareReceiveError("invalid-link", "legacy share link format is invalid");
6219
+ }
6220
+ try {
6221
+ options.signal?.throwIfAborted();
6222
+ const origin = new URL(link).origin;
6223
+ const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
6224
+ if (!Number.isSafeInteger(limit) || limit <= 0) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope limit is invalid");
6225
+ let sealed;
6226
+ if (options.fetchBlob !== void 0) {
6227
+ try {
6228
+ sealed = await options.fetchBlob({ origin, cid: parsed.ciphertextCid, ...options.signal === void 0 ? {} : { signal: options.signal } });
6229
+ } catch {
6230
+ throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
6231
+ }
6232
+ } else {
6233
+ if (options.registryBaseUrl === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry is required");
6234
+ const response = await (options.fetch ?? globalThis.fetch)(`${options.registryBaseUrl.replace(/\/+$/, "")}/ipfs/${parsed.ciphertextCid}?format=raw`, {
6235
+ headers: { accept: "application/vnd.ipld.raw" },
6236
+ redirect: "error",
6237
+ ...options.signal === void 0 ? {} : { signal: options.signal }
6238
+ });
6239
+ if (!response.ok) throw new ShareReceiveError("fetch-failed", "legacy registry unavailable");
6240
+ if (Number(response.headers.get("content-length")) > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6241
+ const reader = response.body?.getReader();
6242
+ if (reader === void 0) throw new ShareReceiveError("fetch-failed", "legacy registry returned no body");
6243
+ const chunks = [];
6244
+ let length3 = 0;
6245
+ for (; ; ) {
6246
+ const part = await reader.read();
6247
+ if (part.done) break;
6248
+ length3 += part.value.length;
6249
+ if (length3 > limit) {
6250
+ await reader.cancel();
6251
+ throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6252
+ }
6253
+ chunks.push(part.value);
6254
+ }
6255
+ sealed = new Uint8Array(length3);
6256
+ let offset = 0;
6257
+ for (const chunk of chunks) {
6258
+ sealed.set(chunk, offset);
6259
+ offset += chunk.length;
6260
+ }
6261
+ }
6262
+ options.signal?.throwIfAborted();
6263
+ if (!(sealed instanceof Uint8Array) || sealed.length > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed envelope exceeds the size limit");
6264
+ if (await computeCid(sealed) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "legacy envelope CID does not match");
6265
+ let plaintext;
6266
+ try {
6267
+ plaintext = await open(sealed, parsed.key32);
6268
+ } catch {
6269
+ throw new ShareReceiveError("decrypt-failed", "legacy envelope could not be opened");
6270
+ }
6271
+ let envelope;
6272
+ try {
6273
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
6274
+ const value = JSON.parse(encoded);
6275
+ if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
6276
+ envelope = shareEnvelopeV2Schema.parse(value);
6277
+ } catch {
6278
+ throw new ShareReceiveError("envelope-invalid", "legacy envelope is invalid");
6279
+ }
6280
+ return { envelope, origin, cid: parsed.ciphertextCid };
6281
+ } finally {
6282
+ parsed.key32.fill(0);
6283
+ }
6284
+ }
6285
+ function evidenceMatchesV2(envelope, evidence) {
6286
+ const actions = new Set(evidence.actions.map((action) => action === "read" ? "tinycloud.kv/get" : action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : action));
6287
+ 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}/`);
6288
+ 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);
6289
+ }
6290
+ async function verifyLegacyV2(envelope, options) {
6291
+ try {
6292
+ if (!await verifyEnvelopeV2(envelope, { expectedSignerDid: envelope.signature.signerDid })) throw new Error("signature");
6293
+ } catch {
6294
+ throw new ShareReceiveError("signature-invalid", "legacy envelope signature is invalid");
6295
+ }
6296
+ const expiry = Date.parse(envelope.expiry);
6297
+ if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "legacy share expiry is invalid");
6298
+ if (expiry <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "legacy share has expired", { expiresAt: envelope.expiry });
6299
+ if (envelope.authorizationTarget.kind === "policy") {
6300
+ if (options.trustedPolicyAuthority === void 0) throw new ShareReceiveError("envelope-invalid", "a legacy policy authority is required");
6301
+ let evidence;
6302
+ try {
6303
+ evidence = await options.trustedPolicyAuthority.resolve({ policyCid: envelope.authorizationTarget.policyCid, envelope });
6304
+ } catch {
6305
+ }
6306
+ if (evidence === void 0 || !evidenceMatchesV2(envelope, evidence)) throw new ShareReceiveError("capability-invalid", "legacy policy evidence does not match");
6307
+ if (options.trustedSignerDid !== void 0 && options.trustedSignerDid !== evidence.signerDid) throw new ShareReceiveError("signature-invalid", "legacy signer is not trusted");
6308
+ } 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) {
6309
+ throw new ShareReceiveError("capability-invalid", "legacy recipient authority does not match");
6310
+ }
6311
+ }
6099
6312
  async function inspectShare(link, options = {}) {
6313
+ if (isLegacyV2Link(link)) {
6314
+ const legacy = await resolveLegacyV2(link, options);
6315
+ await verifyLegacyV2(legacy.envelope, options);
6316
+ options.onResolvedAddressedEnvelope?.(legacy.envelope, legacy.cid);
6317
+ return { metadata: metadataFor(legacy.envelope, legacy.origin), link: { origin: legacy.origin, cid: legacy.cid, kind: "compact" } };
6318
+ }
6100
6319
  const resolved = await resolvePolicyShare(link, options);
6101
6320
  return { metadata: metadataFor(resolved.envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "policy" } };
6102
6321
  }
6103
6322
  async function receiveShare(link, options = {}) {
6323
+ if (isLegacyV2Link(link)) return receiveLegacyV2(link, options);
6104
6324
  const resolved = await resolvePolicyShare(link, options);
6105
6325
  return { state: "authorization-required", method: resolved.envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim" };
6106
6326
  }
6327
+ function isLegacyV2Link(link) {
6328
+ try {
6329
+ const url = new URL(link);
6330
+ return /^\/s\/bafkrei[a-z2-7]{52}$/.test(url.pathname) && url.hash.startsWith("#k=");
6331
+ } catch {
6332
+ return false;
6333
+ }
6334
+ }
6335
+ async function receiveLegacyV2(link, options) {
6336
+ const resolved = await resolveLegacyV2(link, options);
6337
+ const { envelope } = resolved;
6338
+ await verifyLegacyV2(envelope, options);
6339
+ options.onResolvedAddressedEnvelope?.(envelope, resolved.cid);
6340
+ const method = envelope.recipientMatcher.kind === "recipientDid" ? "openkey-device" : "email-claim";
6341
+ if (options.authorization === void 0) return { state: "authorization-required", method };
6342
+ const result = options.authorizationResumeToken === void 0 ? await options.authorization.begin({ envelope, method }) : await options.authorization.resume({
6343
+ envelope,
6344
+ method,
6345
+ resumeToken: options.authorizationResumeToken,
6346
+ ...options.authorizationProof === void 0 ? {} : { proof: options.authorizationProof }
6347
+ });
6348
+ if (result.state === "authorization-required") return result;
6349
+ if (result.state === "denied") throw new ShareReceiveError("authorization-denied", "legacy share authorization was denied");
6350
+ if (result.value === null || typeof result.value !== "object" || !(result.value.bytes instanceof Uint8Array) || result.value.proof === void 0 || options.authorization.verifyResult === void 0) {
6351
+ throw new ShareReceiveError("content-integrity-failed", "legacy content lacks a verifiable proof");
6352
+ }
6353
+ const authorized = result.value;
6354
+ try {
6355
+ if (!await options.authorization.verifyResult({ envelope, value: authorized, proof: authorized.proof })) throw new Error("proof");
6356
+ } catch {
6357
+ throw new ShareReceiveError("content-integrity-failed", "legacy content proof is invalid");
6358
+ }
6359
+ const bytes3 = authorized.bytes.slice();
6360
+ if (bytes3.length > (options.maxContentBlobBytes ?? DEFAULT_MAX_CONTENT_BLOB_BYTES)) throw new ShareReceiveError("max-bytes-exceeded", "legacy content exceeds the size limit");
6361
+ const bodyDigest = toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes3)));
6362
+ if (authorized.bodyDigest !== bodyDigest || authorized.contentSourceDigest !== envelope.contentSourceDigest || authorized.binding.shareId !== envelope.shareId || authorized.binding.delegationCid !== envelope.delegationCid || authorized.binding.authorityMaterialHandle !== envelope.authorityMaterialHandle || authorized.binding.authorityMaterialDigest !== envelope.authorityMaterialDigest || authorized.binding.resource.kind !== envelope.resource.kind || authorized.binding.resource.path !== envelope.resource.path || authorized.binding.action !== void 0 && !envelope.actions.some((action) => action === authorized.binding.action || `tinycloud.kv/${action}` === authorized.binding.action) || envelope.metadata.byteLength !== void 0 && envelope.metadata.byteLength !== bytes3.length) {
6363
+ throw new ShareReceiveError("content-integrity-failed", "legacy content binding does not match");
6364
+ }
6365
+ let text;
6366
+ try {
6367
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes3);
6368
+ } catch {
6369
+ }
6370
+ return { metadata: metadataFor(envelope, resolved.origin), link: { origin: resolved.origin, cid: resolved.cid, kind: "compact" }, bytes: bytes3, ...text === void 0 ? {} : { text } };
6371
+ }
6107
6372
 
6108
6373
  // src/publish.ts
6109
6374
  var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
@@ -6638,7 +6903,10 @@ async function publishTargetShare(input) {
6638
6903
  expiresAt,
6639
6904
  expiryWasExplicit: input.expiryWasExplicit ?? input.expiresAt !== void 0,
6640
6905
  origin: input.origin,
6641
- ...input.notify === void 0 ? {} : { notify: input.notify }
6906
+ ...input.notify === void 0 ? {} : { notify: input.notify },
6907
+ ...input.credentialDescriptor === void 0 ? {} : { credentialDescriptor: input.credentialDescriptor },
6908
+ ...input.issuerDid === void 0 ? {} : { issuerDid: input.issuerDid },
6909
+ ...input.issuerKid === void 0 ? {} : { issuerKid: input.issuerKid }
6642
6910
  });
6643
6911
  }
6644
6912
 
@@ -6726,7 +6994,7 @@ async function createPolicy(options, capabilities) {
6726
6994
  function addressedPlan(options) {
6727
6995
  const capabilities = sortCanonical([
6728
6996
  { kind: "kv", resource: options.contentSource.kvResource, selector: options.resource.kind, actions: [...options.policyActions] },
6729
- { kind: "encryption", resource: options.contentSource.encryptionNetwork, action: "tinycloud.encryption/decrypt" }
6997
+ { kind: "encryption", resource: options.contentSource.encryptionKeyPath === void 0 ? options.contentSource.encryptionNetwork : `${options.contentSource.encryptionNetwork}/${options.contentSource.encryptionKeyPath}`, action: "tinycloud.encryption/decrypt" }
6730
6998
  ]);
6731
6999
  return {
6732
7000
  expiry: rfc3339Seconds(options.expiresAt),
@@ -7056,6 +7324,7 @@ async function addressedShareBodyUpperBound(input) {
7056
7324
  kvResource: `${input.spaceId}/kv/${path}`,
7057
7325
  selector: input.resource.kind,
7058
7326
  encryptionNetwork: input.encryptionNetwork,
7327
+ ...input.encryptionKeyPath === void 0 ? {} : { encryptionKeyPath: input.encryptionKeyPath },
7059
7328
  encryptedSymmetricKeyDigestHex: "f".repeat(64),
7060
7329
  keyVersion: Number.MAX_SAFE_INTEGER,
7061
7330
  mode: "immutable",
@@ -8441,41 +8710,18 @@ function bytes2(value, label) {
8441
8710
  if (decoded.length !== 64 || toBase64Url(decoded) !== value) throw new Error(`${label} is invalid`);
8442
8711
  return decoded;
8443
8712
  }
8444
- var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8445
8713
  function toBase64(bytes3) {
8446
- let out = "";
8447
- for (let i = 0; i < bytes3.length; i += 3) {
8448
- const b0 = bytes3[i];
8449
- const b1 = i + 1 < bytes3.length ? bytes3[i + 1] : 0;
8450
- const b2 = i + 2 < bytes3.length ? bytes3[i + 2] : 0;
8451
- out += BASE64_ALPHABET[b0 >> 2 & 63];
8452
- out += BASE64_ALPHABET[(b0 << 4 | b1 >> 4) & 63];
8453
- out += i + 1 < bytes3.length ? BASE64_ALPHABET[(b1 << 2 | b2 >> 6) & 63] : "=";
8454
- out += i + 2 < bytes3.length ? BASE64_ALPHABET[b2 & 63] : "=";
8455
- }
8456
- return out;
8714
+ return toBase64Url(bytes3).replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(bytes3.length / 3) * 4, "=");
8457
8715
  }
8458
8716
  function fromBase64(value, label) {
8459
8717
  if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
8460
8718
  throw new Error(`${label} is invalid`);
8461
8719
  }
8462
- const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
8463
- const out = new Uint8Array(value.length / 4 * 3 - padding);
8464
- let outIdx = 0;
8465
- for (let i = 0; i < value.length; i += 4) {
8466
- const v0 = BASE64_ALPHABET.indexOf(value[i]);
8467
- const v1 = BASE64_ALPHABET.indexOf(value[i + 1]);
8468
- const v2 = value[i + 2] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 2]);
8469
- const v3 = value[i + 3] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 3]);
8470
- const b0 = v0 << 2 | v1 >> 4;
8471
- const b1 = (v1 & 15) << 4 | v2 >> 2;
8472
- const b2 = (v2 & 3) << 6 | v3;
8473
- if (outIdx < out.length) out[outIdx++] = b0;
8474
- if (outIdx < out.length) out[outIdx++] = b1;
8475
- if (outIdx < out.length) out[outIdx++] = b2;
8476
- }
8477
- if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
8478
- return out;
8720
+ try {
8721
+ return fromBase64Url(value.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"));
8722
+ } catch {
8723
+ throw new Error(`${label} is invalid`);
8724
+ }
8479
8725
  }
8480
8726
  async function digestBytes2(value) {
8481
8727
  return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
@@ -8592,16 +8838,16 @@ function matchesInitialCiphertextDigest(bytes3, expected) {
8592
8838
  function canonicalHashHex(value) {
8593
8839
  return hex3(sha2566(new TextEncoder().encode(canonicalize(value))));
8594
8840
  }
8595
- async function aesGcmDecrypt(key, blob) {
8841
+ async function aesGcmDecrypt(key, blob, aad) {
8596
8842
  if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
8597
8843
  const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
8598
- return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
8844
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12), ...aad && { additionalData: aad } }, cryptoKey, blob.slice(12)));
8599
8845
  }
8600
- async function aesGcmEncrypt(key, plaintext) {
8846
+ async function aesGcmEncrypt(key, plaintext, aad) {
8601
8847
  if (key.length !== 32) throw new Error("encrypted content key is malformed");
8602
8848
  const nonce = crypto.getRandomValues(new Uint8Array(12));
8603
8849
  const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
8604
- const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
8850
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce, ...aad && { additionalData: aad } }, cryptoKey, plaintext));
8605
8851
  const output = new Uint8Array(nonce.length + ciphertext.length);
8606
8852
  output.set(nonce);
8607
8853
  output.set(ciphertext, nonce.length);
@@ -8616,8 +8862,9 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
8616
8862
  }
8617
8863
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
8618
8864
  const record = value;
8619
- const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
8620
- 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"))) {
8865
+ const allowed = ["v", "networkId", "keyPath", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "aad", "metadata"];
8866
+ const source = expected.contentSource;
8867
+ if (Object.keys(record).some((key) => !allowed.includes(key)) || record.v !== (source.encryptionKeyPath === void 0 ? 1 : 2) || record.networkId !== source.encryptionNetwork || record.networkId !== expected.encryptionNetwork || record.keyPath !== source.encryptionKeyPath || record.keyPath !== void 0 && record.keyPath !== `share/${expected.shareId}` || record.alg !== "x25519-aes256gcm/v1" || record.keyVersion !== source.keyVersion || typeof record.encryptedSymmetricKey !== "string" || typeof record.encryptedSymmetricKeyHash !== "string" || hex3(sha2566(fromBase64(record.encryptedSymmetricKey, "v3 encrypted symmetric key"))) !== source.encryptedSymmetricKeyDigestHex || record.encryptedSymmetricKeyHash !== canonicalHashHex(record.encryptedSymmetricKey) || typeof record.ciphertext !== "string" || record.aad !== void 0 && typeof record.aad !== "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"))) {
8621
8868
  throw new Error("encrypted content envelope binding is invalid");
8622
8869
  }
8623
8870
  return record;
@@ -8738,7 +8985,7 @@ var ShareRecipientClient = class {
8738
8985
  this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
8739
8986
  return this.session;
8740
8987
  }
8741
- /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
8988
+ /** Decrypt unscoped v1 or path-bound v2 content, independently checking canonical and registered raw-key digests. */
8742
8989
  async decryptV3Content(bytes3) {
8743
8990
  const envelope = this.options.envelope;
8744
8991
  const signer = this.nativeSigner ?? this.signer;
@@ -8746,18 +8993,22 @@ var ShareRecipientClient = class {
8746
8993
  assertArtifactBinding(artifactBindingView(envelope));
8747
8994
  if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
8748
8995
  const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
8996
+ const ciphertext = fromBase64(encrypted.ciphertext, "v3 encrypted content");
8997
+ if (ciphertext.length < 28) throw new Error("encrypted content is malformed");
8998
+ const aad = encrypted.aad === void 0 ? void 0 : fromBase64(encrypted.aad, "v3 encrypted content AAD");
8749
8999
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
8750
9000
  const receiverPublicKey = toBase64(x25519.getPublicKey(receiverPrivateKey));
8751
9001
  const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
8752
- 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 };
9002
+ 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 };
8753
9003
  const bodyHash = hex3(sha2566(new TextEncoder().encode(canonicalize(body))));
8754
9004
  const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
8755
9005
  const now = Math.floor(Date.now() / 1e3);
9006
+ const decryptResource = encrypted.keyPath === void 0 ? encrypted.networkId : `${encrypted.networkId}/${encrypted.keyPath}`;
8756
9007
  const invocation = await signCompactUcanAuthorization({
8757
9008
  issuerDid: this.options.holderDid,
8758
9009
  audienceDid: this.v3NodeAudience,
8759
- attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
8760
- facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
9010
+ attenuation: { [decryptResource]: { "tinycloud.encryption/decrypt": [{}] } },
9011
+ 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 }],
8761
9012
  proofs: [session.cid],
8762
9013
  notBefore: now,
8763
9014
  expiresAt: Math.min(now + 60, session.payload.exp),
@@ -8781,7 +9032,7 @@ var ShareRecipientClient = class {
8781
9032
  const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(33));
8782
9033
  shared.fill(0);
8783
9034
  if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
8784
- const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64(encrypted.ciphertext, "v3 encrypted content"));
9035
+ const plaintext = await aesGcmDecrypt(symmetricKey, ciphertext, aad);
8785
9036
  this.v3ContentKey?.fill(0);
8786
9037
  this.v3ContentKey = symmetricKey;
8787
9038
  this.v3ContentEnvelope = encrypted;
@@ -8793,7 +9044,9 @@ var ShareRecipientClient = class {
8793
9044
  /** Re-encrypt edited v3 content with the admitted content key. */
8794
9045
  async encryptV3Content(bytes3, mediaType) {
8795
9046
  if (this.options.envelope.version !== 3 || this.v3ContentKey === void 0 || this.v3ContentEnvelope === void 0) throw new Error("v3 content must be decrypted before it can be saved");
8796
- return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
9047
+ const envelope = this.v3ContentEnvelope;
9048
+ const aad = envelope.aad === void 0 ? void 0 : fromBase64(envelope.aad, "v3 encrypted content AAD");
9049
+ return new TextEncoder().encode(canonicalize({ ...envelope, ciphertext: toBase64(await aesGcmEncrypt(this.v3ContentKey, bytes3, aad)), metadata: { ...envelope.metadata ?? {}, contentType: mediaType } }));
8797
9050
  }
8798
9051
  async resumeWithProof(envelope, resumeToken, proof) {
8799
9052
  void envelope;