@tinycloud/share-sdk 0.2.0-beta.0 → 0.2.0-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.cjs CHANGED
@@ -235,10 +235,10 @@ var util;
235
235
  return obj[e];
236
236
  });
237
237
  };
238
- util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object2) => {
238
+ util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object3) => {
239
239
  const keys = [];
240
- for (const key in object2) {
241
- if (Object.prototype.hasOwnProperty.call(object2, key)) {
240
+ for (const key in object3) {
241
+ if (Object.prototype.hasOwnProperty.call(object3, key)) {
242
242
  keys.push(key);
243
243
  }
244
244
  }
@@ -4134,6 +4134,9 @@ var NEVER = INVALID;
4134
4134
 
4135
4135
  // ../share-envelope/dist/index.js
4136
4136
  var import_ed255192 = require("@noble/curves/ed25519");
4137
+ var import_blake3 = require("@noble/hashes/blake3");
4138
+ var import_ed255193 = require("@noble/curves/ed25519");
4139
+ var import_sha256 = require("@noble/hashes/sha256");
4137
4140
  var empty = new Uint8Array(0);
4138
4141
  function equals(aa, bb) {
4139
4142
  if (aa === bb) {
@@ -5306,6 +5309,128 @@ function validateV2Invariants(value, ctx) {
5306
5309
  }
5307
5310
  var unsignedShareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.superRefine(validateV2Invariants);
5308
5311
  var shareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV2Invariants);
5312
+ var unifiedResourceSchema = external_exports.string().refine((value) => {
5313
+ const match = /^tinycloud:\/\/([^/]+)\/kv\/(.+)$/.exec(value);
5314
+ if (match === null || /[:?#%]/.test(match[1])) return false;
5315
+ const path = match[2];
5316
+ return !path.startsWith("/") && !path.endsWith("/") && !path.includes("//") && path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
5317
+ }, { message: "expected canonical TinyCloud KV resource" });
5318
+ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) => {
5319
+ if (!value.startsWith("urn:tinycloud:encryption:")) return false;
5320
+ const rest = value.slice("urn:tinycloud:encryption:".length);
5321
+ const separator = rest.lastIndexOf(":");
5322
+ const owner = separator < 0 ? "" : rest.slice(0, separator);
5323
+ const network = separator < 0 ? "" : rest.slice(separator + 1);
5324
+ return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
5325
+ }, { message: "expected canonical TinyCloud encryption network" });
5326
+ var unifiedKvCapabilitySchema = external_exports.object({
5327
+ kind: external_exports.literal("kv"),
5328
+ resource: unifiedResourceSchema,
5329
+ selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5330
+ actions: external_exports.array(external_exports.union([
5331
+ external_exports.literal("tinycloud.kv/get"),
5332
+ external_exports.literal("tinycloud.kv/list"),
5333
+ external_exports.literal("tinycloud.kv/metadata"),
5334
+ external_exports.literal("tinycloud.kv/put")
5335
+ ])).min(1)
5336
+ }).strict();
5337
+ var unifiedEncryptionCapabilitySchema = external_exports.object({
5338
+ kind: external_exports.literal("encryption"),
5339
+ resource: unifiedEncryptionNetworkSchema,
5340
+ action: external_exports.literal("tinycloud.encryption/decrypt")
5341
+ }).strict();
5342
+ var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
5343
+ var unifiedContentSourceSchema = external_exports.object({
5344
+ shareId: external_exports.string().min(1),
5345
+ kvResource: unifiedResourceSchema,
5346
+ selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5347
+ encryptionNetwork: unifiedEncryptionNetworkSchema,
5348
+ encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5349
+ keyVersion: external_exports.number().int().positive(),
5350
+ mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
5351
+ initialCiphertextDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/).optional()
5352
+ }).strict();
5353
+ var unifiedPolicySchema = external_exports.object({
5354
+ schema: external_exports.literal("xyz.tinycloud.policy/policy/v1"),
5355
+ policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
5356
+ ownerDid: external_exports.string().min(1),
5357
+ createdAt: external_exports.string().datetime({ offset: true }),
5358
+ expiresAt: external_exports.string().datetime({ offset: true }).optional(),
5359
+ contentSource: unifiedContentSourceSchema,
5360
+ capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
5361
+ signature: external_exports.object({ suite: external_exports.string().min(1), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
5362
+ }).strict();
5363
+ var unifiedRootSchema = external_exports.object({
5364
+ cid: external_exports.string().min(1),
5365
+ authorization: external_exports.string().regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
5366
+ role: external_exports.union([external_exports.literal("policy-authority"), external_exports.literal("policy-enforcement")])
5367
+ }).strict();
5368
+ var attestedEnforcerBindingV2Schema = external_exports.object({
5369
+ schema: external_exports.literal("xyz.tinycloud.policy/attested-enforcer/v2"),
5370
+ enforcerDid: external_exports.string().regex(/^did:key:z[1-9A-HJ-NP-Za-km-z]+$/),
5371
+ nodeAudience: external_exports.string().min(1),
5372
+ attestationBindingDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5373
+ issuedAt: external_exports.string().datetime({ offset: true }),
5374
+ expiresAt: external_exports.string().datetime({ offset: true }),
5375
+ signature: external_exports.object({ suite: external_exports.literal("Ed25519"), signerDid: external_exports.string().min(1), value: base64UrlString() }).strict()
5376
+ }).strict();
5377
+ var v3TargetSchema = external_exports.object({
5378
+ origin: external_exports.string().refine(isCanonicalHttpsOrigin, { message: "expected a canonical https origin" }),
5379
+ nodeAudience: external_exports.string().min(1),
5380
+ spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
5381
+ }).strict();
5382
+ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5383
+ version: external_exports.literal(3),
5384
+ shareId: external_exports.string().min(1),
5385
+ recipientMatcher: recipientMatcherSchema,
5386
+ deliveryEmail: external_exports.string().email().optional(),
5387
+ actions: external_exports.array(shareActionSchema).min(1).max(3),
5388
+ resource: resourceSelectorSchema,
5389
+ target: v3TargetSchema,
5390
+ policy: unifiedPolicySchema,
5391
+ policyCid: external_exports.string().min(1),
5392
+ policyRoot: unifiedRootSchema,
5393
+ enforcementRoot: unifiedRootSchema,
5394
+ attestedEnforcerBinding: attestedEnforcerBindingV2Schema,
5395
+ contentSource: unifiedContentSourceSchema,
5396
+ contentSourceDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5397
+ encryptionNetwork: unifiedEncryptionNetworkSchema,
5398
+ expiry: external_exports.string().datetime({ offset: true }),
5399
+ display: displaySchema,
5400
+ encrypted: external_exports.literal(true),
5401
+ metadata: contentMetadataSchema
5402
+ }).strict();
5403
+ function validateV3Invariants(value, ctx) {
5404
+ const actions = [...value.actions];
5405
+ if (new Set(actions).size !== actions.length || actions.some((action, index) => action !== ["read", "list", "edit"].filter((candidate) => actions.includes(candidate))[index])) {
5406
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["actions"], message: "actions must be unique and canonically ordered" });
5407
+ }
5408
+ if (value.policyRoot.role !== "policy-authority" || value.enforcementRoot.role !== "policy-enforcement") {
5409
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policyRoot", "role"], message: "root roles are fixed" });
5410
+ }
5411
+ if (value.policyCid.length === 0 || value.policyCid === value.policyRoot.cid || value.policyCid === value.enforcementRoot.cid) {
5412
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policyCid"], message: "policy CID must be distinct from both roots" });
5413
+ }
5414
+ if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
5415
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
5416
+ }
5417
+ if (value.attestedEnforcerBinding.enforcerDid !== value.target.nodeAudience || value.attestedEnforcerBinding.nodeAudience !== value.target.nodeAudience || value.attestedEnforcerBinding.signature.signerDid !== value.target.nodeAudience || Date.parse(value.attestedEnforcerBinding.expiresAt) < Date.parse(value.expiry)) {
5418
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
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.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
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
5422
+ }
5423
+ if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
5424
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
5425
+ }
5426
+ const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
5427
+ const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === value.encryptionNetwork && capability.action === "tinycloud.encryption/decrypt");
5428
+ if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
5429
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
5430
+ }
5431
+ }
5432
+ var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
5433
+ var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
5309
5434
  var BEARER_READ_ABILITY = "kv/get";
5310
5435
  var READ_ABILITIES = /* @__PURE__ */ new Set([BEARER_READ_ABILITY]);
5311
5436
  var ED25519_VERIFY_OPTS = { zip215: false };
@@ -5645,6 +5770,83 @@ function serialize(value) {
5645
5770
  function canonicalize(value) {
5646
5771
  return serialize(value);
5647
5772
  }
5773
+ async function signCompactUcanAuthorization(input) {
5774
+ if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 60) {
5775
+ throw new TypeError("compact invocation lifetime must be between one and 60 seconds");
5776
+ }
5777
+ const principal = input.issuerDid.split("#", 1)[0];
5778
+ const publicKey = ed25519PublicKeyFromDidKey(principal);
5779
+ const header = {
5780
+ alg: "EdDSA",
5781
+ jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url(publicKey) },
5782
+ typ: "JWT",
5783
+ ucv: "0.10.0"
5784
+ };
5785
+ const payload = {
5786
+ att: input.attenuation,
5787
+ aud: input.audienceDid,
5788
+ exp: input.expiresAt,
5789
+ fct: input.facts,
5790
+ iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
5791
+ nbf: input.notBefore,
5792
+ nnc: input.nonce,
5793
+ prf: input.proofs
5794
+ };
5795
+ const protectedSegment = encodeBase64Url(new TextEncoder().encode(canonicalize(header)));
5796
+ const payloadSegment = encodeBase64Url(new TextEncoder().encode(canonicalize(payload)));
5797
+ const signingInput = new TextEncoder().encode(`${protectedSegment}.${payloadSegment}`);
5798
+ const signature = await input.sign(signingInput);
5799
+ if (signature.length !== 64) throw new TypeError("compact Authorization signature must be Ed25519");
5800
+ return verifyCompactUcanAuthorization(
5801
+ `${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`
5802
+ );
5803
+ }
5804
+ function verifyCompactUcanAuthorization(authorization, expectedCid) {
5805
+ if (/\s/.test(authorization)) throw new TypeError("compact Authorization contains whitespace");
5806
+ const segments = authorization.split(".");
5807
+ if (segments.length !== 3 || segments.some((segment) => segment.length === 0)) throw new TypeError("compact Authorization must contain three segments");
5808
+ const [headerSegment, payloadSegment, signatureSegment] = segments;
5809
+ const headerBytes = decodeBase64Url(headerSegment);
5810
+ const payloadBytes = decodeBase64Url(payloadSegment);
5811
+ const signature = decodeBase64Url(signatureSegment);
5812
+ const decoder = new TextDecoder("utf-8", { fatal: true });
5813
+ const header = JSON.parse(decoder.decode(headerBytes));
5814
+ const payload = JSON.parse(decoder.decode(payloadBytes));
5815
+ if (canonicalize(header) !== decoder.decode(headerBytes) || canonicalize(payload) !== decoder.decode(payloadBytes)) throw new TypeError("compact Authorization JSON is not canonical");
5816
+ assertExactKeys(header, ["alg", "jwk", "typ", "ucv"], "protected header");
5817
+ const jwk = object(header.jwk, "protected JWK");
5818
+ assertExactKeys(jwk, ["alg", "crv", "kty", "x"], "protected JWK");
5819
+ if (header.alg !== "EdDSA" || header.typ !== "JWT" || header.ucv !== "0.10.0" || jwk.alg !== "EdDSA" || jwk.crv !== "Ed25519" || jwk.kty !== "OKP" || typeof jwk.x !== "string") throw new TypeError("compact Authorization header is invalid");
5820
+ assertExactKeys(payload, ["att", "aud", "exp", "fct", "iss", "nbf", "nnc", "prf"], "UCAN payload");
5821
+ if (typeof payload.iss !== "string" || typeof payload.aud !== "string" || typeof payload.nnc !== "string" || !Number.isInteger(payload.nbf) || !Number.isInteger(payload.exp) || payload.nbf >= payload.exp || !Array.isArray(payload.prf) || payload.prf.some((proof) => typeof proof !== "string") || !Array.isArray(payload.fct) || payload.fct.length !== 1) throw new TypeError("compact Authorization payload is invalid");
5822
+ const principal = payload.iss.split("#", 1)[0];
5823
+ const publicKey = ed25519PublicKeyFromDidKey(principal);
5824
+ if (!equal(publicKey, decodeBase64Url(jwk.x))) throw new TypeError("compact Authorization JWK does not bind issuer");
5825
+ if (!import_ed255192.ed25519.verify(signature, new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), publicKey, { zip215: false })) throw new TypeError("compact Authorization signature is invalid");
5826
+ const cid2 = CID.createV1(85, create(30, (0, import_blake3.blake3)(new TextEncoder().encode(authorization)))).toString();
5827
+ if (expectedCid !== void 0 && cid2 !== expectedCid) throw new TypeError("compact Authorization CID mismatch");
5828
+ return { authorization, cid: cid2, header, payload };
5829
+ }
5830
+ function object(value, label) {
5831
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
5832
+ return value;
5833
+ }
5834
+ function assertExactKeys(value, keys, label) {
5835
+ if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) throw new TypeError(`${label} has unknown or missing fields`);
5836
+ }
5837
+ function decodeBase64Url(value) {
5838
+ if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) throw new TypeError("compact Authorization segment is not base64url");
5839
+ const bytes3 = typeof Buffer !== "undefined" ? new Uint8Array(Buffer.from(value, "base64url")) : Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=")), (character) => character.charCodeAt(0));
5840
+ const encoded = typeof Buffer !== "undefined" ? Buffer.from(bytes3).toString("base64url") : btoa(String.fromCharCode(...bytes3)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
5841
+ if (encoded !== value) throw new TypeError("compact Authorization segment is not canonical");
5842
+ return bytes3;
5843
+ }
5844
+ function encodeBase64Url(value) {
5845
+ return typeof Buffer !== "undefined" ? Buffer.from(value).toString("base64url") : btoa(String.fromCharCode(...value)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
5846
+ }
5847
+ function equal(left, right) {
5848
+ return left.length === right.length && left.every((byte, index) => byte === right[index]);
5849
+ }
5648
5850
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5649
5851
  var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
5650
5852
  var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
@@ -5666,8 +5868,8 @@ function signingBytesV2(unsigned) {
5666
5868
  }
5667
5869
  function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
5668
5870
  const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
5669
- const publicKey = import_ed255192.ed25519.getPublicKey(ed25519PrivKey);
5670
- const signature = import_ed255192.ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
5871
+ const publicKey = import_ed255193.ed25519.getPublicKey(ed25519PrivKey);
5872
+ const signature = import_ed255193.ed25519.sign(signingBytes(unsigned), ed25519PrivKey);
5671
5873
  return {
5672
5874
  ...unsigned,
5673
5875
  signature: {
@@ -5681,7 +5883,7 @@ function verifyEnvelopeSignatureOnly(envelope) {
5681
5883
  const parsed = shareEnvelopeSchema.parse(envelope);
5682
5884
  const { signature, ...unsigned } = parsed;
5683
5885
  const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
5684
- return import_ed255192.ed25519.verify(
5886
+ return import_ed255193.ed25519.verify(
5685
5887
  fromBase64Url(signature.value),
5686
5888
  signingBytes(unsigned),
5687
5889
  publicKey,
@@ -5703,7 +5905,7 @@ async function verifyEnvelope(envelope, options) {
5703
5905
  function verifyEnvelopeV2SignatureOnly(envelope) {
5704
5906
  const parsed = shareEnvelopeV2Schema.parse(envelope);
5705
5907
  const { signature, ...unsigned } = parsed;
5706
- return import_ed255192.ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5908
+ return import_ed255193.ed25519.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5707
5909
  }
5708
5910
  async function verifyEnvelopeV2(envelope, options) {
5709
5911
  const parsed = shareEnvelopeV2Schema.parse(envelope);
@@ -6141,7 +6343,7 @@ async function receiveShare(link, options = {}) {
6141
6343
  }
6142
6344
 
6143
6345
  // src/publish.ts
6144
- var import_ed255193 = require("@noble/curves/ed25519");
6346
+ var import_ed255194 = require("@noble/curves/ed25519");
6145
6347
  var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
6146
6348
  var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
6147
6349
  var SHARE_PUBLISH_RESULT_VERSION = 1;
@@ -6326,12 +6528,12 @@ async function publishShare(options) {
6326
6528
  const spaceId = options.spaceId ?? "bearer";
6327
6529
  const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
6328
6530
  const path = `shares/${shareId}/${options.filename}`;
6329
- const sessionPrivateKey = import_ed255193.ed25519.utils.randomPrivateKey();
6330
- const senderPrivateKey = import_ed255193.ed25519.utils.randomPrivateKey();
6531
+ const sessionPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
6532
+ const senderPrivateKey = import_ed255194.ed25519.utils.randomPrivateKey();
6331
6533
  let contentKey;
6332
6534
  let envelopeKey;
6333
6535
  try {
6334
- const sessionPublicKey = import_ed255193.ed25519.getPublicKey(sessionPrivateKey);
6536
+ const sessionPublicKey = import_ed255194.ed25519.getPublicKey(sessionPrivateKey);
6335
6537
  const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
6336
6538
  const delegation = mintBearerDelegation({
6337
6539
  issuerPrivateKey: senderPrivateKey,
@@ -6423,8 +6625,8 @@ async function parseAddressedEnvelope(value) {
6423
6625
  }
6424
6626
 
6425
6627
  // src/owner-policy.ts
6426
- var import_ed255194 = require("@noble/curves/ed25519");
6427
- var import_sha256 = require("@noble/hashes/sha256");
6628
+ var import_ed255195 = require("@noble/curves/ed25519");
6629
+ var import_sha2562 = require("@noble/hashes/sha256");
6428
6630
 
6429
6631
  // ../../node_modules/multiformats/dist/src/bytes.js
6430
6632
  var empty2 = new Uint8Array(0);
@@ -7331,10 +7533,10 @@ function fromB64(value) {
7331
7533
  throw new Error("base64url decoding is unavailable");
7332
7534
  }
7333
7535
  function cid(bytesValue) {
7334
- return CID2.createV1(code2, create2(SHA256_CODE2, (0, import_sha256.sha256)(bytesValue))).toString(base322.encoder);
7536
+ return CID2.createV1(code2, create2(SHA256_CODE2, (0, import_sha2562.sha256)(bytesValue))).toString(base322.encoder);
7335
7537
  }
7336
7538
  function digest(bytesValue) {
7337
- return b64((0, import_sha256.sha256)(bytesValue));
7539
+ return b64((0, import_sha2562.sha256)(bytesValue));
7338
7540
  }
7339
7541
  function didKeyFromEd25519PublicKey2(publicKey) {
7340
7542
  if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
@@ -7512,7 +7714,7 @@ function validateOwnerSharePolicyRegistration(value, expected) {
7512
7714
  const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
7513
7715
  const signatureBytes = fromB64(proof.signature);
7514
7716
  const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
7515
- if (signatureBytes.length !== 64 || !import_ed255194.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
7717
+ if (signatureBytes.length !== 64 || !import_ed255195.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
7516
7718
  return { registration, proof };
7517
7719
  }
7518
7720
  function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
@@ -7672,7 +7874,7 @@ function assertSafeInput(input) {
7672
7874
  if (input.artifact === "html" && (input.resource.kind !== "prefix" || !input.actions.includes("read") || !input.actions.includes("list"))) throw new TypeError("html artifacts require a readable prefix");
7673
7875
  if (input.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
7674
7876
  }
7675
- async function sha2563(value) {
7877
+ async function sha2564(value) {
7676
7878
  return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
7677
7879
  }
7678
7880
  function publicationResult(input) {
@@ -7721,7 +7923,7 @@ async function publishAddressedShare(options) {
7721
7923
  const contentSource = options.contentSource;
7722
7924
  const matcher = targetMatcher(target);
7723
7925
  const expiresAt = options.expiresAt.toISOString();
7724
- const contentSourceDigest = options.contentSourceDigest ?? await sha2563(new TextEncoder().encode(canonicalize(contentSource)));
7926
+ const contentSourceDigest = options.contentSourceDigest ?? await sha2564(new TextEncoder().encode(canonicalize(contentSource)));
7725
7927
  const shareKey = await createDelegatedShareKey({ extractable: false });
7726
7928
  let envelopeKey;
7727
7929
  try {
@@ -7785,7 +7987,7 @@ async function publishAddressedShare(options) {
7785
7987
  enforcementDelegation,
7786
7988
  contentSourceDigest
7787
7989
  });
7788
- const authorityMaterialDigest = await sha2563(fromBase64Url(enforcementDelegation.dagCbor));
7990
+ const authorityMaterialDigest = await sha2564(fromBase64Url(enforcementDelegation.dagCbor));
7789
7991
  const authorityTarget = {
7790
7992
  origin: options.nodeOrigin,
7791
7993
  nodeAudience: options.nodeAudience,
@@ -7885,7 +8087,7 @@ async function publishAddressedShare(options) {
7885
8087
  }
7886
8088
 
7887
8089
  // src/policy-authority.ts
7888
- var import_ed255195 = require("@noble/curves/ed25519");
8090
+ var import_ed255196 = require("@noble/curves/ed25519");
7889
8091
  var ENVELOPE_DOMAIN2 = "xyz.tinycloud.share/envelope/v2\0";
7890
8092
  function record(value, keys, label) {
7891
8093
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
@@ -7941,7 +8143,7 @@ async function verifyEnforcement(delegation) {
7941
8143
  });
7942
8144
  if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
7943
8145
  const signature = fromBase64Url(delegation.signature);
7944
- return signature.length === 64 && import_ed255195.ed25519.verify(
8146
+ return signature.length === 64 && import_ed255196.ed25519.verify(
7945
8147
  signature,
7946
8148
  dagCbor,
7947
8149
  ed25519PublicKeyFromDidKey(delegation.issuerDid),
@@ -7965,7 +8167,7 @@ async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerD
7965
8167
  if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
7966
8168
  const unsigned = { ...identity, envelopeCid, shareCid };
7967
8169
  const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
7968
- if (signatureBytes.length !== 64 || !import_ed255195.ed25519.verify(
8170
+ if (signatureBytes.length !== 64 || !import_ed255196.ed25519.verify(
7969
8171
  signatureBytes,
7970
8172
  new TextEncoder().encode(`${ENVELOPE_DOMAIN2}${canonicalize(unsigned)}`),
7971
8173
  ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
@@ -8032,13 +8234,13 @@ function createRegisteredPolicyAuthority(options) {
8032
8234
  }
8033
8235
 
8034
8236
  // src/cache.ts
8035
- var import_sha2562 = require("@noble/hashes/sha256");
8237
+ var import_sha2563 = require("@noble/hashes/sha256");
8036
8238
  function cacheKey(key) {
8037
8239
  if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
8038
8240
  return `${key.cid}\0${key.expiresAt}`;
8039
8241
  }
8040
8242
  function digest2(bytes3) {
8041
- return (0, import_sha2562.sha256)(bytes3);
8243
+ return (0, import_sha2563.sha256)(bytes3);
8042
8244
  }
8043
8245
  function equalBytes(left, right) {
8044
8246
  if (left.byteLength !== right.byteLength) return false;
@@ -8093,7 +8295,7 @@ var MemoryShareCache = class {
8093
8295
  };
8094
8296
 
8095
8297
  // src/delivery.ts
8096
- var import_ed255196 = require("@noble/curves/ed25519");
8298
+ var import_ed255197 = require("@noble/curves/ed25519");
8097
8299
  var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
8098
8300
  function isCanonicalHttpsOrigin2(value) {
8099
8301
  let url;
@@ -8185,7 +8387,7 @@ function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
8185
8387
  if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
8186
8388
  const signatureBytes = fromB642(proof.signature);
8187
8389
  const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
8188
- if (signatureBytes.length !== 64 || !import_ed255196.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
8390
+ if (signatureBytes.length !== 64 || !import_ed255197.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
8189
8391
  return { authorization, proof };
8190
8392
  }
8191
8393
  function shareDeliveryTrustedKid(publicKey) {
@@ -8484,7 +8686,8 @@ async function migrateShare(input) {
8484
8686
  }
8485
8687
 
8486
8688
  // src/recipient.ts
8487
- var import_ed255197 = require("@noble/curves/ed25519");
8689
+ var import_ed255198 = require("@noble/curves/ed25519");
8690
+ var import_sha2564 = require("@noble/hashes/sha256");
8488
8691
 
8489
8692
  // src/protocol.ts
8490
8693
  var SHARE_V2_PROTOCOL = Object.freeze({
@@ -8529,7 +8732,7 @@ var DOMAIN = SHARE_V2_PROTOCOL.challengeDomain;
8529
8732
  var PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
8530
8733
  var SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
8531
8734
  var INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
8532
- function object(value, label) {
8735
+ function object2(value, label) {
8533
8736
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
8534
8737
  return value;
8535
8738
  }
@@ -8539,6 +8742,42 @@ function bytes2(value, label) {
8539
8742
  if (decoded.length !== 64 || toBase64Url(decoded) !== value) throw new Error(`${label} is invalid`);
8540
8743
  return decoded;
8541
8744
  }
8745
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8746
+ function toBase64(bytes3) {
8747
+ let out = "";
8748
+ for (let i = 0; i < bytes3.length; i += 3) {
8749
+ const b0 = bytes3[i];
8750
+ const b1 = i + 1 < bytes3.length ? bytes3[i + 1] : 0;
8751
+ const b2 = i + 2 < bytes3.length ? bytes3[i + 2] : 0;
8752
+ out += BASE64_ALPHABET[b0 >> 2 & 63];
8753
+ out += BASE64_ALPHABET[(b0 << 4 | b1 >> 4) & 63];
8754
+ out += i + 1 < bytes3.length ? BASE64_ALPHABET[(b1 << 2 | b2 >> 6) & 63] : "=";
8755
+ out += i + 2 < bytes3.length ? BASE64_ALPHABET[b2 & 63] : "=";
8756
+ }
8757
+ return out;
8758
+ }
8759
+ function fromBase64(value, label) {
8760
+ if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
8761
+ throw new Error(`${label} is invalid`);
8762
+ }
8763
+ const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
8764
+ const out = new Uint8Array(value.length / 4 * 3 - padding);
8765
+ let outIdx = 0;
8766
+ for (let i = 0; i < value.length; i += 4) {
8767
+ const v0 = BASE64_ALPHABET.indexOf(value[i]);
8768
+ const v1 = BASE64_ALPHABET.indexOf(value[i + 1]);
8769
+ const v2 = value[i + 2] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 2]);
8770
+ const v3 = value[i + 3] === "=" ? 0 : BASE64_ALPHABET.indexOf(value[i + 3]);
8771
+ const b0 = v0 << 2 | v1 >> 4;
8772
+ const b1 = (v1 & 15) << 4 | v2 >> 2;
8773
+ const b2 = (v2 & 3) << 6 | v3;
8774
+ if (outIdx < out.length) out[outIdx++] = b0;
8775
+ if (outIdx < out.length) out[outIdx++] = b1;
8776
+ if (outIdx < out.length) out[outIdx++] = b2;
8777
+ }
8778
+ if (toBase64(out) !== value) throw new Error(`${label} is invalid`);
8779
+ return out;
8780
+ }
8542
8781
  async function digest3(value) {
8543
8782
  return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalize(value)))));
8544
8783
  }
@@ -8555,12 +8794,12 @@ function trustedPublicKey(value) {
8555
8794
  return value.invitationPublicKey;
8556
8795
  }
8557
8796
  async function verifyWrapped(value, key, domain, trust) {
8558
- const wrapper = object(value, `${key} response`);
8797
+ const wrapper = object2(value, `${key} response`);
8559
8798
  if (Object.keys(wrapper).length !== 2 || !Object.hasOwn(wrapper, key) || !Object.hasOwn(wrapper, "proof")) throw new Error(`${key} response is invalid`);
8560
- const artifact = object(wrapper[key], `${key} artifact`);
8561
- const proof = object(wrapper.proof, `${key} proof`);
8799
+ const artifact = object2(wrapper[key], `${key} artifact`);
8800
+ const proof = object2(wrapper.proof, `${key} proof`);
8562
8801
  if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error(`${key} proof is invalid`);
8563
- if (!import_ed255197.ed25519.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
8802
+ if (!import_ed255198.ed25519.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
8564
8803
  return artifact;
8565
8804
  }
8566
8805
  function nativeAction(action) {
@@ -8572,6 +8811,42 @@ function uiAction(action) {
8572
8811
  function selectedAction(envelope) {
8573
8812
  return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
8574
8813
  }
8814
+ function hex(value) {
8815
+ return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8816
+ }
8817
+ function canonicalHashHex(value) {
8818
+ return hex((0, import_sha2564.sha256)(new TextEncoder().encode(canonicalize(value))));
8819
+ }
8820
+ async function aesGcmDecrypt(key, blob) {
8821
+ if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
8822
+ const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
8823
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
8824
+ }
8825
+ async function aesGcmEncrypt(key, plaintext) {
8826
+ if (key.length !== 32) throw new Error("encrypted content key is malformed");
8827
+ const nonce = crypto.getRandomValues(new Uint8Array(12));
8828
+ const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
8829
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
8830
+ const output = new Uint8Array(nonce.length + ciphertext.length);
8831
+ output.set(nonce);
8832
+ output.set(ciphertext, nonce.length);
8833
+ return output;
8834
+ }
8835
+ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
8836
+ let value;
8837
+ try {
8838
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
8839
+ } catch {
8840
+ throw new Error("encrypted content envelope is malformed");
8841
+ }
8842
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
8843
+ const record2 = value;
8844
+ const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
8845
+ if (Object.keys(record2).some((key) => !allowed.includes(key)) || record2.v !== 1 || record2.networkId !== expected.encryptionNetwork || record2.alg !== "x25519-aes256gcm/v1" || record2.keyVersion !== expected.contentSource.keyVersion || typeof record2.encryptedSymmetricKey !== "string" || typeof record2.encryptedSymmetricKeyHash !== "string" || record2.encryptedSymmetricKeyHash !== expected.contentSource.encryptedSymmetricKeyDigestHex || record2.encryptedSymmetricKeyHash !== canonicalHashHex(record2.encryptedSymmetricKey) || typeof record2.ciphertext !== "string" || record2.metadata !== void 0 && (record2.metadata === null || typeof record2.metadata !== "object" || Array.isArray(record2.metadata) || Object.values(record2.metadata).some((entry) => typeof entry !== "string"))) {
8846
+ throw new Error("encrypted content envelope binding is invalid");
8847
+ }
8848
+ return record2;
8849
+ }
8575
8850
  async function verifyDetachedResponse(response, trust) {
8576
8851
  let value;
8577
8852
  try {
@@ -8579,12 +8854,12 @@ async function verifyDetachedResponse(response, trust) {
8579
8854
  } catch {
8580
8855
  throw new Error("share read response is invalid");
8581
8856
  }
8582
- const record2 = object(value, "share read response");
8583
- const proof = object(record2.proof, "share read detached proof");
8857
+ const record2 = object2(value, "share read response");
8858
+ const proof = object2(record2.proof, "share read detached proof");
8584
8859
  if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error("share read detached proof is invalid");
8585
8860
  const unsigned = { ...record2 };
8586
8861
  delete unsigned.proof;
8587
- if (!import_ed255197.ed25519.verify(bytes2(proof.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(trust))) throw new Error("share read detached proof is invalid");
8862
+ if (!import_ed255198.ed25519.verify(bytes2(proof.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(trust))) throw new Error("share read detached proof is invalid");
8588
8863
  }
8589
8864
  async function post(fetchFn, origin, path, body) {
8590
8865
  const response = await fetchFn(new URL(path, origin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify(body) });
@@ -8600,15 +8875,19 @@ var ShareRecipientClient = class {
8600
8875
  fetchFn;
8601
8876
  session;
8602
8877
  signer;
8878
+ nativeSigner;
8603
8879
  holderProof;
8880
+ v3Authorization;
8881
+ v3ContentKey;
8882
+ v3ContentEnvelope;
8604
8883
  async beginChallenge(envelope) {
8605
8884
  const authority = envelope.ownerAuthority;
8606
8885
  if (authority === void 0) throw new Error("addressed owner authority is required");
8607
- const outer = object(authority.outerEnvelope, "owner authority outer envelope");
8608
- const enforcement = object(authority.enforcementDelegation, "owner authority enforcement delegation");
8609
- const target = object(outer.target, "owner authority target");
8610
- const outerResource = object(outer.resource, "owner authority resource");
8611
- const source = object(outer.contentSource, "owner authority content source");
8886
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8887
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8888
+ const target = object2(outer.target, "owner authority target");
8889
+ const outerResource = object2(outer.resource, "owner authority resource");
8890
+ const source = object2(outer.contentSource, "owner authority content source");
8612
8891
  if (!Array.isArray(outer.actions) || outer.actions.length === 0 || outer.actions.some((value) => typeof value !== "string")) throw new Error("owner authority outer envelope actions are invalid");
8613
8892
  const actions = [...outer.actions];
8614
8893
  const action = selectedAction(envelope);
@@ -8621,11 +8900,11 @@ var ShareRecipientClient = class {
8621
8900
  async establish(envelope) {
8622
8901
  const authority = envelope.ownerAuthority;
8623
8902
  if (authority === void 0) throw new Error("addressed owner authority is required");
8624
- const outer = object(authority.outerEnvelope, "owner authority outer envelope");
8625
- const enforcement = object(authority.enforcementDelegation, "owner authority enforcement delegation");
8626
- const target = object(outer.target, "owner authority target");
8627
- const outerResource = object(outer.resource, "owner authority resource");
8628
- const source = object(outer.contentSource, "owner authority content source");
8903
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8904
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8905
+ const target = object2(outer.target, "owner authority target");
8906
+ const outerResource = object2(outer.resource, "owner authority resource");
8907
+ const source = object2(outer.contentSource, "owner authority content source");
8629
8908
  if (!Array.isArray(outer.actions) || outer.actions.length === 0 || outer.actions.some((value) => typeof value !== "string")) throw new Error("owner authority outer envelope actions are invalid");
8630
8909
  const actions = [...outer.actions];
8631
8910
  const action = selectedAction(envelope);
@@ -8637,6 +8916,7 @@ var ShareRecipientClient = class {
8637
8916
  const material = await this.options.buildPresentation({ challenge, envelope, policy: {} });
8638
8917
  this.holderProof = material.proof;
8639
8918
  this.signer = material.sign;
8919
+ this.nativeSigner = material.sign;
8640
8920
  if (this.signer === void 0) throw new Error("share holder signer is required");
8641
8921
  const presentation = {
8642
8922
  type: "TinyCloudSharePolicyPresentation",
@@ -8672,7 +8952,7 @@ var ShareRecipientClient = class {
8672
8952
  if (this.session === void 0) await this.establish(envelope);
8673
8953
  const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
8674
8954
  if (!response.ok) throw new Error("share recipient read was rejected");
8675
- const value = object(await response.json(), "share read response");
8955
+ const value = object2(await response.json(), "share read response");
8676
8956
  const expectedAction = nativeAction("read");
8677
8957
  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");
8678
8958
  const content = fromBase64Url(value.content);
@@ -8681,18 +8961,110 @@ var ShareRecipientClient = class {
8681
8961
  }
8682
8962
  async establishPolicySession() {
8683
8963
  if (this.session !== void 0) return this.session;
8684
- return this.establish(this.options.envelope);
8964
+ const envelope = this.options.envelope;
8965
+ if (envelope.version === 3) return this.establishV3(envelope);
8966
+ return this.establish(envelope);
8967
+ }
8968
+ async establishV3(envelope) {
8969
+ if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
8970
+ const challengeResponse = await this.fetchFn(new URL("/share/v3/policy/challenges", this.options.nodeOrigin), {
8971
+ method: "POST",
8972
+ redirect: "error",
8973
+ headers: { accept: "application/json", "content-type": "application/json" },
8974
+ body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling })
8975
+ });
8976
+ if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
8977
+ const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
8978
+ if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid) throw new Error("v3 policy challenge binding mismatch");
8979
+ const material = await this.options.buildPresentation({
8980
+ challenge,
8981
+ envelope,
8982
+ policy: envelope.policy
8983
+ });
8984
+ if (material.sign === void 0 || material.claim === void 0 || material.presentation === void 0) throw new Error("v3 ceremony requires a recipient signer, claim, and presentation");
8985
+ const delegationResponse = await this.fetchFn(new URL("/share/v3/policy/delegations", this.options.nodeOrigin), {
8986
+ method: "POST",
8987
+ redirect: "error",
8988
+ headers: { accept: "application/json", "content-type": "application/json" },
8989
+ body: JSON.stringify({ policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation })
8990
+ });
8991
+ if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8992
+ const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
8993
+ if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
8994
+ const compact = verifyCompactUcanAuthorization(delegation.authorization, delegation.sessionCid);
8995
+ const fact = compact.payload.fct[0];
8996
+ if (compact.payload.aud !== this.options.holderDid || fact.profile !== "policy-session-ucan/v1" || fact.policyCid !== envelope.policyCid || fact.recipientDid !== this.options.holderDid || fact.policyDelegationCid !== envelope.policyRoot.cid || fact.enforcementDelegationCid !== envelope.enforcementRoot.cid || compact.payload.prf.length !== 2 || compact.payload.prf[0] !== envelope.policyRoot.cid || compact.payload.prf[1] !== envelope.enforcementRoot.cid || compact.payload.exp - compact.payload.nbf > 60) throw new Error("v3 policy delegation signed binding mismatch");
8997
+ const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization } });
8998
+ if (!imported.ok) throw new Error(`ordinary delegation import rejected (${imported.status})`);
8999
+ this.signer = material.sign;
9000
+ this.v3Authorization = delegation.authorization;
9001
+ this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
9002
+ return this.session;
9003
+ }
9004
+ /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
9005
+ async decryptV3Content(bytes3) {
9006
+ const envelope = this.options.envelope;
9007
+ const signer = this.nativeSigner ?? this.signer;
9008
+ if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
9009
+ const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
9010
+ const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
9011
+ const receiverPublicKey = toBase64Url(import_ed255198.x25519.getPublicKey(receiverPrivateKey));
9012
+ const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
9013
+ const body = { type: "tinycloud.encryption.decrypt/v1", targetNode: envelope.target.nodeAudience, networkId: encrypted.networkId, alg: encrypted.alg, keyVersion: encrypted.keyVersion, encryptedSymmetricKey: encrypted.encryptedSymmetricKey, encryptedSymmetricKeyHash: encrypted.encryptedSymmetricKeyHash, receiverPublicKey, receiverPublicKeyHash };
9014
+ const bodyHash = hex((0, import_sha2564.sha256)(new TextEncoder().encode(canonicalize(body))));
9015
+ const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
9016
+ const now = Math.floor(Date.now() / 1e3);
9017
+ const invocation = await signCompactUcanAuthorization({
9018
+ issuerDid: this.options.holderDid,
9019
+ audienceDid: envelope.target.nodeAudience,
9020
+ attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
9021
+ facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
9022
+ proofs: [session.cid],
9023
+ notBefore: now,
9024
+ expiresAt: Math.min(now + 60, session.payload.exp),
9025
+ nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))),
9026
+ sign: signer
9027
+ });
9028
+ try {
9029
+ 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) });
9030
+ if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
9031
+ const value = object2(await response.json(), "v3 decrypt response");
9032
+ const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
9033
+ if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex((0, import_sha2564.sha256)(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
9034
+ const unsigned = { ...value };
9035
+ delete unsigned.nodeSignature;
9036
+ const signature = fromBase64(value.nodeSignature, "v3 decrypt response signature");
9037
+ if (signature.length !== 64 || !import_ed255198.ed25519.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
9038
+ const wrapped = fromBase64(value.wrappedKey, "v3 wrapped content key");
9039
+ if (wrapped.length < 60) throw new Error("v3 wrapped content key is malformed");
9040
+ const shared = import_ed255198.x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
9041
+ const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(32));
9042
+ shared.fill(0);
9043
+ if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
9044
+ const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64Url(encrypted.ciphertext));
9045
+ this.v3ContentKey?.fill(0);
9046
+ this.v3ContentKey = symmetricKey;
9047
+ this.v3ContentEnvelope = encrypted;
9048
+ return { bytes: plaintext, mediaType: encrypted.metadata?.contentType ?? envelope.metadata.mediaType ?? "application/octet-stream" };
9049
+ } finally {
9050
+ receiverPrivateKey.fill(0);
9051
+ }
9052
+ }
9053
+ /** Re-encrypt edited v3 content with the admitted content key. */
9054
+ async encryptV3Content(bytes3, mediaType) {
9055
+ 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");
9056
+ return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
8685
9057
  }
8686
9058
  async resumeWithProof(envelope, resumeToken, proof) {
8687
- const material = object(proof, "share authorization proof");
8688
- const presentation = object(material.presentation, "share presentation");
8689
- const presentationProof = object(material.presentationProof, "share presentation proof");
9059
+ const material = object2(proof, "share authorization proof");
9060
+ const presentation = object2(material.presentation, "share presentation");
9061
+ const presentationProof = object2(material.presentationProof, "share presentation proof");
8690
9062
  const nonce = material.nonce;
8691
9063
  const credential = material.credential;
8692
9064
  const holderDid = material.holderDid;
8693
9065
  const holderBinding = material.holderBinding;
8694
9066
  if (typeof nonce !== "string" || typeof credential !== "string" || typeof holderDid !== "string" || holderDid !== this.options.holderDid || typeof holderBinding !== "object" || holderBinding === null || presentationProof.alg !== "EdDSA" || typeof presentationProof.signature !== "string") throw new Error("share authorization proof is incomplete");
8695
- const value = object(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/session", { challengeId: resumeToken, nonce, presentation, credential, proof: presentationProof, holderBinding, readSignerDid: holderDid }), "share policy session");
9067
+ const value = object2(await post(this.fetchFn, this.options.nodeOrigin, "/share/v2/policy/session", { challengeId: resumeToken, nonce, presentation, credential, proof: presentationProof, holderBinding, readSignerDid: holderDid }), "share policy session");
8696
9068
  const session = await verifyWrapped(value, "session", SESSION_DOMAIN, this.options.trustedNode);
8697
9069
  const authority = envelope.ownerAuthority;
8698
9070
  if (authority === void 0 || session.type !== "TinyCloudSharePolicySession" || session.version !== 2 || typeof session.sessionId !== "string" || session.shareCid !== authority.shareCid || session.shareId !== envelope.shareId || session.registrationCid !== authority.registrationCid || session.envelopeCid !== authority.envelopeCid || session.delegationCid !== envelope.delegationCid || typeof session.resource !== "string" || typeof session.expiresAt !== "string") throw new Error("share authority returned an unbound session");
@@ -8706,27 +9078,61 @@ var ShareRecipientClient = class {
8706
9078
  }
8707
9079
  async nativeInvoke(request) {
8708
9080
  if (this.session === void 0) throw new Error("share policy session is required");
8709
- if (this.options.envelope.ownerAuthority === void 0 || this.signer === void 0 || this.holderProof === void 0) throw new Error("share holder signer is required");
8710
- const authority = this.options.envelope.ownerAuthority;
9081
+ const envelope = this.options.envelope;
9082
+ if (envelope.version === 3) return this.nativeInvokeV3(request, envelope);
9083
+ if (envelope.ownerAuthority === void 0 || this.signer === void 0 || this.holderProof === void 0) throw new Error("share holder signer is required");
9084
+ const authority = envelope.ownerAuthority;
8711
9085
  const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
8712
9086
  const resource = typeof request.resource?.path === "string" ? request.resource.path : this.session.resource.path;
8713
9087
  const actions = [...new Set(this.session.actions.map(nativeAction).concat(action === "tinycloud.kv/metadata" ? [action] : []))].sort();
8714
9088
  const bodyBytes = request.body === void 0 ? void 0 : Uint8Array.from(request.body);
8715
9089
  const bodyDigest = bodyBytes === void 0 ? void 0 : toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bodyBytes)));
8716
- const outer = object(authority.outerEnvelope, "owner authority outer envelope");
8717
- const enforcement = object(authority.enforcementDelegation, "owner authority enforcement delegation");
8718
- const invocationBase = { type: "TinyCloudShareReadInvocation", version: 2, sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: this.options.envelope.shareId, registrationCid: authority.registrationCid, delegationCid: this.options.envelope.delegationCid, policyCid: this.options.envelope.authorizationTarget.kind === "policy" ? this.options.envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, targetOrigin: String(object(outer.target, "owner authority target").origin), nodeAudience: String(object(outer.target, "owner authority target").nodeAudience), action, actions, resource, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyDigest === void 0 ? {} : { bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType }, issuedAt: (/* @__PURE__ */ new Date()).toISOString(), expiresAt: new Date(Math.min(Date.now() + 6e4, Date.parse(this.session.expiresAt))).toISOString(), jti: toBase64Url(crypto.getRandomValues(new Uint8Array(16))) };
9090
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
9091
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
9092
+ const invocationBase = { type: "TinyCloudShareReadInvocation", version: 2, sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, targetOrigin: String(object2(outer.target, "owner authority target").origin), nodeAudience: String(object2(outer.target, "owner authority target").nodeAudience), action, actions, resource, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyDigest === void 0 ? {} : { bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType }, issuedAt: (/* @__PURE__ */ new Date()).toISOString(), expiresAt: new Date(Math.min(Date.now() + 6e4, Date.parse(this.session.expiresAt))).toISOString(), jti: toBase64Url(crypto.getRandomValues(new Uint8Array(16))) };
8719
9093
  const requestBodyDigest = await digest3(invocationBase);
8720
9094
  const invocation = { ...invocationBase, requestBodyDigest };
8721
9095
  const proof = { ...this.holderProof, signature: toBase64Url(await this.signer(new TextEncoder().encode(`${INVOCATION_DOMAIN}${canonicalize(invocation)}`))) };
8722
- const signedRequest = { sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: this.options.envelope.shareId, registrationCid: authority.registrationCid, delegationCid: this.options.envelope.delegationCid, policyCid: this.options.envelope.authorizationTarget.kind === "policy" ? this.options.envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, nodeAudience: String(object(outer.target, "owner authority target").nodeAudience), action, actions, resource, requestBodyDigest, invocation, proof };
9096
+ const signedRequest = { sessionId: this.session.sessionId, envelopeCid: authority.envelopeCid, shareCid: authority.shareCid, shareId: envelope.shareId, registrationCid: authority.registrationCid, delegationCid: envelope.delegationCid, policyCid: envelope.authorizationTarget.kind === "policy" ? envelope.authorizationTarget.policyCid : "", enforcementDelegationCid: String(enforcement.cid), contentSource: outer.contentSource, contentSourceDigest: String(outer.contentSourceDigest), holderDid: this.options.holderDid, nodeAudience: String(object2(outer.target, "owner authority target").nodeAudience), action, actions, resource, requestBodyDigest, invocation, proof };
8723
9097
  const response = await this.fetchFn(new URL("/share/v2/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/vnd.tinycloud.share+json", "content-type": "application/vnd.tinycloud.share+json" }, body: JSON.stringify({ request: signedRequest, ...action === "tinycloud.kv/list" ? { limit: 100 } : {}, ...bodyBytes === void 0 ? {} : { body: toBase64Url(bodyBytes), bodyDigest, ifMatch: request.ifMatch, contentType: request.contentType } }) });
8724
9098
  if (response.ok) await verifyDetachedResponse(response, this.options.trustedNode);
8725
9099
  return response;
8726
9100
  }
9101
+ async nativeInvokeV3(request, envelope) {
9102
+ const signer = this.nativeSigner ?? this.signer;
9103
+ if (this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
9104
+ const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
9105
+ if (session.payload.aud !== this.options.holderDid) throw new Error("v3 session recipient mismatch");
9106
+ const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
9107
+ const path = typeof request.resource?.path === "string" ? request.resource.path.replace(/^\//, "").replace(/\/$/, "") : envelope.resource.path;
9108
+ const capability = envelope.policy.capabilityCeiling.find((candidate) => candidate.kind === "kv");
9109
+ if (capability === void 0 || !capability.actions.includes(action)) throw new Error("v3 requested capability is not in the signed policy");
9110
+ const marker = "/kv/";
9111
+ const split = capability.resource.indexOf(marker);
9112
+ if (split < 0) throw new Error("v3 KV resource is invalid");
9113
+ const root = capability.resource.slice(split + marker.length).replace(/\/$/, "");
9114
+ if (path !== root && !(capability.selector === "prefix" && path.startsWith(`${root}/`))) throw new Error("v3 KV request is outside the signed selector");
9115
+ const resource = `${capability.resource.slice(0, split + marker.length)}${path}`;
9116
+ const now = Math.floor(Date.now() / 1e3);
9117
+ const invocation = await signCompactUcanAuthorization({ issuerDid: this.options.holderDid, audienceDid: envelope.target.nodeAudience, attenuation: { [resource]: { [action]: [{ type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: resource }] } }, facts: [{ type: "tinycloud.policy.invocation/v1", policyCid: envelope.policyCid, sessionCid: session.cid }], proofs: [session.cid], notBefore: now, expiresAt: Math.min(now + 60, session.payload.exp), nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))), sign: signer });
9118
+ const headers = new Headers({ accept: "application/json", Authorization: invocation.authorization });
9119
+ let body;
9120
+ if (action === "tinycloud.kv/put") {
9121
+ if (request.body === void 0) throw new Error("v3 KV put requires bytes");
9122
+ body = Uint8Array.from(request.body);
9123
+ headers.set("content-type", request.contentType ?? "application/octet-stream");
9124
+ if (request.ifMatch !== void 0) headers.set("if-match", request.ifMatch);
9125
+ }
9126
+ return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body } });
9127
+ }
8727
9128
  };
8728
9129
  function createAddressedAuthorization(input) {
8729
- const client = (envelope) => new ShareRecipientClient({ ...input, envelope });
9130
+ const { buildPresentation, ...options } = input;
9131
+ const client = (envelope) => new ShareRecipientClient({
9132
+ ...options,
9133
+ envelope,
9134
+ ...buildPresentation === void 0 ? {} : { buildPresentation: ({ challenge, envelope: candidate, policy }) => buildPresentation({ challenge, envelope: candidate, policy }) }
9135
+ });
8730
9136
  return {
8731
9137
  async begin({ envelope, method }) {
8732
9138
  const current = client(envelope);
@@ -8740,15 +9146,15 @@ function createAddressedAuthorization(input) {
8740
9146
  },
8741
9147
  async verifyResult({ envelope, value, proof }) {
8742
9148
  try {
8743
- const wrapper = object(proof, "share read proof");
8744
- const response = object(wrapper.response, "share read response proof");
9149
+ const wrapper = object2(proof, "share read proof");
9150
+ const response = object2(wrapper.response, "share read response proof");
8745
9151
  const envelopeAction = nativeAction("read");
8746
9152
  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;
8747
- const detached = object(wrapper.detached, "share read detached proof");
9153
+ const detached = object2(wrapper.detached, "share read detached proof");
8748
9154
  if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
8749
9155
  const unsigned = { ...response };
8750
9156
  delete unsigned.proof;
8751
- return import_ed255197.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
9157
+ return import_ed255198.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
8752
9158
  } catch {
8753
9159
  return false;
8754
9160
  }