@tinycloud/sdk-core 3.0.0-beta.7 → 3.0.0-beta.9

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
@@ -139,6 +139,7 @@ __export(index_exports, {
139
139
  NETWORK_NAME_PATTERN: () => import_sdk_services8.NETWORK_NAME_PATTERN,
140
140
  NetworkIdError: () => import_sdk_services8.NetworkIdError,
141
141
  OPENKEY_ACTION_ID_SEPARATOR: () => OPENKEY_ACTION_ID_SEPARATOR,
142
+ OWNER_NODE_BINDING_TIMEOUT_MS: () => OWNER_NODE_BINDING_TIMEOUT_MS,
142
143
  OpenKeyManageKeyError: () => OpenKeyManageKeyError,
143
144
  POLICY_CAPABILITY_V1_DOMAIN: () => POLICY_CAPABILITY_V1_DOMAIN,
144
145
  POLICY_CAPABILITY_V1_SCHEMA: () => POLICY_CAPABILITY_V1_SCHEMA,
@@ -178,7 +179,6 @@ __export(index_exports, {
178
179
  SECRET_RECORDS_SCHEMA: () => import_bootstrap4.SECRET_RECORDS_SCHEMA,
179
180
  SERVICE_LONG_TO_SHORT: () => SERVICE_LONG_TO_SHORT,
180
181
  SERVICE_SHORT_TO_LONG: () => SERVICE_SHORT_TO_LONG,
181
- SHARE_DELIVERY_AUTHORIZATION_DOMAIN: () => SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
182
182
  SPACE: () => import_bootstrap4.SPACE,
183
183
  SQL: () => import_bootstrap4.SQL,
184
184
  SQLAction: () => import_sdk_services8.SQLAction,
@@ -381,6 +381,7 @@ __export(index_exports, {
381
381
  parsePolicySessionUcan: () => parsePolicySessionUcan,
382
382
  parsePortableDelegation: () => parsePortableDelegation,
383
383
  parseRecapCapabilities: () => parseRecapCapabilities,
384
+ parseSecretCatalogKey: () => import_sdk_services8.parseSecretCatalogKey,
384
385
  parseShareArtifact: () => parseShareArtifact,
385
386
  parseShareUrl: () => parseShareUrl,
386
387
  parseSpaceUri: () => parseSpaceUri,
@@ -393,6 +394,7 @@ __export(index_exports, {
393
394
  principalDidEquals: () => principalDidEquals,
394
395
  projectUnifiedPolicyCapabilities: () => projectUnifiedPolicyCapabilities,
395
396
  projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
397
+ publishLocationRecord: () => publishLocationRecord,
396
398
  recapCaveatsEqual: () => recapCaveatsEqual,
397
399
  registerPolicyV3: () => registerPolicyV3,
398
400
  renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
@@ -412,7 +414,6 @@ __export(index_exports, {
412
414
  serviceError: () => import_sdk_services8.serviceError,
413
415
  sha256Base64Url: () => sha256Base64Url,
414
416
  shareCapabilityAllows: () => shareCapabilityAllows,
415
- shareDeliveryTrustedKid: () => shareDeliveryTrustedKid,
416
417
  signCompactUcanAuthorization: () => signCompactUcanAuthorization,
417
418
  signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
418
419
  signLocationRecord: () => signLocationRecord,
@@ -449,7 +450,6 @@ __export(index_exports, {
449
450
  validatePolicyStatusSignedShape: () => validatePolicyStatusSignedShape,
450
451
  validatePolicyStatusUnsigned: () => validatePolicyStatusUnsigned,
451
452
  validatePolicyUnsigned: () => validatePolicyUnsigned,
452
- validateShareDeliveryAuthorizationBytes: () => validateShareDeliveryAuthorizationBytes,
453
453
  validateShareDeliveryAuthorizationV3Bytes: () => validateShareDeliveryAuthorizationV3Bytes,
454
454
  validateUnifiedPolicyV2: () => validateUnifiedPolicyV2,
455
455
  verifyDecryptResponse: () => import_sdk_services8.verifyDecryptResponse,
@@ -457,6 +457,7 @@ __export(index_exports, {
457
457
  verifyEip191MessageSignature: () => verifyEip191MessageSignature,
458
458
  verifyIssuedCredential: () => verifyIssuedCredential,
459
459
  verifyLocationRecord: () => verifyLocationRecord,
460
+ verifyOwnerNodeBinding: () => verifyOwnerNodeBinding,
460
461
  verifyPolicy: () => verifyPolicy,
461
462
  verifyPolicyEngineRecord: () => verifyPolicyEngineRecord,
462
463
  verifyPolicyRootRevocationV3: () => verifyPolicyRootRevocationV3,
@@ -9331,25 +9332,33 @@ function parsePublishedShareLink(link, options2 = {}) {
9331
9332
  }
9332
9333
  const origin2 = normalizeOrigin(url.origin);
9333
9334
  if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
9334
- if (url.search !== "" || url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
9335
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
9335
+ if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
9336
+ if (url.pathname === "/viewer") {
9337
+ const secretPayload = url.search === "" && url.hash.startsWith("#tc2=") ? url.hash.slice("#tc2=".length) : void 0;
9338
+ const publicPayload = url.hash === "" && url.searchParams.size === 1 ? url.searchParams.get("tc2") ?? void 0 : void 0;
9339
+ if (secretPayload === void 0 && publicPayload === void 0) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
9340
+ if (publicPayload !== void 0 && url.search !== `?tc2=${publicPayload}`) throw new ShareEnvelopeError("invalid-link", "published share link is not canonical");
9336
9341
  let payload;
9337
9342
  try {
9338
- const bytes2 = base64UrlToBytes(url.hash.slice("#v=2&p=".length));
9343
+ const bytes2 = base64UrlToBytes(secretPayload ?? publicPayload);
9339
9344
  if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
9340
- const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
9345
+ const text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
9346
+ const value = JSON.parse(text2);
9341
9347
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
9348
+ if (canonicalJson(value) !== text2) throw new Error("inline payload is not canonical JSON");
9342
9349
  payload = value;
9343
9350
  } catch (error) {
9344
9351
  throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
9345
9352
  }
9346
- if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || Object.keys(payload).some((key3) => !["v", "c", "cid", "k"].includes(key3))) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
9353
+ const payloadKeys = Object.keys(payload);
9354
+ if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || payloadKeys.some((key3) => !["v", "c", "cid", "k"].includes(key3)) || publicPayload !== void 0 && (payloadKeys.length !== 3 || payload.k !== void 0)) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
9347
9355
  const sealed = base64UrlToBytes(payload.c);
9348
9356
  if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
9349
9357
  const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
9350
9358
  if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
9351
9359
  return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
9352
9360
  }
9361
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
9353
9362
  const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
9354
9363
  if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
9355
9364
  const key = base64UrlToBytes(url.hash.slice(3));
@@ -9369,13 +9378,13 @@ function parseShareUrl(link, options2 = {}) {
9369
9378
  } catch {
9370
9379
  throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
9371
9380
  }
9372
- if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
9373
9381
  const origin2 = normalizeOrigin(url.origin);
9374
9382
  if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
9375
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
9383
+ if (url.pathname === "/viewer" && (url.hash.startsWith("#tc2=") || url.searchParams.has("tc2"))) {
9376
9384
  const published = parsePublishedShareLink(link, options2);
9377
9385
  return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
9378
9386
  }
9387
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
9379
9388
  const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
9380
9389
  if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
9381
9390
  const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
@@ -9398,7 +9407,8 @@ function parseShareUrl(link, options2 = {}) {
9398
9407
  }
9399
9408
  function isV2ShareLink(link) {
9400
9409
  try {
9401
- return new URL(link).pathname.startsWith("/s/");
9410
+ const url = new URL(link);
9411
+ return url.pathname.startsWith("/s/") || url.pathname === "/viewer";
9402
9412
  } catch {
9403
9413
  return link.startsWith(INLINE_PREFIX);
9404
9414
  }
@@ -12211,114 +12221,13 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
12211
12221
  return validateOwnerSharePolicyRegistration(value, expected);
12212
12222
  }
12213
12223
 
12214
- // src/delegations/share-delivery.ts
12215
- var import_ed255199 = require("@noble/curves/ed25519");
12216
- var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
12217
- function isCanonicalHttpsOrigin(value) {
12218
- let url;
12219
- try {
12220
- url = new URL(value);
12221
- } catch {
12222
- return false;
12223
- }
12224
- return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
12225
- }
12226
- var AUTHORIZATION_KEYS = [
12227
- "type",
12228
- "version",
12229
- "jti",
12230
- "shareCid",
12231
- "shareId",
12232
- "registrationCid",
12233
- "delegationCid",
12234
- "enforcementDelegationCid",
12235
- "envelopeCid",
12236
- "policyCid",
12237
- "nodeAudience",
12238
- "targetOrigin",
12239
- "openCredentialsAudience",
12240
- "holder",
12241
- "recipientMatcher",
12242
- "deliveryEmail",
12243
- "shareUrl",
12244
- "returnOrigin",
12245
- "documentName",
12246
- "senderDid",
12247
- "senderTrust",
12248
- "contentSource",
12249
- "contentSourceDigest",
12250
- "shareExpiresAt",
12251
- "issuedAt",
12252
- "reportAbuseToken",
12253
- "actions",
12254
- "resource",
12255
- "authorityMaterialHandle",
12256
- "authorityMaterialDigest",
12257
- "requestBodyDigest",
12258
- "idempotencyKey",
12259
- "expiresAt",
12260
- "dataAuthority"
12261
- ];
12262
- function assertObject2(value, keys, label) {
12263
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
12264
- const record2 = value;
12265
- if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
12266
- return record2;
12267
- }
12268
- function fromB642(value) {
12269
- if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
12270
- if (typeof atob === "function") {
12271
- const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
12272
- return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
12273
- }
12274
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
12275
- throw new Error("base64url decoding is unavailable");
12276
- }
12277
- function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
12278
- let value;
12279
- try {
12280
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
12281
- } catch {
12282
- throw new Error("share delivery authorization response is not valid UTF-8 JSON");
12283
- }
12284
- const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
12285
- const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
12286
- const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
12287
- const request = expected.request;
12288
- if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
12289
- throw new Error("share delivery authorization is not bound to the submitted request");
12290
- }
12291
- if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
12292
- if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
12293
- if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
12294
- throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
12295
- }
12296
- if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
12297
- if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
12298
- const encodedKid = expected.nodeProof.publicKey;
12299
- const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
12300
- if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
12301
- const signatureBytes = fromB642(proof.signature);
12302
- const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
12303
- if (signatureBytes.length !== 64 || !import_ed255199.ed25519.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
12304
- return { authorization, proof };
12305
- }
12306
- function shareDeliveryTrustedKid(publicKey) {
12307
- if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
12308
- const prefixed = new Uint8Array(34);
12309
- prefixed.set([237, 1]);
12310
- prefixed.set(publicKey, 2);
12311
- const did = `did:key:${base58btc.encode(prefixed)}`;
12312
- return `${did}#${did.slice("did:key:".length)}`;
12313
- }
12314
-
12315
12224
  // src/delegations/share-delivery-v3.ts
12316
- var import_ed2551910 = require("@noble/curves/ed25519");
12225
+ var import_ed255199 = require("@noble/curves/ed25519");
12317
12226
  var import_sha2567 = require("@noble/hashes/sha256");
12318
12227
  var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
12319
12228
  var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
12320
- var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
12321
- var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
12229
+ var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
12230
+ var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
12322
12231
  function exactObject2(value, keys, label) {
12323
12232
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
12324
12233
  const record2 = value;
@@ -12362,15 +12271,16 @@ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
12362
12271
  const contentSource = object3(envelope.contentSource, "v3 content source");
12363
12272
  const envelopeSignature = object3(envelope.signature, "v3 envelope signature");
12364
12273
  const target = object3(envelope.target, "v3 target");
12365
- const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "audience", "issuedAt", "expiresAt", "nonce"];
12366
- if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.audience !== expected.credentialsAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || signature.signerDid !== target.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
12274
+ const binding = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
12275
+ const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
12276
+ if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.label !== expected.request.documentName || request.shareExpiresAt !== envelope.expiry || request.audience !== expected.deliveryAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || target.nodeAudience !== binding.nodeAudience || signature.signerDid !== binding.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
12367
12277
  const issuedAt = Date.parse(String(request.issuedAt));
12368
12278
  const expiresAt = Date.parse(String(request.expiresAt));
12369
12279
  if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
12370
12280
  const admissionSignature = decodeBase64Url3(signature.value);
12371
12281
  const { signature: _signature, ...unsignedAdmission } = admission;
12372
12282
  const digest4 = (0, import_sha2567.sha256)(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
12373
- if (admissionSignature.length !== 64 || !import_ed2551910.ed25519.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
12283
+ if (admissionSignature.length !== 64 || !import_ed255199.ed25519.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
12374
12284
  return { request, admission };
12375
12285
  }
12376
12286
 
@@ -17995,7 +17905,7 @@ function portForProtocol(protocol) {
17995
17905
  }
17996
17906
 
17997
17907
  // src/location.ts
17998
- var import_ed2551911 = require("@noble/curves/ed25519");
17908
+ var import_ed2551910 = require("@noble/curves/ed25519");
17999
17909
  var import_viem5 = require("viem");
18000
17910
  var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
18001
17911
  var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
@@ -18003,6 +17913,7 @@ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
18003
17913
  var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
18004
17914
  var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
18005
17915
  var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
17916
+ var OWNER_NODE_BINDING_TIMEOUT_MS = 5e3;
18006
17917
  function createInMemoryLocalNodeIdentityStore() {
18007
17918
  const pins = /* @__PURE__ */ new Map();
18008
17919
  return {
@@ -18135,21 +18046,80 @@ async function verifyLocationRecord(input) {
18135
18046
  }
18136
18047
  return false;
18137
18048
  }
18138
- async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
18049
+ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
18139
18050
  const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
18140
- const response = await fetchFn(url);
18051
+ const response = await fetchFn(url, {
18052
+ redirect: "error",
18053
+ headers: { accept: "application/json" },
18054
+ ...signal === void 0 ? {} : { signal }
18055
+ });
18141
18056
  if (response.status === 404) {
18142
18057
  return null;
18143
18058
  }
18144
18059
  if (!response.ok) {
18145
18060
  throw new Error(`location registry returned HTTP ${response.status}`);
18146
18061
  }
18147
- const body = await response.json();
18062
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
18063
+ let body;
18064
+ try {
18065
+ body = JSON.parse(text2);
18066
+ } catch {
18067
+ throw new LocationRecordValidationError("registry response is not JSON");
18068
+ }
18148
18069
  if (body.record === void 0) {
18149
18070
  throw new LocationRecordValidationError("registry response missing record");
18150
18071
  }
18151
18072
  return validateLocationRecord(body.record);
18152
18073
  }
18074
+ async function publishLocationRecord(options2) {
18075
+ validateSubject(options2.subject);
18076
+ const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
18077
+ const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
18078
+ const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
18079
+ const current = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
18080
+ if (current !== null) {
18081
+ if (current.subject !== options2.subject || !await verifyLocationRecord(current)) {
18082
+ throw new LocationRecordValidationError("existing location record signature is invalid");
18083
+ }
18084
+ if (publishedHttpOrigins(current).includes(nodeOrigin)) {
18085
+ return current;
18086
+ }
18087
+ }
18088
+ const record2 = await signLocationRecord({
18089
+ version: 1,
18090
+ subject: options2.subject,
18091
+ multiaddrs: [...current?.multiaddrs ?? [], httpUrlToMultiaddr(nodeOrigin)],
18092
+ updated_at: (options2.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
18093
+ sequence: (current?.sequence ?? -1) + 1
18094
+ }, options2.signer);
18095
+ const response = await fetchFn(
18096
+ `${registryUrl}/v1/locations/${encodeURIComponent(options2.subject)}`,
18097
+ {
18098
+ method: "PUT",
18099
+ redirect: "error",
18100
+ headers: { accept: "application/json", "content-type": "application/json" },
18101
+ body: JSON.stringify(record2)
18102
+ }
18103
+ );
18104
+ if (!response.ok) {
18105
+ throw new Error(`location registry publish returned HTTP ${response.status}`);
18106
+ }
18107
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
18108
+ let body;
18109
+ try {
18110
+ body = JSON.parse(text2);
18111
+ } catch {
18112
+ throw new LocationRecordValidationError("registry publish response is not JSON");
18113
+ }
18114
+ if (body.record === void 0) {
18115
+ throw new LocationRecordValidationError("registry publish response missing record");
18116
+ }
18117
+ const published = validateLocationRecord(body.record);
18118
+ if (canonicalLocationPayload(locationPayloadForRecord(published)) !== canonicalLocationPayload(locationPayloadForRecord(record2)) || published.signature !== record2.signature || !await verifyLocationRecord(published)) {
18119
+ throw new LocationRecordValidationError("registry publish response does not match the signed record");
18120
+ }
18121
+ return published;
18122
+ }
18153
18123
  async function resolveCloudLocation(subject, options2 = {}) {
18154
18124
  validateSubject(subject);
18155
18125
  const verifyRecords = options2.verifyRecords ?? true;
@@ -18400,6 +18370,123 @@ function multiaddrToHttpUrl(input) {
18400
18370
  }
18401
18371
  return uri;
18402
18372
  }
18373
+ function exactHttpOrigin(value, label) {
18374
+ let url;
18375
+ try {
18376
+ url = new URL(value);
18377
+ } catch {
18378
+ throw new LocationRecordValidationError(`${label} must be an absolute URL origin`);
18379
+ }
18380
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
18381
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
18382
+ throw new LocationRecordValidationError(`${label} must use HTTPS (or loopback HTTP)`);
18383
+ }
18384
+ if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
18385
+ throw new LocationRecordValidationError(`${label} must be an origin without credentials, path, query, or fragment`);
18386
+ }
18387
+ return url.origin;
18388
+ }
18389
+ function publishedHttpOrigins(record2) {
18390
+ const origins = [];
18391
+ for (const address of record2.multiaddrs) {
18392
+ try {
18393
+ origins.push(exactHttpOrigin(multiaddrToHttpUrl(address), "published node URL"));
18394
+ } catch {
18395
+ }
18396
+ }
18397
+ return origins;
18398
+ }
18399
+ async function boundedResponseText(response, label, maxBytes) {
18400
+ const declaredLength = response.headers.get("content-length");
18401
+ if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
18402
+ throw new LocationRecordValidationError(`${label} response is too large`);
18403
+ }
18404
+ if (response.body === null) return "";
18405
+ const reader = response.body.getReader();
18406
+ const chunks = [];
18407
+ let total = 0;
18408
+ try {
18409
+ while (true) {
18410
+ const { done, value } = await reader.read();
18411
+ if (done) break;
18412
+ if (value === void 0) continue;
18413
+ total += value.byteLength;
18414
+ if (total > maxBytes) {
18415
+ await reader.cancel().catch(() => void 0);
18416
+ throw new LocationRecordValidationError(`${label} response is too large`);
18417
+ }
18418
+ chunks.push(value);
18419
+ }
18420
+ } finally {
18421
+ reader.releaseLock();
18422
+ }
18423
+ const bytes2 = new Uint8Array(total);
18424
+ let offset = 0;
18425
+ for (const chunk of chunks) {
18426
+ bytes2.set(chunk, offset);
18427
+ offset += chunk.byteLength;
18428
+ }
18429
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
18430
+ }
18431
+ function boundedSignal(parent, timeoutMs) {
18432
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
18433
+ throw new LocationRecordValidationError("owner-node binding timeout must be positive");
18434
+ }
18435
+ const controller = new AbortController();
18436
+ const abortFromParent = () => controller.abort(parent?.reason);
18437
+ if (parent?.aborted) abortFromParent();
18438
+ else parent?.addEventListener("abort", abortFromParent, { once: true });
18439
+ const timer = setTimeout(() => controller.abort(new DOMException("owner-node binding timed out", "TimeoutError")), timeoutMs);
18440
+ return {
18441
+ signal: controller.signal,
18442
+ dispose() {
18443
+ clearTimeout(timer);
18444
+ parent?.removeEventListener("abort", abortFromParent);
18445
+ }
18446
+ };
18447
+ }
18448
+ async function verifyOwnerNodeBinding(options2) {
18449
+ validateSubject(options2.ownerDid);
18450
+ validateSubject(options2.nodeDid);
18451
+ const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
18452
+ const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
18453
+ const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
18454
+ const lookup = boundedSignal(options2.signal, options2.timeoutMs ?? OWNER_NODE_BINDING_TIMEOUT_MS);
18455
+ try {
18456
+ const record2 = await fetchLocationRecord(registryUrl, options2.ownerDid, fetchFn, lookup.signal);
18457
+ if (record2 === null) {
18458
+ throw new LocationRecordValidationError("owner has no published location record");
18459
+ }
18460
+ if (record2.subject !== options2.ownerDid || !await verifyLocationRecord(record2)) {
18461
+ throw new LocationRecordValidationError("owner location record signature is invalid");
18462
+ }
18463
+ const publishedOrigins = publishedHttpOrigins(record2);
18464
+ if (!publishedOrigins.includes(nodeOrigin)) {
18465
+ throw new LocationRecordValidationError("share target is not in the owner's signed location record");
18466
+ }
18467
+ const response = await fetchFn(new URL("/info", nodeOrigin), {
18468
+ redirect: "error",
18469
+ headers: { accept: "application/json" },
18470
+ signal: lookup.signal
18471
+ });
18472
+ if (!response.ok) {
18473
+ throw new LocationRecordValidationError(`target node /info returned HTTP ${response.status}`);
18474
+ }
18475
+ const text2 = await boundedResponseText(response, "target node /info", 16 * 1024);
18476
+ let body;
18477
+ try {
18478
+ body = JSON.parse(text2);
18479
+ } catch {
18480
+ throw new LocationRecordValidationError("target node /info response is not JSON");
18481
+ }
18482
+ if (body === null || typeof body !== "object" || Array.isArray(body) || body.nodeId !== options2.nodeDid) {
18483
+ throw new LocationRecordValidationError("target node identity does not match the share attestation");
18484
+ }
18485
+ return Object.freeze({ record: record2, nodeOrigin, nodeDid: options2.nodeDid });
18486
+ } finally {
18487
+ lookup.dispose();
18488
+ }
18489
+ }
18403
18490
  function httpUrlToMultiaddr(input) {
18404
18491
  const url = new URL(input);
18405
18492
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -18567,7 +18654,7 @@ function verifyDidKeySignature(did, payload, signature) {
18567
18654
  "did:key signature must be a base64url Ed25519 signature"
18568
18655
  );
18569
18656
  }
18570
- return import_ed2551911.ed25519.verify(
18657
+ return import_ed2551910.ed25519.verify(
18571
18658
  signatureBytes,
18572
18659
  new TextEncoder().encode(payload),
18573
18660
  publicKey
@@ -18575,7 +18662,7 @@ function verifyDidKeySignature(did, payload, signature) {
18575
18662
  }
18576
18663
  function verifyDidKeyEd25519Signature(did, payload, signature) {
18577
18664
  const publicKey = ed25519PublicKeyFromDidKey2(did);
18578
- return import_ed2551911.ed25519.verify(signature, payload, publicKey);
18665
+ return import_ed2551910.ed25519.verify(signature, payload, publicKey);
18579
18666
  }
18580
18667
  function ed25519PublicKeyFromDidKey2(did) {
18581
18668
  const identifier = did.slice("did:key:".length);