@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.js CHANGED
@@ -152,10 +152,10 @@ var util;
152
152
  return obj[e];
153
153
  });
154
154
  };
155
- util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object2) => {
155
+ util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object3) => {
156
156
  const keys = [];
157
- for (const key in object2) {
158
- if (Object.prototype.hasOwnProperty.call(object2, key)) {
157
+ for (const key in object3) {
158
+ if (Object.prototype.hasOwnProperty.call(object3, key)) {
159
159
  keys.push(key);
160
160
  }
161
161
  }
@@ -4051,6 +4051,9 @@ var NEVER = INVALID;
4051
4051
 
4052
4052
  // ../share-envelope/dist/index.js
4053
4053
  import { ed25519 as ed255192 } from "@noble/curves/ed25519";
4054
+ import { blake3 } from "@noble/hashes/blake3";
4055
+ import { ed25519 as ed255193 } from "@noble/curves/ed25519";
4056
+ import { sha256 as sha2562 } from "@noble/hashes/sha256";
4054
4057
  var empty = new Uint8Array(0);
4055
4058
  function equals(aa, bb) {
4056
4059
  if (aa === bb) {
@@ -5223,6 +5226,128 @@ function validateV2Invariants(value, ctx) {
5223
5226
  }
5224
5227
  var unsignedShareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.superRefine(validateV2Invariants);
5225
5228
  var shareEnvelopeV2Schema = unsignedShareEnvelopeV2BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV2Invariants);
5229
+ var unifiedResourceSchema = external_exports.string().refine((value) => {
5230
+ const match = /^tinycloud:\/\/([^/]+)\/kv\/(.+)$/.exec(value);
5231
+ if (match === null || /[:?#%]/.test(match[1])) return false;
5232
+ const path = match[2];
5233
+ return !path.startsWith("/") && !path.endsWith("/") && !path.includes("//") && path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
5234
+ }, { message: "expected canonical TinyCloud KV resource" });
5235
+ var unifiedEncryptionNetworkSchema = external_exports.string().refine((value) => {
5236
+ if (!value.startsWith("urn:tinycloud:encryption:")) return false;
5237
+ const rest = value.slice("urn:tinycloud:encryption:".length);
5238
+ const separator = rest.lastIndexOf(":");
5239
+ const owner = separator < 0 ? "" : rest.slice(0, separator);
5240
+ const network = separator < 0 ? "" : rest.slice(separator + 1);
5241
+ return owner.startsWith("did:") && owner.length > 4 && network.length > 0 && !/[:/%?#\s]/.test(network);
5242
+ }, { message: "expected canonical TinyCloud encryption network" });
5243
+ var unifiedKvCapabilitySchema = external_exports.object({
5244
+ kind: external_exports.literal("kv"),
5245
+ resource: unifiedResourceSchema,
5246
+ selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5247
+ actions: external_exports.array(external_exports.union([
5248
+ external_exports.literal("tinycloud.kv/get"),
5249
+ external_exports.literal("tinycloud.kv/list"),
5250
+ external_exports.literal("tinycloud.kv/metadata"),
5251
+ external_exports.literal("tinycloud.kv/put")
5252
+ ])).min(1)
5253
+ }).strict();
5254
+ var unifiedEncryptionCapabilitySchema = external_exports.object({
5255
+ kind: external_exports.literal("encryption"),
5256
+ resource: unifiedEncryptionNetworkSchema,
5257
+ action: external_exports.literal("tinycloud.encryption/decrypt")
5258
+ }).strict();
5259
+ var unifiedCapabilitySchema = external_exports.union([unifiedKvCapabilitySchema, unifiedEncryptionCapabilitySchema]);
5260
+ var unifiedContentSourceSchema = external_exports.object({
5261
+ shareId: external_exports.string().min(1),
5262
+ kvResource: unifiedResourceSchema,
5263
+ selector: external_exports.union([external_exports.literal("exact"), external_exports.literal("prefix")]),
5264
+ encryptionNetwork: unifiedEncryptionNetworkSchema,
5265
+ encryptedSymmetricKeyDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5266
+ keyVersion: external_exports.number().int().positive(),
5267
+ mode: external_exports.union([external_exports.literal("mutable"), external_exports.literal("immutable")]),
5268
+ initialCiphertextDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/).optional()
5269
+ }).strict();
5270
+ var unifiedPolicySchema = external_exports.object({
5271
+ schema: external_exports.literal("xyz.tinycloud.policy/policy/v1"),
5272
+ policyId: external_exports.string().regex(/^pol_[a-z2-7]+$/),
5273
+ ownerDid: external_exports.string().min(1),
5274
+ createdAt: external_exports.string().datetime({ offset: true }),
5275
+ expiresAt: external_exports.string().datetime({ offset: true }).optional(),
5276
+ contentSource: unifiedContentSourceSchema,
5277
+ capabilityCeiling: external_exports.array(unifiedCapabilitySchema).min(2),
5278
+ signature: external_exports.object({ suite: external_exports.string().min(1), signerDid: external_exports.string().min(1), value: external_exports.string().min(1) }).strict()
5279
+ }).strict();
5280
+ var unifiedRootSchema = external_exports.object({
5281
+ cid: external_exports.string().min(1),
5282
+ authorization: external_exports.string().regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
5283
+ role: external_exports.union([external_exports.literal("policy-authority"), external_exports.literal("policy-enforcement")])
5284
+ }).strict();
5285
+ var attestedEnforcerBindingV2Schema = external_exports.object({
5286
+ schema: external_exports.literal("xyz.tinycloud.policy/attested-enforcer/v2"),
5287
+ enforcerDid: external_exports.string().regex(/^did:key:z[1-9A-HJ-NP-Za-km-z]+$/),
5288
+ nodeAudience: external_exports.string().min(1),
5289
+ attestationBindingDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5290
+ issuedAt: external_exports.string().datetime({ offset: true }),
5291
+ expiresAt: external_exports.string().datetime({ offset: true }),
5292
+ signature: external_exports.object({ suite: external_exports.literal("Ed25519"), signerDid: external_exports.string().min(1), value: base64UrlString() }).strict()
5293
+ }).strict();
5294
+ var v3TargetSchema = external_exports.object({
5295
+ origin: external_exports.string().refine(isCanonicalHttpsOrigin, { message: "expected a canonical https origin" }),
5296
+ nodeAudience: external_exports.string().min(1),
5297
+ spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
5298
+ }).strict();
5299
+ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5300
+ version: external_exports.literal(3),
5301
+ shareId: external_exports.string().min(1),
5302
+ recipientMatcher: recipientMatcherSchema,
5303
+ deliveryEmail: external_exports.string().email().optional(),
5304
+ actions: external_exports.array(shareActionSchema).min(1).max(3),
5305
+ resource: resourceSelectorSchema,
5306
+ target: v3TargetSchema,
5307
+ policy: unifiedPolicySchema,
5308
+ policyCid: external_exports.string().min(1),
5309
+ policyRoot: unifiedRootSchema,
5310
+ enforcementRoot: unifiedRootSchema,
5311
+ attestedEnforcerBinding: attestedEnforcerBindingV2Schema,
5312
+ contentSource: unifiedContentSourceSchema,
5313
+ contentSourceDigestHex: external_exports.string().regex(/^[0-9a-f]{64}$/),
5314
+ encryptionNetwork: unifiedEncryptionNetworkSchema,
5315
+ expiry: external_exports.string().datetime({ offset: true }),
5316
+ display: displaySchema,
5317
+ encrypted: external_exports.literal(true),
5318
+ metadata: contentMetadataSchema
5319
+ }).strict();
5320
+ function validateV3Invariants(value, ctx) {
5321
+ const actions = [...value.actions];
5322
+ if (new Set(actions).size !== actions.length || actions.some((action, index) => action !== ["read", "list", "edit"].filter((candidate) => actions.includes(candidate))[index])) {
5323
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["actions"], message: "actions must be unique and canonically ordered" });
5324
+ }
5325
+ if (value.policyRoot.role !== "policy-authority" || value.enforcementRoot.role !== "policy-enforcement") {
5326
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policyRoot", "role"], message: "root roles are fixed" });
5327
+ }
5328
+ if (value.policyCid.length === 0 || value.policyCid === value.policyRoot.cid || value.policyCid === value.enforcementRoot.cid) {
5329
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policyCid"], message: "policy CID must be distinct from both roots" });
5330
+ }
5331
+ if (value.contentSource.encryptionNetwork !== value.encryptionNetwork) {
5332
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["encryptionNetwork"], message: "encryption network is not bound to the source" });
5333
+ }
5334
+ 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)) {
5335
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["attestedEnforcerBinding"], message: "enforcer binding does not cover the target and share lifetime" });
5336
+ }
5337
+ 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) {
5338
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["contentSource"], message: "content source is not bound to policy" });
5339
+ }
5340
+ if (value.policy.signature.suite !== "Ed25519" || value.policy.signature.signerDid !== value.policy.ownerDid) {
5341
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "signature"], message: "policy must be signed by its owner" });
5342
+ }
5343
+ const kvCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "kv");
5344
+ const decryptCapabilities = value.policy.capabilityCeiling.filter((capability) => capability.kind === "encryption" && capability.resource === value.encryptionNetwork && capability.action === "tinycloud.encryption/decrypt");
5345
+ if (value.policy.capabilityCeiling.length !== 2 || kvCapabilities.length !== 1 || decryptCapabilities.length !== 1 || kvCapabilities[0]?.resource !== value.contentSource.kvResource || kvCapabilities[0]?.selector !== value.contentSource.selector) {
5346
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["policy", "capabilityCeiling"], message: "policy ceiling must contain exact decrypt network" });
5347
+ }
5348
+ }
5349
+ var unsignedShareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.superRefine(validateV3Invariants);
5350
+ var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature: signatureSchema }).strict().superRefine(validateV3Invariants);
5226
5351
  var BEARER_READ_ABILITY = "kv/get";
5227
5352
  var READ_ABILITIES = /* @__PURE__ */ new Set([BEARER_READ_ABILITY]);
5228
5353
  var ED25519_VERIFY_OPTS = { zip215: false };
@@ -5562,6 +5687,83 @@ function serialize(value) {
5562
5687
  function canonicalize(value) {
5563
5688
  return serialize(value);
5564
5689
  }
5690
+ async function signCompactUcanAuthorization(input) {
5691
+ if (input.notBefore >= input.expiresAt || input.expiresAt - input.notBefore > 60) {
5692
+ throw new TypeError("compact invocation lifetime must be between one and 60 seconds");
5693
+ }
5694
+ const principal = input.issuerDid.split("#", 1)[0];
5695
+ const publicKey = ed25519PublicKeyFromDidKey(principal);
5696
+ const header = {
5697
+ alg: "EdDSA",
5698
+ jwk: { alg: "EdDSA", crv: "Ed25519", kty: "OKP", x: encodeBase64Url(publicKey) },
5699
+ typ: "JWT",
5700
+ ucv: "0.10.0"
5701
+ };
5702
+ const payload = {
5703
+ att: input.attenuation,
5704
+ aud: input.audienceDid,
5705
+ exp: input.expiresAt,
5706
+ fct: input.facts,
5707
+ iss: input.issuerDid.includes("#") ? input.issuerDid : `${principal}#${principal.slice("did:key:".length)}`,
5708
+ nbf: input.notBefore,
5709
+ nnc: input.nonce,
5710
+ prf: input.proofs
5711
+ };
5712
+ const protectedSegment = encodeBase64Url(new TextEncoder().encode(canonicalize(header)));
5713
+ const payloadSegment = encodeBase64Url(new TextEncoder().encode(canonicalize(payload)));
5714
+ const signingInput = new TextEncoder().encode(`${protectedSegment}.${payloadSegment}`);
5715
+ const signature = await input.sign(signingInput);
5716
+ if (signature.length !== 64) throw new TypeError("compact Authorization signature must be Ed25519");
5717
+ return verifyCompactUcanAuthorization(
5718
+ `${protectedSegment}.${payloadSegment}.${encodeBase64Url(signature)}`
5719
+ );
5720
+ }
5721
+ function verifyCompactUcanAuthorization(authorization, expectedCid) {
5722
+ if (/\s/.test(authorization)) throw new TypeError("compact Authorization contains whitespace");
5723
+ const segments = authorization.split(".");
5724
+ if (segments.length !== 3 || segments.some((segment) => segment.length === 0)) throw new TypeError("compact Authorization must contain three segments");
5725
+ const [headerSegment, payloadSegment, signatureSegment] = segments;
5726
+ const headerBytes = decodeBase64Url(headerSegment);
5727
+ const payloadBytes = decodeBase64Url(payloadSegment);
5728
+ const signature = decodeBase64Url(signatureSegment);
5729
+ const decoder = new TextDecoder("utf-8", { fatal: true });
5730
+ const header = JSON.parse(decoder.decode(headerBytes));
5731
+ const payload = JSON.parse(decoder.decode(payloadBytes));
5732
+ if (canonicalize(header) !== decoder.decode(headerBytes) || canonicalize(payload) !== decoder.decode(payloadBytes)) throw new TypeError("compact Authorization JSON is not canonical");
5733
+ assertExactKeys(header, ["alg", "jwk", "typ", "ucv"], "protected header");
5734
+ const jwk = object(header.jwk, "protected JWK");
5735
+ assertExactKeys(jwk, ["alg", "crv", "kty", "x"], "protected JWK");
5736
+ 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");
5737
+ assertExactKeys(payload, ["att", "aud", "exp", "fct", "iss", "nbf", "nnc", "prf"], "UCAN payload");
5738
+ 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");
5739
+ const principal = payload.iss.split("#", 1)[0];
5740
+ const publicKey = ed25519PublicKeyFromDidKey(principal);
5741
+ if (!equal(publicKey, decodeBase64Url(jwk.x))) throw new TypeError("compact Authorization JWK does not bind issuer");
5742
+ if (!ed255192.verify(signature, new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), publicKey, { zip215: false })) throw new TypeError("compact Authorization signature is invalid");
5743
+ const cid2 = CID.createV1(85, create(30, blake3(new TextEncoder().encode(authorization)))).toString();
5744
+ if (expectedCid !== void 0 && cid2 !== expectedCid) throw new TypeError("compact Authorization CID mismatch");
5745
+ return { authorization, cid: cid2, header, payload };
5746
+ }
5747
+ function object(value, label) {
5748
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
5749
+ return value;
5750
+ }
5751
+ function assertExactKeys(value, keys, label) {
5752
+ if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) throw new TypeError(`${label} has unknown or missing fields`);
5753
+ }
5754
+ function decodeBase64Url(value) {
5755
+ if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) throw new TypeError("compact Authorization segment is not base64url");
5756
+ 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));
5757
+ const encoded = typeof Buffer !== "undefined" ? Buffer.from(bytes3).toString("base64url") : btoa(String.fromCharCode(...bytes3)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
5758
+ if (encoded !== value) throw new TypeError("compact Authorization segment is not canonical");
5759
+ return bytes3;
5760
+ }
5761
+ function encodeBase64Url(value) {
5762
+ return typeof Buffer !== "undefined" ? Buffer.from(value).toString("base64url") : btoa(String.fromCharCode(...value)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
5763
+ }
5764
+ function equal(left, right) {
5765
+ return left.length === right.length && left.every((byte, index) => byte === right[index]);
5766
+ }
5565
5767
  var ED25519_VERIFY_OPTS2 = { zip215: false };
5566
5768
  var ENVELOPE_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v1\0";
5567
5769
  var ENVELOPE_V2_SIGNATURE_DOMAIN = "xyz.tinycloud.share/envelope/v2\0";
@@ -5583,8 +5785,8 @@ function signingBytesV2(unsigned) {
5583
5785
  }
5584
5786
  function signEnvelope(envelopeWithoutSig, ed25519PrivKey) {
5585
5787
  const unsigned = unsignedShareEnvelopeSchema.parse(envelopeWithoutSig);
5586
- const publicKey = ed255192.getPublicKey(ed25519PrivKey);
5587
- const signature = ed255192.sign(signingBytes(unsigned), ed25519PrivKey);
5788
+ const publicKey = ed255193.getPublicKey(ed25519PrivKey);
5789
+ const signature = ed255193.sign(signingBytes(unsigned), ed25519PrivKey);
5588
5790
  return {
5589
5791
  ...unsigned,
5590
5792
  signature: {
@@ -5598,7 +5800,7 @@ function verifyEnvelopeSignatureOnly(envelope) {
5598
5800
  const parsed = shareEnvelopeSchema.parse(envelope);
5599
5801
  const { signature, ...unsigned } = parsed;
5600
5802
  const publicKey = ed25519PublicKeyFromDidKey(signature.signerDid);
5601
- return ed255192.verify(
5803
+ return ed255193.verify(
5602
5804
  fromBase64Url(signature.value),
5603
5805
  signingBytes(unsigned),
5604
5806
  publicKey,
@@ -5620,7 +5822,7 @@ async function verifyEnvelope(envelope, options) {
5620
5822
  function verifyEnvelopeV2SignatureOnly(envelope) {
5621
5823
  const parsed = shareEnvelopeV2Schema.parse(envelope);
5622
5824
  const { signature, ...unsigned } = parsed;
5623
- return ed255192.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5825
+ return ed255193.verify(fromBase64Url(signature.value), signingBytesV2(unsigned), ed25519PublicKeyFromDidKey(signature.signerDid), ED25519_VERIFY_OPTS2);
5624
5826
  }
5625
5827
  async function verifyEnvelopeV2(envelope, options) {
5626
5828
  const parsed = shareEnvelopeV2Schema.parse(envelope);
@@ -6058,7 +6260,7 @@ async function receiveShare(link, options = {}) {
6058
6260
  }
6059
6261
 
6060
6262
  // src/publish.ts
6061
- import { ed25519 as ed255193 } from "@noble/curves/ed25519";
6263
+ import { ed25519 as ed255194 } from "@noble/curves/ed25519";
6062
6264
  var SHARE_CONTENT_LIMIT = 100 * 1024 * 1024;
6063
6265
  var SHARE_SEALED_OVERHEAD = 1 + 12 + 16;
6064
6266
  var SHARE_PUBLISH_RESULT_VERSION = 1;
@@ -6243,12 +6445,12 @@ async function publishShare(options) {
6243
6445
  const spaceId = options.spaceId ?? "bearer";
6244
6446
  const shareId = toBase64Url(globalThis.crypto.getRandomValues(new Uint8Array(16)));
6245
6447
  const path = `shares/${shareId}/${options.filename}`;
6246
- const sessionPrivateKey = ed255193.utils.randomPrivateKey();
6247
- const senderPrivateKey = ed255193.utils.randomPrivateKey();
6448
+ const sessionPrivateKey = ed255194.utils.randomPrivateKey();
6449
+ const senderPrivateKey = ed255194.utils.randomPrivateKey();
6248
6450
  let contentKey;
6249
6451
  let envelopeKey;
6250
6452
  try {
6251
- const sessionPublicKey = ed255193.getPublicKey(sessionPrivateKey);
6453
+ const sessionPublicKey = ed255194.getPublicKey(sessionPrivateKey);
6252
6454
  const sessionDid = didKeyFromEd25519PublicKey(sessionPublicKey);
6253
6455
  const delegation = mintBearerDelegation({
6254
6456
  issuerPrivateKey: senderPrivateKey,
@@ -6340,8 +6542,8 @@ async function parseAddressedEnvelope(value) {
6340
6542
  }
6341
6543
 
6342
6544
  // src/owner-policy.ts
6343
- import { ed25519 as ed255194 } from "@noble/curves/ed25519";
6344
- import { sha256 as sha2562 } from "@noble/hashes/sha256";
6545
+ import { ed25519 as ed255195 } from "@noble/curves/ed25519";
6546
+ import { sha256 as sha2563 } from "@noble/hashes/sha256";
6345
6547
 
6346
6548
  // ../../node_modules/multiformats/dist/src/bytes.js
6347
6549
  var empty2 = new Uint8Array(0);
@@ -7248,10 +7450,10 @@ function fromB64(value) {
7248
7450
  throw new Error("base64url decoding is unavailable");
7249
7451
  }
7250
7452
  function cid(bytesValue) {
7251
- return CID2.createV1(code2, create2(SHA256_CODE2, sha2562(bytesValue))).toString(base322.encoder);
7453
+ return CID2.createV1(code2, create2(SHA256_CODE2, sha2563(bytesValue))).toString(base322.encoder);
7252
7454
  }
7253
7455
  function digest(bytesValue) {
7254
- return b64(sha2562(bytesValue));
7456
+ return b64(sha2563(bytesValue));
7255
7457
  }
7256
7458
  function didKeyFromEd25519PublicKey2(publicKey) {
7257
7459
  if (publicKey.length !== 32) throw new Error("share key public key must be 32 bytes");
@@ -7429,7 +7631,7 @@ function validateOwnerSharePolicyRegistration(value, expected) {
7429
7631
  const publicKey = encodedKid.length === 34 ? encodedKid.slice(2) : encodedKid;
7430
7632
  const signatureBytes = fromB64(proof.signature);
7431
7633
  const signedBytes = new TextEncoder().encode(`${OWNER_SHARE_REGISTRATION_DOMAIN}${canonicalize(registrationCore)}`);
7432
- if (signatureBytes.length !== 64 || !ed255194.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
7634
+ if (signatureBytes.length !== 64 || !ed255195.verify(signatureBytes, signedBytes, publicKey)) throw new Error("owner-share registration proof signature is invalid");
7433
7635
  return { registration, proof };
7434
7636
  }
7435
7637
  function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
@@ -7589,7 +7791,7 @@ function assertSafeInput(input) {
7589
7791
  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");
7590
7792
  if (input.contentSource.kind !== "kv") throw new TypeError("addressed policy publication requires a KV content source");
7591
7793
  }
7592
- async function sha2563(value) {
7794
+ async function sha2564(value) {
7593
7795
  return toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", value)));
7594
7796
  }
7595
7797
  function publicationResult(input) {
@@ -7638,7 +7840,7 @@ async function publishAddressedShare(options) {
7638
7840
  const contentSource = options.contentSource;
7639
7841
  const matcher = targetMatcher(target);
7640
7842
  const expiresAt = options.expiresAt.toISOString();
7641
- const contentSourceDigest = options.contentSourceDigest ?? await sha2563(new TextEncoder().encode(canonicalize(contentSource)));
7843
+ const contentSourceDigest = options.contentSourceDigest ?? await sha2564(new TextEncoder().encode(canonicalize(contentSource)));
7642
7844
  const shareKey = await createDelegatedShareKey({ extractable: false });
7643
7845
  let envelopeKey;
7644
7846
  try {
@@ -7702,7 +7904,7 @@ async function publishAddressedShare(options) {
7702
7904
  enforcementDelegation,
7703
7905
  contentSourceDigest
7704
7906
  });
7705
- const authorityMaterialDigest = await sha2563(fromBase64Url(enforcementDelegation.dagCbor));
7907
+ const authorityMaterialDigest = await sha2564(fromBase64Url(enforcementDelegation.dagCbor));
7706
7908
  const authorityTarget = {
7707
7909
  origin: options.nodeOrigin,
7708
7910
  nodeAudience: options.nodeAudience,
@@ -7802,7 +8004,7 @@ async function publishAddressedShare(options) {
7802
8004
  }
7803
8005
 
7804
8006
  // src/policy-authority.ts
7805
- import { ed25519 as ed255195 } from "@noble/curves/ed25519";
8007
+ import { ed25519 as ed255196 } from "@noble/curves/ed25519";
7806
8008
  var ENVELOPE_DOMAIN2 = "xyz.tinycloud.share/envelope/v2\0";
7807
8009
  function record(value, keys, label) {
7808
8010
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
@@ -7858,7 +8060,7 @@ async function verifyEnforcement(delegation) {
7858
8060
  });
7859
8061
  if (expectedDagCbor.length !== dagCbor.length || expectedDagCbor.some((byte, index) => byte !== dagCbor[index])) return false;
7860
8062
  const signature = fromBase64Url(delegation.signature);
7861
- return signature.length === 64 && ed255195.verify(
8063
+ return signature.length === 64 && ed255196.verify(
7862
8064
  signature,
7863
8065
  dagCbor,
7864
8066
  ed25519PublicKeyFromDidKey(delegation.issuerDid),
@@ -7882,7 +8084,7 @@ async function verifyOuterEnvelope(value, envelope, policyCid, expectedEnforcerD
7882
8084
  if (shareCid !== await computeCid(new TextEncoder().encode(canonicalize({ version: 2, shareId: envelope.shareId, policyCid, envelopeCid })))) return void 0;
7883
8085
  const unsigned = { ...identity, envelopeCid, shareCid };
7884
8086
  const signatureBytes = fromBase64Url(requiredString(signature.value, "outer signature"));
7885
- if (signatureBytes.length !== 64 || !ed255195.verify(
8087
+ if (signatureBytes.length !== 64 || !ed255196.verify(
7886
8088
  signatureBytes,
7887
8089
  new TextEncoder().encode(`${ENVELOPE_DOMAIN2}${canonicalize(unsigned)}`),
7888
8090
  ed25519PublicKeyFromDidKey(envelope.signature.signerDid),
@@ -7949,13 +8151,13 @@ function createRegisteredPolicyAuthority(options) {
7949
8151
  }
7950
8152
 
7951
8153
  // src/cache.ts
7952
- import { sha256 as sha2564 } from "@noble/hashes/sha256";
8154
+ import { sha256 as sha2565 } from "@noble/hashes/sha256";
7953
8155
  function cacheKey(key) {
7954
8156
  if (!key.cid || !key.expiresAt) throw new Error("share cache keys require CID and expiry");
7955
8157
  return `${key.cid}\0${key.expiresAt}`;
7956
8158
  }
7957
8159
  function digest2(bytes3) {
7958
- return sha2564(bytes3);
8160
+ return sha2565(bytes3);
7959
8161
  }
7960
8162
  function equalBytes(left, right) {
7961
8163
  if (left.byteLength !== right.byteLength) return false;
@@ -8010,7 +8212,7 @@ var MemoryShareCache = class {
8010
8212
  };
8011
8213
 
8012
8214
  // src/delivery.ts
8013
- import { ed25519 as ed255196 } from "@noble/curves/ed25519";
8215
+ import { ed25519 as ed255197 } from "@noble/curves/ed25519";
8014
8216
  var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
8015
8217
  function isCanonicalHttpsOrigin2(value) {
8016
8218
  let url;
@@ -8102,7 +8304,7 @@ function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
8102
8304
  if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
8103
8305
  const signatureBytes = fromB642(proof.signature);
8104
8306
  const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalize(authorization)}`);
8105
- if (signatureBytes.length !== 64 || !ed255196.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
8307
+ if (signatureBytes.length !== 64 || !ed255197.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
8106
8308
  return { authorization, proof };
8107
8309
  }
8108
8310
  function shareDeliveryTrustedKid(publicKey) {
@@ -8401,7 +8603,8 @@ async function migrateShare(input) {
8401
8603
  }
8402
8604
 
8403
8605
  // src/recipient.ts
8404
- import { ed25519 as ed255197 } from "@noble/curves/ed25519";
8606
+ import { ed25519 as ed255198, x25519 } from "@noble/curves/ed25519";
8607
+ import { sha256 as sha2566 } from "@noble/hashes/sha256";
8405
8608
 
8406
8609
  // src/protocol.ts
8407
8610
  var SHARE_V2_PROTOCOL = Object.freeze({
@@ -8446,7 +8649,7 @@ var DOMAIN = SHARE_V2_PROTOCOL.challengeDomain;
8446
8649
  var PRESENTATION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
8447
8650
  var SESSION_DOMAIN = SHARE_V2_PROTOCOL.sessionDomain;
8448
8651
  var INVOCATION_DOMAIN = SHARE_V2_PROTOCOL.invocationDomain;
8449
- function object(value, label) {
8652
+ function object2(value, label) {
8450
8653
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
8451
8654
  return value;
8452
8655
  }
@@ -8472,12 +8675,12 @@ function trustedPublicKey(value) {
8472
8675
  return value.invitationPublicKey;
8473
8676
  }
8474
8677
  async function verifyWrapped(value, key, domain, trust) {
8475
- const wrapper = object(value, `${key} response`);
8678
+ const wrapper = object2(value, `${key} response`);
8476
8679
  if (Object.keys(wrapper).length !== 2 || !Object.hasOwn(wrapper, key) || !Object.hasOwn(wrapper, "proof")) throw new Error(`${key} response is invalid`);
8477
- const artifact = object(wrapper[key], `${key} artifact`);
8478
- const proof = object(wrapper.proof, `${key} proof`);
8680
+ const artifact = object2(wrapper[key], `${key} artifact`);
8681
+ const proof = object2(wrapper.proof, `${key} proof`);
8479
8682
  if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error(`${key} proof is invalid`);
8480
- if (!ed255197.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
8683
+ if (!ed255198.verify(bytes2(proof.signature, `${key} signature`), new TextEncoder().encode(`${domain}${canonicalize(artifact)}`), trustedPublicKey(trust))) throw new Error(`${key} proof is invalid`);
8481
8684
  return artifact;
8482
8685
  }
8483
8686
  function nativeAction(action) {
@@ -8489,6 +8692,42 @@ function uiAction(action) {
8489
8692
  function selectedAction(envelope) {
8490
8693
  return envelope.actions.includes("list") ? "tinycloud.kv/list" : envelope.actions.includes("edit") ? "tinycloud.kv/put" : "tinycloud.kv/get";
8491
8694
  }
8695
+ function hex(value) {
8696
+ return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8697
+ }
8698
+ function canonicalHashHex(value) {
8699
+ return hex(sha2566(new TextEncoder().encode(canonicalize(value))));
8700
+ }
8701
+ async function aesGcmDecrypt(key, blob) {
8702
+ if (key.length !== 32 || blob.length < 28) throw new Error("encrypted content is malformed");
8703
+ const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["decrypt"]);
8704
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: blob.slice(0, 12) }, cryptoKey, blob.slice(12)));
8705
+ }
8706
+ async function aesGcmEncrypt(key, plaintext) {
8707
+ if (key.length !== 32) throw new Error("encrypted content key is malformed");
8708
+ const nonce = crypto.getRandomValues(new Uint8Array(12));
8709
+ const cryptoKey = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
8710
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, plaintext));
8711
+ const output = new Uint8Array(nonce.length + ciphertext.length);
8712
+ output.set(nonce);
8713
+ output.set(ciphertext, nonce.length);
8714
+ return output;
8715
+ }
8716
+ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
8717
+ let value;
8718
+ try {
8719
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes3));
8720
+ } catch {
8721
+ throw new Error("encrypted content envelope is malformed");
8722
+ }
8723
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("encrypted content envelope is malformed");
8724
+ const record2 = value;
8725
+ const allowed = ["v", "networkId", "alg", "keyVersion", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext", "metadata"];
8726
+ 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"))) {
8727
+ throw new Error("encrypted content envelope binding is invalid");
8728
+ }
8729
+ return record2;
8730
+ }
8492
8731
  async function verifyDetachedResponse(response, trust) {
8493
8732
  let value;
8494
8733
  try {
@@ -8496,12 +8735,12 @@ async function verifyDetachedResponse(response, trust) {
8496
8735
  } catch {
8497
8736
  throw new Error("share read response is invalid");
8498
8737
  }
8499
- const record2 = object(value, "share read response");
8500
- const proof = object(record2.proof, "share read detached proof");
8738
+ const record2 = object2(value, "share read response");
8739
+ const proof = object2(record2.proof, "share read detached proof");
8501
8740
  if (proof.alg !== "EdDSA" || proof.kid !== trust.invitationKid) throw new Error("share read detached proof is invalid");
8502
8741
  const unsigned = { ...record2 };
8503
8742
  delete unsigned.proof;
8504
- if (!ed255197.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");
8743
+ if (!ed255198.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");
8505
8744
  }
8506
8745
  async function post(fetchFn, origin, path, body) {
8507
8746
  const response = await fetchFn(new URL(path, origin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify(body) });
@@ -8517,15 +8756,19 @@ var ShareRecipientClient = class {
8517
8756
  fetchFn;
8518
8757
  session;
8519
8758
  signer;
8759
+ nativeSigner;
8520
8760
  holderProof;
8761
+ v3Authorization;
8762
+ v3ContentKey;
8763
+ v3ContentEnvelope;
8521
8764
  async beginChallenge(envelope) {
8522
8765
  const authority = envelope.ownerAuthority;
8523
8766
  if (authority === void 0) throw new Error("addressed owner authority is required");
8524
- const outer = object(authority.outerEnvelope, "owner authority outer envelope");
8525
- const enforcement = object(authority.enforcementDelegation, "owner authority enforcement delegation");
8526
- const target = object(outer.target, "owner authority target");
8527
- const outerResource = object(outer.resource, "owner authority resource");
8528
- const source = object(outer.contentSource, "owner authority content source");
8767
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8768
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8769
+ const target = object2(outer.target, "owner authority target");
8770
+ const outerResource = object2(outer.resource, "owner authority resource");
8771
+ const source = object2(outer.contentSource, "owner authority content source");
8529
8772
  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");
8530
8773
  const actions = [...outer.actions];
8531
8774
  const action = selectedAction(envelope);
@@ -8538,11 +8781,11 @@ var ShareRecipientClient = class {
8538
8781
  async establish(envelope) {
8539
8782
  const authority = envelope.ownerAuthority;
8540
8783
  if (authority === void 0) throw new Error("addressed owner authority is required");
8541
- const outer = object(authority.outerEnvelope, "owner authority outer envelope");
8542
- const enforcement = object(authority.enforcementDelegation, "owner authority enforcement delegation");
8543
- const target = object(outer.target, "owner authority target");
8544
- const outerResource = object(outer.resource, "owner authority resource");
8545
- const source = object(outer.contentSource, "owner authority content source");
8784
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8785
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8786
+ const target = object2(outer.target, "owner authority target");
8787
+ const outerResource = object2(outer.resource, "owner authority resource");
8788
+ const source = object2(outer.contentSource, "owner authority content source");
8546
8789
  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");
8547
8790
  const actions = [...outer.actions];
8548
8791
  const action = selectedAction(envelope);
@@ -8554,6 +8797,7 @@ var ShareRecipientClient = class {
8554
8797
  const material = await this.options.buildPresentation({ challenge, envelope, policy: {} });
8555
8798
  this.holderProof = material.proof;
8556
8799
  this.signer = material.sign;
8800
+ this.nativeSigner = material.sign;
8557
8801
  if (this.signer === void 0) throw new Error("share holder signer is required");
8558
8802
  const presentation = {
8559
8803
  type: "TinyCloudSharePolicyPresentation",
@@ -8589,7 +8833,7 @@ var ShareRecipientClient = class {
8589
8833
  if (this.session === void 0) await this.establish(envelope);
8590
8834
  const response = await this.nativeInvoke({ action: "get", resource: envelope.resource });
8591
8835
  if (!response.ok) throw new Error("share recipient read was rejected");
8592
- const value = object(await response.json(), "share read response");
8836
+ const value = object2(await response.json(), "share read response");
8593
8837
  const expectedAction = nativeAction("read");
8594
8838
  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");
8595
8839
  const content = fromBase64Url(value.content);
@@ -8598,18 +8842,110 @@ var ShareRecipientClient = class {
8598
8842
  }
8599
8843
  async establishPolicySession() {
8600
8844
  if (this.session !== void 0) return this.session;
8601
- return this.establish(this.options.envelope);
8845
+ const envelope = this.options.envelope;
8846
+ if (envelope.version === 3) return this.establishV3(envelope);
8847
+ return this.establish(envelope);
8848
+ }
8849
+ async establishV3(envelope) {
8850
+ if (this.options.buildPresentation === void 0) throw new Error("share presentation builder is required");
8851
+ const challengeResponse = await this.fetchFn(new URL("/share/v3/policy/challenges", this.options.nodeOrigin), {
8852
+ method: "POST",
8853
+ redirect: "error",
8854
+ headers: { accept: "application/json", "content-type": "application/json" },
8855
+ body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling })
8856
+ });
8857
+ if (!challengeResponse.ok) throw new Error(`v3 policy challenge rejected (${challengeResponse.status})`);
8858
+ const challenge = object2(await challengeResponse.json(), "v3 policy challenge");
8859
+ 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");
8860
+ const material = await this.options.buildPresentation({
8861
+ challenge,
8862
+ envelope,
8863
+ policy: envelope.policy
8864
+ });
8865
+ 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");
8866
+ const delegationResponse = await this.fetchFn(new URL("/share/v3/policy/delegations", this.options.nodeOrigin), {
8867
+ method: "POST",
8868
+ redirect: "error",
8869
+ headers: { accept: "application/json", "content-type": "application/json" },
8870
+ body: JSON.stringify({ policyCid: envelope.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: material.claim, presentation: material.presentation })
8871
+ });
8872
+ if (!delegationResponse.ok) throw new Error(`v3 policy delegation rejected (${delegationResponse.status})`);
8873
+ const delegation = object2(await delegationResponse.json(), "v3 policy delegation");
8874
+ if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
8875
+ const compact = verifyCompactUcanAuthorization(delegation.authorization, delegation.sessionCid);
8876
+ const fact = compact.payload.fct[0];
8877
+ 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");
8878
+ const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization } });
8879
+ if (!imported.ok) throw new Error(`ordinary delegation import rejected (${imported.status})`);
8880
+ this.signer = material.sign;
8881
+ this.v3Authorization = delegation.authorization;
8882
+ this.session = { sessionId: delegation.sessionCid, expiresAt: new Date(compact.payload.exp * 1e3).toISOString(), actions: envelope.actions, resource: envelope.resource };
8883
+ return this.session;
8884
+ }
8885
+ /** Decrypt a v3 KV value through a fresh recipient-signed decrypt invocation. */
8886
+ async decryptV3Content(bytes3) {
8887
+ const envelope = this.options.envelope;
8888
+ const signer = this.nativeSigner ?? this.signer;
8889
+ if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
8890
+ const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
8891
+ const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
8892
+ const receiverPublicKey = toBase64Url(x25519.getPublicKey(receiverPrivateKey));
8893
+ const receiverPublicKeyHash = canonicalHashHex(receiverPublicKey);
8894
+ 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 };
8895
+ const bodyHash = hex(sha2566(new TextEncoder().encode(canonicalize(body))));
8896
+ const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
8897
+ const now = Math.floor(Date.now() / 1e3);
8898
+ const invocation = await signCompactUcanAuthorization({
8899
+ issuerDid: this.options.holderDid,
8900
+ audienceDid: envelope.target.nodeAudience,
8901
+ attenuation: { [encrypted.networkId]: { "tinycloud.encryption/decrypt": [{}] } },
8902
+ facts: [{ type: body.type, targetNode: body.targetNode, networkId: body.networkId, bodyHash, encryptedSymmetricKeyHash: body.encryptedSymmetricKeyHash, receiverPublicKeyHash, alg: body.alg, keyVersion: body.keyVersion }],
8903
+ proofs: [session.cid],
8904
+ notBefore: now,
8905
+ expiresAt: Math.min(now + 60, session.payload.exp),
8906
+ nonce: toBase64Url(crypto.getRandomValues(new Uint8Array(16))),
8907
+ sign: signer
8908
+ });
8909
+ try {
8910
+ 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) });
8911
+ if (!response.ok) throw new Error(`v3 decrypt invocation rejected (${response.status})`);
8912
+ const value = object2(await response.json(), "v3 decrypt response");
8913
+ const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
8914
+ 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(sha2566(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
8915
+ const unsigned = { ...value };
8916
+ delete unsigned.nodeSignature;
8917
+ const signature = fromBase64Url(value.nodeSignature);
8918
+ if (signature.length !== 64 || !ed255198.verify(signature, new TextEncoder().encode(canonicalize(unsigned)), ed25519PublicKeyFromDidKey(body.targetNode), { zip215: false })) throw new Error("v3 decrypt response signature is invalid");
8919
+ const wrapped = fromBase64Url(value.wrappedKey);
8920
+ if (wrapped.length < 60) throw new Error("v3 wrapped content key is malformed");
8921
+ const shared = x25519.getSharedSecret(receiverPrivateKey, wrapped.slice(0, 32));
8922
+ const symmetricKey = await aesGcmDecrypt(shared, wrapped.slice(32));
8923
+ shared.fill(0);
8924
+ if (symmetricKey.length !== 32) throw new Error("v3 content key is malformed");
8925
+ const plaintext = await aesGcmDecrypt(symmetricKey, fromBase64Url(encrypted.ciphertext));
8926
+ this.v3ContentKey?.fill(0);
8927
+ this.v3ContentKey = symmetricKey;
8928
+ this.v3ContentEnvelope = encrypted;
8929
+ return { bytes: plaintext, mediaType: encrypted.metadata?.contentType ?? envelope.metadata.mediaType ?? "application/octet-stream" };
8930
+ } finally {
8931
+ receiverPrivateKey.fill(0);
8932
+ }
8933
+ }
8934
+ /** Re-encrypt edited v3 content with the admitted content key. */
8935
+ async encryptV3Content(bytes3, mediaType) {
8936
+ 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");
8937
+ return new TextEncoder().encode(canonicalize({ ...this.v3ContentEnvelope, ciphertext: toBase64Url(await aesGcmEncrypt(this.v3ContentKey, bytes3)), metadata: { ...this.v3ContentEnvelope.metadata ?? {}, contentType: mediaType } }));
8602
8938
  }
8603
8939
  async resumeWithProof(envelope, resumeToken, proof) {
8604
- const material = object(proof, "share authorization proof");
8605
- const presentation = object(material.presentation, "share presentation");
8606
- const presentationProof = object(material.presentationProof, "share presentation proof");
8940
+ const material = object2(proof, "share authorization proof");
8941
+ const presentation = object2(material.presentation, "share presentation");
8942
+ const presentationProof = object2(material.presentationProof, "share presentation proof");
8607
8943
  const nonce = material.nonce;
8608
8944
  const credential = material.credential;
8609
8945
  const holderDid = material.holderDid;
8610
8946
  const holderBinding = material.holderBinding;
8611
8947
  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");
8612
- 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");
8948
+ 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");
8613
8949
  const session = await verifyWrapped(value, "session", SESSION_DOMAIN, this.options.trustedNode);
8614
8950
  const authority = envelope.ownerAuthority;
8615
8951
  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");
@@ -8623,27 +8959,61 @@ var ShareRecipientClient = class {
8623
8959
  }
8624
8960
  async nativeInvoke(request) {
8625
8961
  if (this.session === void 0) throw new Error("share policy session is required");
8626
- if (this.options.envelope.ownerAuthority === void 0 || this.signer === void 0 || this.holderProof === void 0) throw new Error("share holder signer is required");
8627
- const authority = this.options.envelope.ownerAuthority;
8962
+ const envelope = this.options.envelope;
8963
+ if (envelope.version === 3) return this.nativeInvokeV3(request, envelope);
8964
+ if (envelope.ownerAuthority === void 0 || this.signer === void 0 || this.holderProof === void 0) throw new Error("share holder signer is required");
8965
+ const authority = envelope.ownerAuthority;
8628
8966
  const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
8629
8967
  const resource = typeof request.resource?.path === "string" ? request.resource.path : this.session.resource.path;
8630
8968
  const actions = [...new Set(this.session.actions.map(nativeAction).concat(action === "tinycloud.kv/metadata" ? [action] : []))].sort();
8631
8969
  const bodyBytes = request.body === void 0 ? void 0 : Uint8Array.from(request.body);
8632
8970
  const bodyDigest = bodyBytes === void 0 ? void 0 : toBase64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", bodyBytes)));
8633
- const outer = object(authority.outerEnvelope, "owner authority outer envelope");
8634
- const enforcement = object(authority.enforcementDelegation, "owner authority enforcement delegation");
8635
- 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))) };
8971
+ const outer = object2(authority.outerEnvelope, "owner authority outer envelope");
8972
+ const enforcement = object2(authority.enforcementDelegation, "owner authority enforcement delegation");
8973
+ 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))) };
8636
8974
  const requestBodyDigest = await digest3(invocationBase);
8637
8975
  const invocation = { ...invocationBase, requestBodyDigest };
8638
8976
  const proof = { ...this.holderProof, signature: toBase64Url(await this.signer(new TextEncoder().encode(`${INVOCATION_DOMAIN}${canonicalize(invocation)}`))) };
8639
- 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 };
8977
+ 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 };
8640
8978
  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 } }) });
8641
8979
  if (response.ok) await verifyDetachedResponse(response, this.options.trustedNode);
8642
8980
  return response;
8643
8981
  }
8982
+ async nativeInvokeV3(request, envelope) {
8983
+ const signer = this.nativeSigner ?? this.signer;
8984
+ if (this.session === void 0 || this.v3Authorization === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
8985
+ const session = verifyCompactUcanAuthorization(this.v3Authorization, this.session.sessionId);
8986
+ if (session.payload.aud !== this.options.holderDid) throw new Error("v3 session recipient mismatch");
8987
+ const action = request.action === "list" ? "tinycloud.kv/list" : request.action === "put" ? "tinycloud.kv/put" : request.action === "metadata" ? "tinycloud.kv/metadata" : "tinycloud.kv/get";
8988
+ const path = typeof request.resource?.path === "string" ? request.resource.path.replace(/^\//, "").replace(/\/$/, "") : envelope.resource.path;
8989
+ const capability = envelope.policy.capabilityCeiling.find((candidate) => candidate.kind === "kv");
8990
+ if (capability === void 0 || !capability.actions.includes(action)) throw new Error("v3 requested capability is not in the signed policy");
8991
+ const marker = "/kv/";
8992
+ const split = capability.resource.indexOf(marker);
8993
+ if (split < 0) throw new Error("v3 KV resource is invalid");
8994
+ const root = capability.resource.slice(split + marker.length).replace(/\/$/, "");
8995
+ if (path !== root && !(capability.selector === "prefix" && path.startsWith(`${root}/`))) throw new Error("v3 KV request is outside the signed selector");
8996
+ const resource = `${capability.resource.slice(0, split + marker.length)}${path}`;
8997
+ const now = Math.floor(Date.now() / 1e3);
8998
+ 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 });
8999
+ const headers = new Headers({ accept: "application/json", Authorization: invocation.authorization });
9000
+ let body;
9001
+ if (action === "tinycloud.kv/put") {
9002
+ if (request.body === void 0) throw new Error("v3 KV put requires bytes");
9003
+ body = Uint8Array.from(request.body);
9004
+ headers.set("content-type", request.contentType ?? "application/octet-stream");
9005
+ if (request.ifMatch !== void 0) headers.set("if-match", request.ifMatch);
9006
+ }
9007
+ return this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers, ...body === void 0 ? {} : { body } });
9008
+ }
8644
9009
  };
8645
9010
  function createAddressedAuthorization(input) {
8646
- const client = (envelope) => new ShareRecipientClient({ ...input, envelope });
9011
+ const { buildPresentation, ...options } = input;
9012
+ const client = (envelope) => new ShareRecipientClient({
9013
+ ...options,
9014
+ envelope,
9015
+ ...buildPresentation === void 0 ? {} : { buildPresentation: ({ challenge, envelope: candidate, policy }) => buildPresentation({ challenge, envelope: candidate, policy }) }
9016
+ });
8647
9017
  return {
8648
9018
  async begin({ envelope, method }) {
8649
9019
  const current = client(envelope);
@@ -8657,15 +9027,15 @@ function createAddressedAuthorization(input) {
8657
9027
  },
8658
9028
  async verifyResult({ envelope, value, proof }) {
8659
9029
  try {
8660
- const wrapper = object(proof, "share read proof");
8661
- const response = object(wrapper.response, "share read response proof");
9030
+ const wrapper = object2(proof, "share read proof");
9031
+ const response = object2(wrapper.response, "share read response proof");
8662
9032
  const envelopeAction = nativeAction("read");
8663
9033
  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;
8664
- const detached = object(wrapper.detached, "share read detached proof");
9034
+ const detached = object2(wrapper.detached, "share read detached proof");
8665
9035
  if (detached.alg !== "EdDSA" || detached.kid !== input.trustedNode.invitationKid) return false;
8666
9036
  const unsigned = { ...response };
8667
9037
  delete unsigned.proof;
8668
- return ed255197.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
9038
+ return ed255198.verify(bytes2(detached.signature, "share read signature"), new TextEncoder().encode(`${SHARE_V2_PROTOCOL.readResponseDomain}${canonicalize(unsigned)}`), trustedPublicKey(input.trustedNode));
8669
9039
  } catch {
8670
9040
  return false;
8671
9041
  }