@tinycloud/share-sdk 1.0.0-beta.3 → 1.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -152,10 +152,10 @@ var util;
152
152
  return obj[e];
153
153
  });
154
154
  };
155
- util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object3) => {
155
+ util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object4) => {
156
156
  const keys = [];
157
- for (const key in object3) {
158
- if (Object.prototype.hasOwnProperty.call(object3, key)) {
157
+ for (const key in object4) {
158
+ if (Object.prototype.hasOwnProperty.call(object4, key)) {
159
159
  keys.push(key);
160
160
  }
161
161
  }
@@ -5555,7 +5555,74 @@ function checkBearerDelegation(envelope, options = {}) {
5555
5555
  return { ok: true, delegateeDid: audience };
5556
5556
  }
5557
5557
  var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
5558
+ var SEALED_BLOB_VERSION = 1;
5558
5559
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
5560
+ var KEY_LENGTH = 32;
5561
+ var NONCE_LENGTH = 12;
5562
+ var TAG_LENGTH = 16;
5563
+ var HEADER_LENGTH = 1;
5564
+ function assertKey(key32) {
5565
+ if (key32.length !== KEY_LENGTH) {
5566
+ throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
5567
+ }
5568
+ }
5569
+ async function importAesKey(key32, usage) {
5570
+ assertKey(key32);
5571
+ return globalThis.crypto.subtle.importKey(
5572
+ "raw",
5573
+ key32,
5574
+ "AES-GCM",
5575
+ false,
5576
+ [usage]
5577
+ );
5578
+ }
5579
+ function generateKey() {
5580
+ return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
5581
+ }
5582
+ async function seal(plaintextBytes, key32) {
5583
+ const { nonce, ciphertext } = await encryptEnvelope(plaintextBytes, key32);
5584
+ const blob = new Uint8Array(HEADER_LENGTH + nonce.length + ciphertext.length);
5585
+ blob[0] = SEALED_BLOB_VERSION;
5586
+ blob.set(nonce, HEADER_LENGTH);
5587
+ blob.set(ciphertext, HEADER_LENGTH + nonce.length);
5588
+ return { blob, cid: await computeCid(blob) };
5589
+ }
5590
+ async function open(blob, key32) {
5591
+ if (blob.length < HEADER_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
5592
+ throw new TypeError(`sealed blob too short: ${blob.length} bytes`);
5593
+ }
5594
+ if (blob[0] !== SEALED_BLOB_VERSION) {
5595
+ throw new TypeError(`unknown sealed blob version: ${blob[0]}`);
5596
+ }
5597
+ const nonce = blob.subarray(HEADER_LENGTH, HEADER_LENGTH + NONCE_LENGTH);
5598
+ const ciphertext = blob.subarray(HEADER_LENGTH + NONCE_LENGTH);
5599
+ return decryptEnvelope(nonce, ciphertext, key32);
5600
+ }
5601
+ async function encryptEnvelope(plaintextBytes, key32) {
5602
+ const key = await importAesKey(key32, "encrypt");
5603
+ const nonce = globalThis.crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
5604
+ const ciphertext = new Uint8Array(
5605
+ await globalThis.crypto.subtle.encrypt(
5606
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5607
+ key,
5608
+ plaintextBytes
5609
+ )
5610
+ );
5611
+ return { nonce, ciphertext };
5612
+ }
5613
+ async function decryptEnvelope(nonce, ciphertext, key32) {
5614
+ if (nonce.length !== NONCE_LENGTH) {
5615
+ throw new TypeError(`nonce must be ${NONCE_LENGTH} bytes, got ${nonce.length}`);
5616
+ }
5617
+ const key = await importAesKey(key32, "decrypt");
5618
+ return new Uint8Array(
5619
+ await globalThis.crypto.subtle.decrypt(
5620
+ { name: "AES-GCM", iv: nonce, additionalData: AAD },
5621
+ key,
5622
+ ciphertext
5623
+ )
5624
+ );
5625
+ }
5559
5626
  function isPlainRecord(value) {
5560
5627
  const proto = Object.getPrototypeOf(value);
5561
5628
  return proto === Object.prototype || proto === null;
@@ -5788,8 +5855,6 @@ function base32Lower(bytes3) {
5788
5855
  return output;
5789
5856
  }
5790
5857
  var KEY_LENGTH2 = 32;
5791
- var INLINE_PREFIX = "#tc2=";
5792
- var PUBLIC_INLINE_PARAMETER = "tc2";
5793
5858
  var MAX_INLINE_BYTES = 256 * 1024;
5794
5859
  function assertCanonicalCid(cidString) {
5795
5860
  const cid2 = CID.parse(cidString);
@@ -5798,62 +5863,61 @@ function assertCanonicalCid(cidString) {
5798
5863
  throw new TypeError(`not a canonical CIDv1 raw sha2-256 base32 CID: ${cidString}`);
5799
5864
  }
5800
5865
  }
5801
- async function encodePublicInlineShareUrl(parts) {
5866
+ async function encodeSealedInlineShareUrl(parts) {
5802
5867
  if (!isCanonicalHttpsOrigin(parts.origin)) throw new TypeError("origin must be a canonical https origin");
5803
- if (parts.plaintext.byteLength === 0 || parts.plaintext.byteLength > MAX_INLINE_BYTES) throw new RangeError("public inline envelope is outside the allowed size");
5804
- const ciphertextCid = await computeCid(parts.plaintext);
5805
- const payloadBytes = new TextEncoder().encode(canonicalize({
5868
+ if (parts.ciphertext.byteLength === 0 || parts.ciphertext.byteLength > MAX_INLINE_BYTES) throw new RangeError("inline ciphertext is outside the allowed size");
5869
+ if (parts.key32.byteLength !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
5870
+ const ciphertextCid = await computeCid(parts.ciphertext);
5871
+ const payload = canonicalize({
5806
5872
  v: 2,
5807
- c: toBase64Url(parts.plaintext),
5808
- cid: ciphertextCid
5809
- }));
5873
+ c: toBase64Url(parts.ciphertext),
5874
+ cid: ciphertextCid,
5875
+ k: toBase64Url(parts.key32)
5876
+ });
5877
+ const payloadBytes = new TextEncoder().encode(payload);
5810
5878
  if (payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new RangeError("inline URL is too large");
5811
- return `${parts.origin}/viewer?${PUBLIC_INLINE_PARAMETER}=${toBase64Url(payloadBytes)}`;
5879
+ return `${parts.origin}/s/inline#v=2&p=${toBase64Url(payloadBytes)}`;
5812
5880
  }
5813
- function parseInlineShareUrl(url, options = {}) {
5881
+ async function parseSealedInlineShareUrl(url, options = {}) {
5814
5882
  const parsed = new URL(url);
5815
- if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("inline share URL must be canonical HTTPS without userinfo");
5816
- if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("inline share URL origin is not trusted");
5817
- if (parsed.pathname !== "/viewer") throw new TypeError("not a TinyCloud policy share URL");
5818
- const secretPayload = parsed.search === "" && parsed.hash.startsWith(INLINE_PREFIX) ? parsed.hash.slice(INLINE_PREFIX.length) : void 0;
5819
- const publicPayload = parsed.hash === "" && parsed.searchParams.size === 1 ? parsed.searchParams.get(PUBLIC_INLINE_PARAMETER) ?? void 0 : void 0;
5820
- if (secretPayload === void 0 && publicPayload === void 0) throw new TypeError("not a canonical TinyCloud policy share URL");
5821
- if (publicPayload !== void 0 && parsed.search !== `?${PUBLIC_INLINE_PARAMETER}=${publicPayload}`) throw new TypeError("public inline URL is not canonical");
5883
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new TypeError("sealed inline share URL must be canonical HTTPS without userinfo");
5884
+ if (!isCanonicalHttpsOrigin(parsed.origin) || options.expectedOrigin !== void 0 && parsed.origin !== options.expectedOrigin) throw new TypeError("sealed inline share URL origin is not trusted");
5885
+ if (parsed.pathname !== "/s/inline" || parsed.search !== "") throw new TypeError("not a Node sealed-inline share URL");
5886
+ if (url !== `${parsed.origin}/s/inline${parsed.hash}`) throw new TypeError("sealed inline share URL is not lexically canonical");
5887
+ const prefix = "#v=2&p=";
5888
+ if (!parsed.hash.startsWith(prefix)) throw new TypeError("sealed inline URL is missing its canonical fragment");
5889
+ const encoded = parsed.hash.slice(prefix.length);
5890
+ if (encoded.length === 0 || parsed.hash !== `${prefix}${encoded}`) throw new TypeError("sealed inline URL fragment is not canonical");
5822
5891
  let payloadBytes;
5823
5892
  try {
5824
- payloadBytes = fromBase64Url(secretPayload ?? publicPayload);
5893
+ payloadBytes = fromBase64Url(encoded);
5825
5894
  } catch {
5826
- throw new TypeError("inline payload is not canonical base64url");
5895
+ throw new TypeError("sealed inline payload is not canonical base64url");
5827
5896
  }
5828
- if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("inline payload is too large");
5897
+ if (payloadBytes.byteLength === 0 || payloadBytes.byteLength > MAX_INLINE_BYTES * 2) throw new TypeError("sealed inline payload is too large");
5898
+ const payloadText = new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes);
5829
5899
  let value;
5830
5900
  try {
5831
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payloadBytes));
5901
+ value = JSON.parse(payloadText);
5832
5902
  } catch {
5833
- throw new TypeError("inline payload is not valid JSON");
5903
+ throw new TypeError("sealed inline payload is not valid JSON");
5834
5904
  }
5835
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("inline payload must be an object");
5836
- if (canonicalize(value) !== new TextDecoder("utf-8").decode(payloadBytes)) throw new TypeError("inline payload is not canonical JSON");
5905
+ if (typeof value !== "object" || value === null || Array.isArray(value) || canonicalize(value) !== payloadText) throw new TypeError("sealed inline payload is not canonical JSON");
5837
5906
  const record = value;
5838
- const keys = Object.keys(record);
5839
- if (!keys.every((key) => key === "v" || key === "c" || key === "cid" || key === "k") || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || publicPayload !== void 0 && (keys.length !== 3 || record.k !== void 0)) throw new TypeError("inline payload has invalid fields");
5907
+ if (Object.keys(record).length !== 4 || record.v !== 2 || typeof record.c !== "string" || typeof record.cid !== "string" || typeof record.k !== "string") throw new TypeError("sealed inline payload has invalid fields");
5840
5908
  let ciphertext;
5909
+ let key32;
5841
5910
  try {
5842
5911
  ciphertext = fromBase64Url(record.c);
5912
+ key32 = fromBase64Url(record.k);
5843
5913
  } catch {
5844
- throw new TypeError("inline ciphertext is not canonical base64url");
5914
+ throw new TypeError("sealed inline payload has invalid base64url material");
5845
5915
  }
5846
- if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("inline ciphertext is outside the allowed size");
5916
+ if (ciphertext.byteLength === 0 || ciphertext.byteLength > MAX_INLINE_BYTES) throw new TypeError("sealed inline ciphertext is outside the allowed size");
5917
+ if (key32.byteLength !== KEY_LENGTH2) throw new TypeError("sealed inline key must be 32 bytes");
5847
5918
  assertCanonicalCid(record.cid);
5848
- if (record.k !== void 0 && typeof record.k !== "string") throw new TypeError("inline key must be base64url");
5849
- let key32;
5850
- try {
5851
- key32 = record.k === void 0 ? void 0 : fromBase64Url(record.k);
5852
- } catch {
5853
- throw new TypeError("inline key is not canonical base64url");
5854
- }
5855
- if (key32 !== void 0 && key32.length !== KEY_LENGTH2) throw new TypeError("inline key must be 32 bytes");
5856
- return { kind: "inline", ciphertextCid: record.cid, ciphertext, ...key32 === void 0 ? {} : { key32 } };
5919
+ if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
5920
+ return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
5857
5921
  }
5858
5922
 
5859
5923
  // src/receive.ts
@@ -5901,19 +5965,17 @@ async function resolvePolicyShare(link, options) {
5901
5965
  let parsed;
5902
5966
  try {
5903
5967
  url = new URL(link);
5904
- if (url.pathname !== "/viewer" || url.hash !== "" || url.searchParams.size !== 1 || !url.searchParams.has("tc2")) throw new Error("not public Policy/v3");
5905
- parsed = parseInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5906
- if (parsed.key32 !== void 0) throw new Error("secret inline transport is retired");
5968
+ parsed = await parseSealedInlineShareUrl(link, { ...options.expectedOrigin === void 0 ? {} : { expectedOrigin: options.expectedOrigin } });
5907
5969
  } catch {
5908
5970
  throw new ShareReceiveError("invalid-link", "share link format is invalid");
5909
5971
  }
5910
5972
  const limit = options.maxSealedBlobBytes ?? DEFAULT_MAX_SEALED_BLOB_BYTES;
5911
- if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "public policy envelope exceeds the configured byte limit");
5912
- if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "public envelope bytes do not match the link CID");
5973
+ if (!Number.isSafeInteger(limit) || limit <= 0 || parsed.ciphertext.byteLength > limit) throw new ShareReceiveError("max-bytes-exceeded", "sealed policy envelope exceeds the configured byte limit");
5974
+ if (await computeCid(parsed.ciphertext) !== parsed.ciphertextCid) throw new ShareReceiveError("cid-mismatch", "sealed envelope bytes do not match the link CID");
5913
5975
  options.signal?.throwIfAborted();
5914
5976
  let envelope;
5915
5977
  try {
5916
- const encoded = new TextDecoder("utf-8", { fatal: true }).decode(parsed.ciphertext);
5978
+ const encoded = new TextDecoder("utf-8", { fatal: true }).decode(await open(parsed.ciphertext, parsed.key32));
5917
5979
  const value = JSON.parse(encoded);
5918
5980
  if (canonicalize(value) !== encoded) throw new Error("non-canonical envelope");
5919
5981
  envelope = shareEnvelopeV3Schema.parse(value);
@@ -6664,14 +6726,25 @@ async function publishAddressedShare(options) {
6664
6726
  const envelope = { ...unsigned, signature: { signerDid: options.authority.ownerDid, algorithm: "Ed25519", value: toBase64Url(envelopeSignature) } };
6665
6727
  shareEnvelopeV3Schema.parse(envelope);
6666
6728
  const envelopeBytes = textEncoder.encode(canonicalize(envelope));
6667
- const envelopeCid = await computeCid(envelopeBytes);
6668
- const url = await encodePublicInlineShareUrl({ origin: options.shareOrigin, plaintext: envelopeBytes });
6669
- const deliveryMaterial = { envelope, shareCid: envelopeCid };
6729
+ const envelopeKeyBytes = generateKey();
6730
+ const sealed = await seal(envelopeBytes, envelopeKeyBytes);
6731
+ const envelopeKey = toBase64Url(envelopeKeyBytes);
6732
+ const url = await encodeSealedInlineShareUrl({
6733
+ origin: options.shareOrigin,
6734
+ ciphertext: sealed.blob,
6735
+ key32: envelopeKeyBytes
6736
+ });
6737
+ const deliveryMaterial = {
6738
+ envelope,
6739
+ shareCid: sealed.cid,
6740
+ sealedEnvelope: toBase64Url(sealed.blob),
6741
+ envelopeKey
6742
+ };
6670
6743
  options.onDeliveryMaterial?.(deliveryMaterial);
6671
6744
  return publicationResult({
6672
6745
  options,
6673
6746
  url,
6674
- envelopeCid,
6747
+ envelopeCid: sealed.cid,
6675
6748
  matcher,
6676
6749
  policyCid: created.policyCid,
6677
6750
  policyRootCid: policyRoot.cid,
@@ -7728,6 +7801,119 @@ async function defaultIdempotencyKey(shareId, recipient) {
7728
7801
  return `tinycloud-share:${shareId}:${toBase64Url(digest3)}`;
7729
7802
  }
7730
7803
 
7804
+ // src/credential-invitation.ts
7805
+ var CredentialInvitationError = class extends Error {
7806
+ constructor(code3, message) {
7807
+ super(message);
7808
+ this.code = code3;
7809
+ this.name = "CredentialInvitationError";
7810
+ }
7811
+ };
7812
+ function canonicalOrigin(value) {
7813
+ let url;
7814
+ try {
7815
+ url = new URL(value);
7816
+ } catch {
7817
+ throw new CredentialInvitationError(
7818
+ "invalid-origin",
7819
+ "credential invitation origin is invalid"
7820
+ );
7821
+ }
7822
+ if (url.origin !== value || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || url.protocol !== "https:")
7823
+ throw new CredentialInvitationError(
7824
+ "invalid-origin",
7825
+ "credential invitation origin is invalid"
7826
+ );
7827
+ return url.origin;
7828
+ }
7829
+ function object2(value) {
7830
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7831
+ }
7832
+ async function validateReceipt(receipt, shareUrl) {
7833
+ if (!object2(receipt) || !object2(receipt.request) || !object2(receipt.admission) || !object2(receipt.proof)) {
7834
+ throw new CredentialInvitationError(
7835
+ "invalid-receipt",
7836
+ "credential invitation receipt is invalid"
7837
+ );
7838
+ }
7839
+ if (receipt.request.returnLink !== shareUrl) {
7840
+ throw new CredentialInvitationError(
7841
+ "invalid-receipt",
7842
+ "credential invitation is not bound to the share link"
7843
+ );
7844
+ }
7845
+ let link;
7846
+ try {
7847
+ link = new URL(shareUrl);
7848
+ } catch {
7849
+ throw new CredentialInvitationError(
7850
+ "invalid-receipt",
7851
+ "credential invitation share link is invalid"
7852
+ );
7853
+ }
7854
+ if (link.search !== "" || link.pathname !== "/s/inline") {
7855
+ throw new CredentialInvitationError(
7856
+ "invalid-receipt",
7857
+ "credential invitation share link must be sealed inline"
7858
+ );
7859
+ }
7860
+ try {
7861
+ await parseSealedInlineShareUrl(shareUrl);
7862
+ } catch {
7863
+ throw new CredentialInvitationError(
7864
+ "invalid-receipt",
7865
+ "credential invitation share link is invalid"
7866
+ );
7867
+ }
7868
+ }
7869
+ async function deliverCredentialInvitation(input) {
7870
+ const credentialsOrigin = canonicalOrigin(input.credentialsOrigin);
7871
+ await validateReceipt(input.receipt, input.shareUrl);
7872
+ const fetchFn = input.fetchFn ?? globalThis.fetch;
7873
+ let response;
7874
+ try {
7875
+ response = await fetchFn(`${credentialsOrigin}/v1/credential-invitations`, {
7876
+ method: "POST",
7877
+ credentials: "omit",
7878
+ redirect: "error",
7879
+ referrerPolicy: "no-referrer",
7880
+ headers: {
7881
+ accept: "application/json",
7882
+ "content-type": "application/json"
7883
+ },
7884
+ body: JSON.stringify(input.receipt),
7885
+ ...input.signal === void 0 ? {} : { signal: input.signal }
7886
+ });
7887
+ } catch {
7888
+ throw new CredentialInvitationError(
7889
+ "transport",
7890
+ "credential invitation delivery is unavailable"
7891
+ );
7892
+ }
7893
+ if (response.status !== 202) {
7894
+ throw new CredentialInvitationError(
7895
+ "rejected",
7896
+ "credential invitation delivery was not accepted"
7897
+ );
7898
+ }
7899
+ let body;
7900
+ try {
7901
+ body = await response.json();
7902
+ } catch {
7903
+ throw new CredentialInvitationError(
7904
+ "invalid-response",
7905
+ "credential invitation response is invalid"
7906
+ );
7907
+ }
7908
+ if (!object2(body) || Object.keys(body).length !== 1 || body.status !== "accepted") {
7909
+ throw new CredentialInvitationError(
7910
+ "invalid-response",
7911
+ "credential invitation response is invalid"
7912
+ );
7913
+ }
7914
+ return { status: "accepted" };
7915
+ }
7916
+
7731
7917
  // src/lifecycle.ts
7732
7918
  function targetKind2(record) {
7733
7919
  if (record.targetKind !== void 0) return record.targetKind;
@@ -7834,7 +8020,7 @@ var DOMAIN = SHARE_V2_PROTOCOL.challengeDomain;
7834
8020
  var PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
7835
8021
  var SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
7836
8022
  var INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
7837
- function object2(value, label) {
8023
+ function object3(value, label) {
7838
8024
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
7839
8025
  return value;
7840
8026
  }
@@ -7893,11 +8079,11 @@ function nativeAction(action) {
7893
8079
  return action === "list" ? "tinycloud.kv/list" : action === "edit" ? "tinycloud.kv/put" : "tinycloud.kv/get";
7894
8080
  }
7895
8081
  function policyAttenuationForV3(envelope) {
7896
- const policy = object2(envelope.policy, "v3 policy");
8082
+ const policy = object3(envelope.policy, "v3 policy");
7897
8083
  if (!Array.isArray(policy.capabilityCeiling)) throw new Error("v3 policy capability ceiling is invalid");
7898
8084
  const attenuation = {};
7899
8085
  for (const raw of policy.capabilityCeiling) {
7900
- const capability = object2(raw, "v3 policy capability");
8086
+ const capability = object3(raw, "v3 policy capability");
7901
8087
  if (typeof capability.resource !== "string" || attenuation[capability.resource] !== void 0) throw new Error("v3 policy capability is invalid");
7902
8088
  if (capability.kind === "encryption") {
7903
8089
  if (capability.action !== "tinycloud.encryption/decrypt") throw new Error("v3 policy capability is invalid");
@@ -7955,8 +8141,8 @@ function verifyV3PolicyAuthorization(input) {
7955
8141
  const factKeys = Object.keys(fact);
7956
8142
  const legacyShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
7957
8143
  const v4Shape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
7958
- const binding = object2(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
7959
- const policy = object2(input.envelope.policy, "v3 policy");
8144
+ const binding = object3(input.envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8145
+ const policy = object3(input.envelope.policy, "v3 policy");
7960
8146
  const now = Math.floor(Date.now() / 1e3);
7961
8147
  if (!legacyShape && !v4Shape || POLICY_SESSION_DIGEST_FACT_KEYS.some((key) => !LOWER_SHA256_HEX.test(fact[key])) || v4Shape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)) || compact.payload.aud !== input.holderDid || compact.payload.iss.split("#", 1)[0] !== fact.nodeAudience || fact.profile !== "policy-session-ucan/v1" || fact.ownerDid !== policy.ownerDid || fact.policyId !== policy.policyId || fact.policyCid !== input.envelope.policyCid || fact.contentSourceDigestHex !== input.envelope.contentSourceDigestHex || fact.enforcerDid !== binding.enforcerDid || fact.nodeAudience !== binding.nodeAudience || fact.recipientDid !== input.holderDid || fact.policyDelegationCid !== input.envelope.policyRoot.cid || fact.enforcementDelegationCid !== input.envelope.enforcementRoot.cid || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== input.envelope.policyRoot.cid || compact.payload.prf[1] !== input.envelope.enforcementRoot.cid || compact.payload.nbf > now || compact.payload.exp <= now || compact.payload.exp - compact.payload.nbf > 60 || canonicalize(compact.payload.att) !== canonicalize(policyAttenuationForV3(input.envelope))) throw new Error("v3 policy delegation signed binding mismatch");
7962
8148
  return compact;
@@ -8047,7 +8233,7 @@ var ShareRecipientClient = class {
8047
8233
  if (this.session === void 0) await this.establish(envelope);
8048
8234
  const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
8049
8235
  if (!response.ok) throw new Error("share recipient read was rejected");
8050
- const value = object2(await response.json(), "share read response");
8236
+ const value = object3(await response.json(), "share read response");
8051
8237
  const expectedAction = nativeAction("read");
8052
8238
  if (value.type !== "TinyCloudShareInvokeResponse" || value.version !== 2 || value.action !== expectedAction || value.resource !== envelope.resource.path || typeof value.content !== "string" || typeof value.bodyDigest !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.bodyDigest) || !Object.hasOwn(value, "proof")) throw new Error("share read response is invalid");
8053
8239
  const content = fromBase64Url(value.content);
@@ -8062,7 +8248,7 @@ var ShareRecipientClient = class {
8062
8248
  }
8063
8249
  async establishV3(envelope) {
8064
8250
  if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
8065
- const attestedEnforcer = object2(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8251
+ const attestedEnforcer = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
8066
8252
  if (attestedEnforcer.enforcerDid !== envelope.target.nodeAudience || typeof attestedEnforcer.nodeAudience !== "string") throw new Error("v3 attested enforcer binding mismatch");
8067
8253
  try {
8068
8254
  ed25519PublicKeyFromDidKey(attestedEnforcer.nodeAudience);
@@ -8077,7 +8263,7 @@ var ShareRecipientClient = class {
8077
8263
  ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8078
8264
  });
8079
8265
  if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
8080
- const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
8266
+ const challenge = object3(await challengeResponse.json(), "v3 policy challenge");
8081
8267
  if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid || challenge.nodeAudience !== attestedEnforcer.nodeAudience || challenge.enforcerDid !== void 0 && challenge.enforcerDid !== attestedEnforcer.enforcerDid || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("v3 policy challenge binding mismatch");
8082
8268
  const material = await this.options.buildPresentation({
8083
8269
  challenge,
@@ -8098,7 +8284,7 @@ var ShareRecipientClient = class {
8098
8284
  ...this.options.signal === void 0 ? {} : { signal: this.options.signal }
8099
8285
  });
8100
8286
  if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8101
- const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
8287
+ const delegation = object3(await delegationResponse.json(), "v3 policy delegation");
8102
8288
  if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
8103
8289
  const compact = verifyV3PolicyAuthorization({ authorization: delegation.authorization, cid: delegation.sessionCid, envelope, holderDid: this.options.holderDid });
8104
8290
  const fact = compact.payload.fct[0];
@@ -8140,7 +8326,7 @@ var ShareRecipientClient = class {
8140
8326
  this.options.onStage?.("decryption");
8141
8327
  const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body), ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
8142
8328
  if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
8143
- const value = object2(await response.json(), "v3 decrypt response");
8329
+ const value = object3(await response.json(), "v3 decrypt response");
8144
8330
  const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
8145
8331
  if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex3(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
8146
8332
  const unsigned = { ...value };
@@ -8228,11 +8414,11 @@ function createAddressedAuthorization(input) {
8228
8414
  },
8229
8415
  async verifyResult({ envelope, value, proof }) {
8230
8416
  try {
8231
- const wrapper = object2(proof, "share read proof");
8232
- const response = object2(wrapper.response, "share read response proof");
8417
+ const wrapper = object3(proof, "share read proof");
8418
+ const response = object3(wrapper.response, "share read response proof");
8233
8419
  const envelopeAction = nativeAction("read");
8234
8420
  if (response.type !== "TinyCloudShareInvokeResponse" || response.version !== 2 || response.action !== envelopeAction || response.resource !== envelope.resource.path || typeof response.bodyDigest !== "string" || response.bodyDigest !== value.bodyDigest || response.bodyDigest !== await digestBytes2(value.bytes)) return false;
8235
- const detached = object2(wrapper.detached, "share read detached proof");
8421
+ const detached = object3(wrapper.detached, "share read detached proof");
8236
8422
  if (input.trustedNode === void 0 || detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
8237
8423
  const unsigned = { ...response };
8238
8424
  delete unsigned.proof;
@@ -8300,6 +8486,7 @@ async function openNativeShare(sharing, link) {
8300
8486
  return sharing.receive(parseNativeShareUrl(link), { autoSubdelegate: false, useSessionKey: false });
8301
8487
  }
8302
8488
  export {
8489
+ CredentialInvitationError,
8303
8490
  DEFAULT_MAX_CONTENT_BLOB_BYTES,
8304
8491
  DEFAULT_MAX_SEALED_BLOB_BYTES,
8305
8492
  DEFAULT_SHARE_LIFETIME_MS,
@@ -8332,6 +8519,7 @@ export {
8332
8519
  createNativeShare,
8333
8520
  createPolicyEnforcementDelegation,
8334
8521
  createShareV2HolderBindingArtifact,
8522
+ deliverCredentialInvitation,
8335
8523
  fromBase64Url,
8336
8524
  historyRecordForPublishedShare,
8337
8525
  inspectShare,