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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }
@@ -8555,12 +8758,12 @@ function trustedPublicKey(value) {
8555
8758
  return value.invitationPublicKey;
8556
8759
  }
8557
8760
  async function verifyWrapped(value, key, domain, trust) {
8558
- const wrapper = object(value, `${key} response`);
8761
+ const wrapper = object2(value, `${key} response`);
8559
8762
  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`);
8763
+ const artifact = object2(wrapper[key], `${key} artifact`);
8764
+ const proof = object2(wrapper.proof, `${key} proof`);
8562
8765
  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`);
8766
+ 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
8767
  return artifact;
8565
8768
  }
8566
8769
  function nativeAction(action) {
@@ -8572,6 +8775,42 @@ function uiAction(action) {
8572
8775
  function selectedAction(envelope) {
8573
8776
  return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
8574
8777
  }
8778
+ function hex(value) {
8779
+ return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8780
+ }
8781
+ function canonicalHashHex(value) {
8782
+ return hex((0, import_sha2564.sha256)(new TextEncoder().encode(canonicalize(value))));
8783
+ }
8784
+ async function aesGcmDecrypt(key, blob) {
8785
+ if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
8786
+ const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
8787
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
8788
+ }
8789
+ async function aesGcmEncrypt(key, plaintext) {
8790
+ if (key.length !== 32) throw new Error("encrypted content key is malformed");
8791
+ const nonce = crypto.getRandomValues(new Uint8Array(12));
8792
+ const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
8793
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
8794
+ const output = new Uint8Array(nonce.length + ciphertext.length);
8795
+ output.set(nonce);
8796
+ output.set(ciphertext, nonce.length);
8797
+ return output;
8798
+ }
8799
+ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
8800
+ let value;
8801
+ try {
8802
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
8803
+ } catch {
8804
+ throw new Error("encrypted content envelope is malformed");
8805
+ }
8806
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
8807
+ const record2 = value;
8808
+ const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
8809
+ 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"))) {
8810
+ throw new Error("encrypted content envelope binding is invalid");
8811
+ }
8812
+ return record2;
8813
+ }
8575
8814
  async function verifyDetachedResponse(response, trust) {
8576
8815
  let value;
8577
8816
  try {
@@ -8579,12 +8818,12 @@ async function verifyDetachedResponse(response, trust) {
8579
8818
  } catch {
8580
8819
  throw new Error("share read response is invalid");
8581
8820
  }
8582
- const record2 = object(value, "share read response");
8583
- const proof = object(record2.proof, "share read detached proof");
8821
+ const record2 = object2(value, "share read response");
8822
+ const proof = object2(record2.proof, "share read detached proof");
8584
8823
  if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error("share read detached proof is invalid");
8585
8824
  const unsigned = { ...record2 };
8586
8825
  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");
8826
+ 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
8827
  }
8589
8828
  async function post(fetchFn, origin, path, body) {
8590
8829
  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 +8839,19 @@ var ShareRecipientClient = class {
8600
8839
  fetchFn;
8601
8840
  session;
8602
8841
  signer;
8842
+ nativeSigner;
8603
8843
  holderProof;
8844
+ v3Authorization;
8845
+ v3ContentKey;
8846
+ v3ContentEnvelope;
8604
8847
  async beginChallenge(envelope) {
8605
8848
  const authority = envelope.ownerAuthority;
8606
8849
  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");
8850
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8851
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8852
+ const target = object2(outer.target, "owner authority target");
8853
+ const outerResource = object2(outer.resource, "owner authority resource");
8854
+ const source = object2(outer.contentSource, "owner authority content source");
8612
8855
  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
8856
  const actions = [...outer.actions];
8614
8857
  const action = selectedAction(envelope);
@@ -8621,11 +8864,11 @@ var ShareRecipientClient = class {
8621
8864
  async establish(envelope) {
8622
8865
  const authority = envelope.ownerAuthority;
8623
8866
  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");
8867
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8868
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8869
+ const target = object2(outer.target, "owner authority target");
8870
+ const outerResource = object2(outer.resource, "owner authority resource");
8871
+ const source = object2(outer.contentSource, "owner authority content source");
8629
8872
  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
8873
  const actions = [...outer.actions];
8631
8874
  const action = selectedAction(envelope);
@@ -8637,6 +8880,7 @@ var ShareRecipientClient = class {
8637
8880
  const material = await this.options.buildPresentation({ challenge, envelope, policy: {} });
8638
8881
  this.holderProof = material.proof;
8639
8882
  this.signer = material.sign;
8883
+ this.nativeSigner = material.sign;
8640
8884
  if (this.signer === void 0) throw new Error("share holder signer is required");
8641
8885
  const presentation = {
8642
8886
  type: "TinyCloudSharePolicyPresentation",
@@ -8672,7 +8916,7 @@ var ShareRecipientClient = class {
8672
8916
  if (this.session === void 0) await this.establish(envelope);
8673
8917
  const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
8674
8918
  if (!response.ok) throw new Error("share recipient read was rejected");
8675
- const value = object(await response.json(), "share read response");
8919
+ const value = object2(await response.json(), "share read response");
8676
8920
  const expectedAction = nativeAction("read");
8677
8921
  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
8922
  const content = fromBase64Url(value.content);
@@ -8681,18 +8925,110 @@ var ShareRecipientClient = class {
8681
8925
  }
8682
8926
  async establishPolicySession() {
8683
8927
  if (this.session !== void 0) return this.session;
8684
- return this.establish(this.options.envelope);
8928
+ const envelope = this.options.envelope;
8929
+ if (envelope.version === 3) return this.establishV3(envelope);
8930
+ return this.establish(envelope);
8931
+ }
8932
+ async establishV3(envelope) {
8933
+ if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
8934
+ const challengeResponse = await this.fetchFn(new URL("/share/v3/policy/challenges", this.options.nodeOrigin), {
8935
+ method: "POST",
8936
+ redirect: "error",
8937
+ headers: { accept: "application/json", "content-type": "application/json" },
8938
+ body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling })
8939
+ });
8940
+ if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
8941
+ const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
8942
+ 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");
8943
+ const material = await this.options.buildPresentation({
8944
+ challenge,
8945
+ envelope,
8946
+ policy: envelope.policy
8947
+ });
8948
+ 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");
8949
+ const delegationResponse = await this.fetchFn(new URL("/share/v3/policy/delegations", this.options.nodeOrigin), {
8950
+ method: "POST",
8951
+ redirect: "error",
8952
+ headers: { accept: "application/json", "content-type": "application/json" },
8953
+ body: JSON.stringify({ policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation })
8954
+ });
8955
+ if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8956
+ const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
8957
+ if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
8958
+ const compact = verifyCompactUcanAuthorization(delegation.authorization, delegation.sessionCid);
8959
+ const fact = compact.payload.fct[0];
8960
+ 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");
8961
+ const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization } });
8962
+ if (!imported.ok) throw new Error(`ordinary delegation import rejected (${imported.status})`);
8963
+ this.signer = material.sign;
8964
+ this.v3Authorization = delegation.authorization;
8965
+ this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
8966
+ return this.session;
8967
+ }
8968
+ /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
8969
+ async decryptV3Content(bytes3) {
8970
+ const envelope = this.options.envelope;
8971
+ const signer = this.nativeSigner ?? this.signer;
8972
+ if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
8973
+ const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
8974
+ const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
8975
+ const receiverPublicKey = toBase64Url(import_ed255198.x25519.getPublicKey(receiverPrivateKey));
8976
+ const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
8977
+ 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 };
8978
+ const bodyHash = hex((0, import_sha2564.sha256)(new TextEncoder().encode(canonicalize(body))));
8979
+ const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
8980
+ const now = Math.floor(Date.now() / 1e3);
8981
+ const invocation = await signCompactUcanAuthorization({
8982
+ issuerDid: this.options.holderDid,
8983
+ audienceDid: envelope.target.nodeAudience,
8984
+ attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
8985
+ facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
8986
+ proofs: [session.cid],
8987
+ notBefore: now,
8988
+ expiresAt: Math.min(now + 60, session.payload.exp),
8989
+ nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))),
8990
+ sign: signer
8991
+ });
8992
+ try {
8993
+ 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) });
8994
+ if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
8995
+ const value = object2(await response.json(), "v3 decrypt response");
8996
+ const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
8997
+ 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");
8998
+ const unsigned = { ...value };
8999
+ delete unsigned.nodeSignature;
9000
+ const signature = fromBase64Url(value.nodeSignature);
9001
+ 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");
9002
+ const wrapped = fromBase64Url(value.wrappedKey);
9003
+ if (wrapped.length < 60) throw new Error("v3 wrapped content key is malformed");
9004
+ const shared = import_ed255198.x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
9005
+ const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(32));
9006
+ shared.fill(0);
9007
+ if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
9008
+ const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64Url(encrypted.ciphertext));
9009
+ this.v3ContentKey?.fill(0);
9010
+ this.v3ContentKey = symmetricKey;
9011
+ this.v3ContentEnvelope = encrypted;
9012
+ return { bytes: plaintext, mediaType: encrypted.metadata?.contentType ?? envelope.metadata.mediaType ?? "application/octet-stream" };
9013
+ } finally {
9014
+ receiverPrivateKey.fill(0);
9015
+ }
9016
+ }
9017
+ /** Re-encrypt edited v3 content with the admitted content key. */
9018
+ async encryptV3Content(bytes3, mediaType) {
9019
+ 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");
9020
+ return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
8685
9021
  }
8686
9022
  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");
9023
+ const material = object2(proof, "share authorization proof");
9024
+ const presentation = object2(material.presentation, "share presentation");
9025
+ const presentationProof = object2(material.presentationProof, "share presentation proof");
8690
9026
  const nonce = material.nonce;
8691
9027
  const credential = material.credential;
8692
9028
  const holderDid = material.holderDid;
8693
9029
  const holderBinding = material.holderBinding;
8694
9030
  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");
9031
+ 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
9032
  const session = await verifyWrapped(value, "session", SESSION_DOMAIN, this.options.trustedNode);
8697
9033
  const authority = envelope.ownerAuthority;
8698
9034
  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 +9042,61 @@ var ShareRecipientClient = class {
8706
9042
  }
8707
9043
  async nativeInvoke(request) {
8708
9044
  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;
9045
+ const envelope = this.options.envelope;
9046
+ if (envelope.version === 3) return this.nativeInvokeV3(request, envelope);
9047
+ if (envelope.ownerAuthority === void 0 || this.signer === void 0 || this.holderProof === void 0) throw new Error("share holder signer is required");
9048
+ const authority = envelope.ownerAuthority;
8711
9049
  const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
8712
9050
  const resource = typeof request.resource?.path === "string" ? request.resource.path : this.session.resource.path;
8713
9051
  const actions = [...new Set(this.session.actions.map(nativeAction).concat(action === "tinycloud.kv/metadata" ? [action] : []))].sort();
8714
9052
  const bodyBytes = request.body === void 0 ? void 0 : Uint8Array.from(request.body);
8715
9053
  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))) };
9054
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
9055
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
9056
+ 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
9057
  const requestBodyDigest = await digest3(invocationBase);
8720
9058
  const invocation = { ...invocationBase, requestBodyDigest };
8721
9059
  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 };
9060
+ 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
9061
  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
9062
  if (response.ok) await verifyDetachedResponse(response, this.options.trustedNode);
8725
9063
  return response;
8726
9064
  }
9065
+ async nativeInvokeV3(request, envelope) {
9066
+ const signer = this.nativeSigner ?? this.signer;
9067
+ if (this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
9068
+ const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
9069
+ if (session.payload.aud !== this.options.holderDid) throw new Error("v3 session recipient mismatch");
9070
+ const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
9071
+ const path = typeof request.resource?.path === "string" ? request.resource.path.replace(/^\//, "").replace(/\/$/, "") : envelope.resource.path;
9072
+ const capability = envelope.policy.capabilityCeiling.find((candidate) => candidate.kind === "kv");
9073
+ if (capability === void 0 || !capability.actions.includes(action)) throw new Error("v3 requested capability is not in the signed policy");
9074
+ const marker = "/kv/";
9075
+ const split = capability.resource.indexOf(marker);
9076
+ if (split < 0) throw new Error("v3 KV resource is invalid");
9077
+ const root = capability.resource.slice(split + marker.length).replace(/\/$/, "");
9078
+ if (path !== root && !(capability.selector === "prefix" && path.startsWith(`${root}/`))) throw new Error("v3 KV request is outside the signed selector");
9079
+ const resource = `${capability.resource.slice(0, split + marker.length)}${path}`;
9080
+ const now = Math.floor(Date.now() / 1e3);
9081
+ 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 });
9082
+ const headers = new Headers({ accept: "application/json", Authorization: invocation.authorization });
9083
+ let body;
9084
+ if (action === "tinycloud.kv/put") {
9085
+ if (request.body === void 0) throw new Error("v3 KV put requires bytes");
9086
+ body = Uint8Array.from(request.body);
9087
+ headers.set("content-type", request.contentType ?? "application/octet-stream");
9088
+ if (request.ifMatch !== void 0) headers.set("if-match", request.ifMatch);
9089
+ }
9090
+ return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body } });
9091
+ }
8727
9092
  };
8728
9093
  function createAddressedAuthorization(input) {
8729
- const client = (envelope) => new ShareRecipientClient({ ...input, envelope });
9094
+ const { buildPresentation, ...options } = input;
9095
+ const client = (envelope) => new ShareRecipientClient({
9096
+ ...options,
9097
+ envelope,
9098
+ ...buildPresentation === void 0 ? {} : { buildPresentation: ({ challenge, envelope: candidate, policy }) => buildPresentation({ challenge, envelope: candidate, policy }) }
9099
+ });
8730
9100
  return {
8731
9101
  async begin({ envelope, method }) {
8732
9102
  const current = client(envelope);
@@ -8740,15 +9110,15 @@ function createAddressedAuthorization(input) {
8740
9110
  },
8741
9111
  async verifyResult({ envelope, value, proof }) {
8742
9112
  try {
8743
- const wrapper = object(proof, "share read proof");
8744
- const response = object(wrapper.response, "share read response proof");
9113
+ const wrapper = object2(proof, "share read proof");
9114
+ const response = object2(wrapper.response, "share read response proof");
8745
9115
  const envelopeAction = nativeAction("read");
8746
9116
  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");
9117
+ const detached = object2(wrapper.detached, "share read detached proof");
8748
9118
  if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
8749
9119
  const unsigned = { ...response };
8750
9120
  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));
9121
+ return import_ed255198.ed25519.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
8752
9122
  } catch {
8753
9123
  return false;
8754
9124
  }