@synonymdev/pubky 0.15.0 → 0.16.0

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/index.cjs CHANGED
@@ -9,6 +9,7 @@ const PUBKY_SESSIONS_DB_NAME = "pubky-auth";
9
9
  const PUBKY_SESSIONS_DB_VERSION = 1;
10
10
  const PUBKY_SESSIONS_STORE_NAME = "storedSessions";
11
11
  const PUBKY_SESSIONS_DELEGATED_KEYS_STORE_NAME = "delegatedGrantKeys";
12
+ const PUBKY_APPROVAL_SESSION_VERSION = "pubky-session-v2";
12
13
 
13
14
  /** Assert that IndexedDB is available for browser session persistence. */
14
15
  function requireIndexedDb() {
@@ -53,25 +54,27 @@ function openSessionStoreDb() {
53
54
  }
54
55
 
55
56
  /**
56
- * Run an IndexedDB operation against the stored-session object store.
57
+ * Run an IndexedDB operation across session records and browser keys.
57
58
  *
58
- * The callback receives the object store and returns a request. This wrapper
59
+ * The callback receives the transaction and returns a request. This wrapper
59
60
  * resolves only after the transaction commits, not when the request succeeds.
60
61
  */
61
- async function withSessionStore(mode, operation) {
62
+ async function withSessionStores(mode, operation) {
62
63
  const db = await openSessionStoreDb();
63
64
  try {
64
65
  return await new Promise((resolve, reject) => {
65
- const tx = db.transaction(PUBKY_SESSIONS_STORE_NAME, mode);
66
- const store = tx.objectStore(PUBKY_SESSIONS_STORE_NAME);
66
+ const tx = db.transaction(
67
+ [PUBKY_SESSIONS_STORE_NAME, PUBKY_SESSIONS_DELEGATED_KEYS_STORE_NAME], mode,
68
+ );
67
69
  let result;
68
70
  try {
69
- const request = operation(store);
71
+ const request = operation(tx);
70
72
  request.onsuccess = () => {
71
73
  result = request.result;
72
74
  };
73
75
  request.onerror = () => reject(request.error ?? new Error("Pubky session store request failed."));
74
76
  } catch (error) {
77
+ tx.abort();
75
78
  reject(error);
76
79
  }
77
80
  tx.onerror = () => reject(tx.error ?? new Error("Pubky session store transaction failed."));
@@ -101,21 +104,44 @@ async function __pubkySessionStorePut(record, lease) {
101
104
  requireIndexedDb();
102
105
  try {
103
106
  const previous = await __pubkySessionStoreGet(record.id);
104
- if (previous?.sharedSession) record.sharedSession = previous.sharedSession;
105
- await withSessionStore("readwrite", (store) => {
106
- requireSessionLease(lease, true);
107
- return store.put(record);
107
+ if (previous?.version === PUBKY_APPROVAL_SESSION_VERSION && record.version !== previous.version) {
108
+ throw new Error("Cannot replace a stored approval with an authentication-only record.");
109
+ }
110
+ const stored = { ...record };
111
+ if (previous?.sharedSession) stored.sharedSession = previous.sharedSession;
112
+ await withSessionStores("readwrite", tx => {
113
+ const held = requireSessionLease(lease, true);
114
+ if (held.id !== record.id || held.homeserver !== record.homeserver) {
115
+ throw new Error("Session record does not match its browser lock.");
116
+ }
117
+ return tx.objectStore(PUBKY_SESSIONS_STORE_NAME).put(stored);
108
118
  });
109
119
  } catch (error) {
110
120
  throw contextualSessionStoreError("Saving Pubky session failed.", error);
111
121
  }
112
122
  }
113
123
 
124
+ function sessionMetadata(record) {
125
+ if (!record) return record;
126
+ const { version, id, storageMode, publicKey, homeserver, grantId, clientId,
127
+ capabilities, grantExpiresAt, createdAt } = record;
128
+ return { version, id, storageMode, publicKey, homeserver, grantId, clientId,
129
+ capabilities, grantExpiresAt, createdAt,
130
+ hasStoredApproval: record.signedApproval !== undefined,
131
+ };
132
+ }
133
+
134
+ async function __pubkySessionStoreMetadata(id) {
135
+ return sessionMetadata(await __pubkySessionStoreGet(id));
136
+ }
137
+
114
138
  /** Load a browser session record by id. */
115
139
  async function __pubkySessionStoreGet(id) {
116
140
  requireIndexedDb();
117
141
  try {
118
- return await withSessionStore("readonly", (store) => store.get(id));
142
+ return await withSessionStores("readonly", tx =>
143
+ tx.objectStore(PUBKY_SESSIONS_STORE_NAME).get(id),
144
+ );
119
145
  } catch (error) {
120
146
  throw contextualSessionStoreError("Reading Pubky session failed.", error);
121
147
  }
@@ -125,7 +151,10 @@ async function __pubkySessionStoreGet(id) {
125
151
  async function __pubkySessionStoreList() {
126
152
  if (!globalThis.indexedDB) return [];
127
153
  try {
128
- return (await withSessionStore("readonly", (store) => store.getAll())) ?? [];
154
+ const records = await withSessionStores("readonly", tx =>
155
+ tx.objectStore(PUBKY_SESSIONS_STORE_NAME).getAll(),
156
+ );
157
+ return records.map(sessionMetadata);
129
158
  } catch (_error) {
130
159
  return [];
131
160
  }
@@ -223,9 +252,9 @@ async function __pubkySharedSessionStore(token, sharedSession) {
223
252
  const lease = requireSessionLease(token, true);
224
253
  const record = await __pubkySessionStoreGet(lease.id);
225
254
  if (!record || record.homeserver !== lease.homeserver) throw new Error("Browser session was removed or changed.");
226
- await withSessionStore("readwrite", store => {
255
+ await withSessionStores("readwrite", tx => {
227
256
  requireSessionLease(token, true);
228
- return store.put({ ...record, sharedSession });
257
+ return tx.objectStore(PUBKY_SESSIONS_STORE_NAME).put({ ...record, sharedSession });
229
258
  });
230
259
  }
231
260
  async function __pubkySharedSessionRemove(token) {
@@ -504,14 +533,42 @@ async function __pubkyGrantLoadDelegatedPublicKey(keyId) {
504
533
  * bytes are returned to Rust, which finishes compact JWS serialization.
505
534
  */
506
535
  async function __pubkyGrantDelegatedSign(keyId, signingInput) {
507
- requireBrowserCrypto();
508
- const existing = await getRecord(keyId);
509
- if (!existing?.privateKey) throw new Error(`Delegated grant key not found: ${keyId}`);
536
+ let existing;
537
+ try {
538
+ requireBrowserCrypto();
539
+ existing = await getRecord(keyId);
540
+ if (!existing?.privateKey) throw new Error(`Delegated grant key not found: ${keyId}`);
541
+ } catch (cause) {
542
+ // getRecord wraps IndexedDB failures; retain the underlying diagnostic.
543
+ const detail = cause?.cause ?? cause;
544
+ const error = contextualError(
545
+ `Delegated grant signing key unavailable: ${detail?.message ?? String(detail)}`,
546
+ cause,
547
+ );
548
+ error.code = "SigningKeyUnavailable";
549
+ throw error;
550
+ }
510
551
  const data = new TextEncoder().encode(signingInput);
511
552
  return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, existing.privateKey, data));
512
553
  }
513
554
 
514
555
 
556
+ function __pubkyRecoveredEncryptionKeys(publicKey, grantId, keys) {
557
+ return { publicKey, grantId, keys };
558
+ }
559
+
560
+ async function __pubkyImportEncryptionCryptoKey(bytes) {
561
+ try {
562
+ if (!globalThis.crypto?.subtle) {
563
+ throw new Error("WebCrypto is unavailable; use a secure context.");
564
+ }
565
+ return await globalThis.crypto.subtle.importKey(
566
+ "raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
567
+ } finally {
568
+ bytes.fill(0);
569
+ }
570
+ }
571
+
515
572
  /**
516
573
  * Start and control a pubkyauth authorization flow.
517
574
  *
@@ -967,6 +1024,23 @@ class BrowserSessionStore {
967
1024
  const ret = wasm.browsersessionstore_remove(this.__wbg_ptr, ptr0, len0);
968
1025
  return ret;
969
1026
  }
1027
+ /**
1028
+ * Recover a stored session's encryption keys without network access.
1029
+ * Verifies the signed approval, exact grant binding, and requested session
1030
+ * identity. Works after grant expiry/revocation and without the delegated
1031
+ * WebCrypto signing key.
1032
+ * Restore material is stored as plaintext in IndexedDB.
1033
+ * Bare-grant records return undefined; signed approvals without `e` scopes
1034
+ * return an empty bundle. Creates no session.
1035
+ * @param {string} id
1036
+ * @returns {Promise<EncryptionKeys | undefined>}
1037
+ */
1038
+ restoreEncryptionKeys(id) {
1039
+ const ptr0 = passStringToWasm0(id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1040
+ const len0 = WASM_VECTOR_LEN;
1041
+ const ret = wasm.browsersessionstore_restoreEncryptionKeys(this.__wbg_ptr, ptr0, len0);
1042
+ return ret;
1043
+ }
970
1044
  /**
971
1045
  * Restore a specific stored session by id.
972
1046
  *
@@ -982,7 +1056,11 @@ class BrowserSessionStore {
982
1056
  return ret;
983
1057
  }
984
1058
  /**
985
- * Persist a completed grant session in IndexedDB.
1059
+ * Persist a completed grant session and its optional signed approval.
1060
+ * Confidential restore material is stored as plaintext in IndexedDB.
1061
+ * Delegated signing keys remain non-extractable WebCrypto keys.
1062
+ * Records are identified by user and grant ID. Reauthorizing the same
1063
+ * client creates a separate record; use the returned ID to restore it.
986
1064
  * @param {Session} session
987
1065
  * @returns {Promise<StoredSessionInfo>}
988
1066
  */
@@ -1257,6 +1335,90 @@ class DirectSignupDeepLink {
1257
1335
  if (Symbol.dispose) DirectSignupDeepLink.prototype[Symbol.dispose] = DirectSignupDeepLink.prototype.free;
1258
1336
  exports.DirectSignupDeepLink = DirectSignupDeepLink;
1259
1337
 
1338
+ /**
1339
+ * Verified scoped encryption keys obtained from a session or offline recovery.
1340
+ * This object owns its secrets independently of the session and grants no storage
1341
+ * access. Call `free()` when finished; secret bytes are zeroized on free/drop.
1342
+ */
1343
+ class EncryptionKeys {
1344
+ static __wrap(ptr) {
1345
+ const obj = Object.create(EncryptionKeys.prototype);
1346
+ obj.__wbg_ptr = ptr;
1347
+ EncryptionKeysFinalization.register(obj, obj.__wbg_ptr, obj);
1348
+ return obj;
1349
+ }
1350
+ __destroy_into_raw() {
1351
+ const ptr = this.__wbg_ptr;
1352
+ this.__wbg_ptr = 0;
1353
+ EncryptionKeysFinalization.unregister(this);
1354
+ return ptr;
1355
+ }
1356
+ free() {
1357
+ const ptr = this.__destroy_into_raw();
1358
+ wasm.__wbg_encryptionkeys_free(ptr, 0);
1359
+ }
1360
+ /**
1361
+ * Derive a non-extractable AES-GCM-256 key for WebCrypto encryption/decryption.
1362
+ * Rejects directories and files outside approved scopes. Requires WebCrypto.
1363
+ * Temporary JS key bytes are cleared after import, including on failure.
1364
+ * @param {string} path
1365
+ * @returns {Promise<CryptoKey>}
1366
+ */
1367
+ deriveCryptoKeyForPath(path) {
1368
+ const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1369
+ const len0 = WASM_VECTOR_LEN;
1370
+ const ret = wasm.encryptionkeys_deriveCryptoKeyForPath(this.__wbg_ptr, ptr0, len0);
1371
+ return ret;
1372
+ }
1373
+ /**
1374
+ * Derive a 32-byte content key for a canonical decoded file path.
1375
+ * Rejects directories and files outside the approved scopes. The caller
1376
+ * owns the JS byte copy and should clear it after use.
1377
+ * @param {string} path
1378
+ * @returns {Uint8Array}
1379
+ */
1380
+ deriveForPath(path) {
1381
+ const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1382
+ const len0 = WASM_VECTOR_LEN;
1383
+ const ret = wasm.encryptionkeys_deriveForPath(this.__wbg_ptr, ptr0, len0);
1384
+ if (ret[2]) {
1385
+ throw takeFromExternrefTable0(ret[1]);
1386
+ }
1387
+ return takeFromExternrefTable0(ret[0]);
1388
+ }
1389
+ /**
1390
+ * Recover authenticated identity and keys from an exportLocalSecret() token offline.
1391
+ * Verifies the signed approval and grant binding even if the grant expired
1392
+ * or was revoked. Bare-grant tokens return undefined; signed approvals
1393
+ * without `e` scopes return a result with an empty key bundle.
1394
+ * Check publicKey (z-base-32) against the expected account and grantId
1395
+ * against the expected grant before using keys. Call keys.free() when done.
1396
+ * @param {string} token
1397
+ * @returns {RecoveredEncryptionKeys | undefined}
1398
+ */
1399
+ static fromLocalSecret(token) {
1400
+ const ptr0 = passStringToWasm0(token, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1401
+ const len0 = WASM_VECTOR_LEN;
1402
+ const ret = wasm.encryptionkeys_fromLocalSecret(ptr0, len0);
1403
+ if (ret[2]) {
1404
+ throw takeFromExternrefTable0(ret[1]);
1405
+ }
1406
+ return takeFromExternrefTable0(ret[0]);
1407
+ }
1408
+ /**
1409
+ * Approved scopes; directory scopes cover descendant files.
1410
+ * @returns {string[]}
1411
+ */
1412
+ get scopes() {
1413
+ const ret = wasm.encryptionkeys_scopes(this.__wbg_ptr);
1414
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
1415
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
1416
+ return v1;
1417
+ }
1418
+ }
1419
+ if (Symbol.dispose) EncryptionKeys.prototype[Symbol.dispose] = EncryptionKeys.prototype.free;
1420
+ exports.EncryptionKeys = EncryptionKeys;
1421
+
1260
1422
  /**
1261
1423
  * A single event from the event stream.
1262
1424
  *
@@ -1678,7 +1840,8 @@ class GrantAuthFlow {
1678
1840
  * Resume a previously saved pending grant auth flow (standalone).
1679
1841
  * Prefer `pubky.resumeGrantAuthFlow()` to reuse a facade client.
1680
1842
  *
1681
- * **Security:** `savedState` contains the relay secret and PoP client private key.
1843
+ * **Security:** `savedState` contains the PoP client private key and either
1844
+ * a shared relay secret or a temporary HPKE recipient secret.
1682
1845
  * Store it only temporarily and delete it once the flow completes or is abandoned.
1683
1846
  *
1684
1847
  * @param {string} savedState A string produced by `grantFlow.saveLocal()`.
@@ -1698,9 +1861,9 @@ class GrantAuthFlow {
1698
1861
  /**
1699
1862
  * Save sensitive state required to resume this delegated pending grant flow.
1700
1863
  *
1701
- * This does not export the delegated private key, but it includes the relay
1702
- * secret in the authorization URL. Store it only temporarily and delete it
1703
- * once the flow completes or is abandoned.
1864
+ * This includes either a shared relay secret or a temporary HPKE recipient
1865
+ * secret. Store it only temporarily and delete it once the flow completes
1866
+ * or is abandoned.
1704
1867
  * @returns {string}
1705
1868
  */
1706
1869
  saveDelegated() {
@@ -1988,11 +2151,40 @@ class GrantSession {
1988
2151
  const ptr = this.__destroy_into_raw();
1989
2152
  wasm.__wbg_grantsession_free(ptr, 0);
1990
2153
  }
2154
+ /**
2155
+ * Sign JSON data and return both the custom proof and its root-signed grant.
2156
+ * Data follows `JSON.stringify` semantics, e.g. `undefined` properties are omitted.
2157
+ * Each proof carries its signing time (`iat`) and a random `nonce`.
2158
+ * Makes no network requests. Your applications handle freshness and replay protection.
2159
+ * @param {JsonValue} data
2160
+ * @returns {Promise<CustomPop>}
2161
+ */
2162
+ createCustomPop(data) {
2163
+ const ret = wasm.grantsession_createCustomPop(this.__wbg_ptr, data);
2164
+ return ret;
2165
+ }
2166
+ /**
2167
+ * Return scoped keys with the same derivation API as offline recovered keys.
2168
+ * Bare grants return `undefined`; signed approvals without `e` scopes
2169
+ * return an object with empty scopes.
2170
+ * Each access creates an owned copy. Keep it for repeated use and call
2171
+ * `free()` when finished. It remains usable after this view or its session
2172
+ * is freed, signed out, or revoked; freeing it does not affect the session.
2173
+ * @returns {EncryptionKeys | undefined}
2174
+ */
2175
+ get encryptionKeys() {
2176
+ const ret = wasm.grantsession_encryptionKeys(this.__wbg_ptr);
2177
+ if (ret[2]) {
2178
+ throw takeFromExternrefTable0(ret[1]);
2179
+ }
2180
+ return ret[0] === 0 ? undefined : EncryptionKeys.__wrap(ret[0]);
2181
+ }
1991
2182
  /**
1992
2183
  * Export the portable local secret material needed to restore this grant session.
1993
2184
  *
1994
2185
  * Treat the returned string as bearer-equivalent secret material until the
1995
- * grant expires or is revoked.
2186
+ * grant expires or is revoked. Included scoped keys remain sensitive after
2187
+ * expiry or revocation.
1996
2188
  *
1997
2189
  * @returns {Promise<string>}
1998
2190
  * @returns {Promise<string>}
@@ -2829,8 +3021,9 @@ class Pubky {
2829
3021
  /**
2830
3022
  * Resume a previously saved pending grant auth flow.
2831
3023
  *
2832
- * **Security:** `savedState` contains the relay secret and PoP client private key.
2833
- * Delete it from storage as soon as the resumed flow completes.
3024
+ * **Security:** `savedState` contains the PoP client private key and either
3025
+ * a shared relay secret or a temporary HPKE private key. Delete it from
3026
+ * storage when the flow completes or is abandoned.
2834
3027
  *
2835
3028
  * @param {string} savedState A string produced by `grantFlow.saveLocal()`.
2836
3029
  * @returns {GrantAuthFlow} A flow reconnected to the original relay channel.
@@ -2922,11 +3115,13 @@ class Pubky {
2922
3115
  *
2923
3116
  * Grant auth uses a user-signed grant JWS plus Proof-of-Possession and
2924
3117
  * returns a self-refreshing session.
3118
+ * Select `approvalFormat: "signedApprovalV1"` and include `e` on scopes that need content
3119
+ * keys. Storage `r` and `w` alone do not deliver encryption keys.
2925
3120
  *
2926
3121
  * @param {string} capabilities Comma-separated caps, e.g. `"/pub/app/:rw,/pub/foo/file:r"`.
2927
3122
  * @param {AuthFlowKind} kind The kind of authentication flow to perform.
2928
3123
  * @param {GrantAuthFlowOptions} options Options for the grant flow:
2929
- * `{ clientId, relay?, xCallback? }`.
3124
+ * `{ clientId, relay?, xCallback?, approvalFormat? }`.
2930
3125
  * @returns {Promise<GrantAuthFlow>}
2931
3126
  * A running grant auth flow. Show `authorizationUrl` as QR/deeplink,
2932
3127
  * then `awaitApproval()` to obtain a grant-backed `Session`.
@@ -4041,7 +4236,8 @@ class Signer {
4041
4236
  /**
4042
4237
  * Blocking sign-in. Waits for PKDNS publish to complete (slower; safer).
4043
4238
  *
4044
- * Creates a valid grant-backed homeserver Session with root capabilities.
4239
+ * Creates a grant-backed homeserver session with root storage access (`/:rw`).
4240
+ * Encryption keys require an explicit `e` request through a signed approval flow.
4045
4241
  * `clientId` is shown in the user's grant/session list.
4046
4242
  * @param {string} clientId App identifier, typically a domain.
4047
4243
  *
@@ -4078,7 +4274,8 @@ class Signer {
4078
4274
  /**
4079
4275
  * Fast sign-in for a returning user. Publishes PKDNS in the background.
4080
4276
  *
4081
- * Creates a valid grant-backed homeserver Session with root capabilities.
4277
+ * Creates a grant-backed homeserver session with root storage access (`/:rw`).
4278
+ * Encryption keys require an explicit `e` request through a signed approval flow.
4082
4279
  * `clientId` is shown in the user's grant/session list.
4083
4280
  * @param {string} clientId App identifier, typically a domain.
4084
4281
  *
@@ -4256,6 +4453,22 @@ class SigninGrantDeepLink {
4256
4453
  const ptr = this.__destroy_into_raw();
4257
4454
  wasm.__wbg_signingrantdeeplink_free(ptr, 0);
4258
4455
  }
4456
+ /**
4457
+ * Negotiated approval format: `bareGrant` or `signedApprovalV1`.
4458
+ * @returns {string}
4459
+ */
4460
+ get approvalFormat() {
4461
+ let deferred1_0;
4462
+ let deferred1_1;
4463
+ try {
4464
+ const ret = wasm.signingrantdeeplink_approvalFormat(this.__wbg_ptr);
4465
+ deferred1_0 = ret[0];
4466
+ deferred1_1 = ret[1];
4467
+ return getStringFromWasm0(ret[0], ret[1]);
4468
+ } finally {
4469
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
4470
+ }
4471
+ }
4259
4472
  /**
4260
4473
  * Base HTTP relay inbox URL used by this auth request.
4261
4474
  *
@@ -4320,6 +4533,19 @@ class SigninGrantDeepLink {
4320
4533
  const ret = wasm.signingrantdeeplink_clientPublicKey(this.__wbg_ptr);
4321
4534
  return PublicKey.__wrap(ret);
4322
4535
  }
4536
+ /**
4537
+ * The 32-byte ephemeral HPKE public key from `epk`, or `undefined`
4538
+ * for a shared-secret link. The relay channel ID is the unpadded
4539
+ * base64url encoding of BLAKE3("pubky-grant-relay-channel-v1" || 0x00 || epk),
4540
+ * where `||` concatenates bytes and `epk` is the raw 32-byte public key.
4541
+ *
4542
+ * @returns {Uint8Array|undefined}
4543
+ * @returns {Uint8Array | undefined}
4544
+ */
4545
+ get ephemeralPublicKey() {
4546
+ const ret = wasm.signingrantdeeplink_ephemeralPublicKey(this.__wbg_ptr);
4547
+ return ret;
4548
+ }
4323
4549
  /**
4324
4550
  * Parse a grant signin deeplink URL.
4325
4551
  *
@@ -4339,12 +4565,11 @@ class SigninGrantDeepLink {
4339
4565
  return SigninGrantDeepLink.__wrap(ret[0]);
4340
4566
  }
4341
4567
  /**
4342
- * Relay channel secret embedded in the deeplink.
4343
- *
4344
- * Treat this as sensitive temporary auth-flow material.
4568
+ * Shared relay secret, or `undefined` for an HPKE link.
4569
+ * Keep this secret confidential until the flow completes or is abandoned.
4345
4570
  *
4346
- * @returns {Uint8Array}
4347
- * @returns {Uint8Array}
4571
+ * @returns {Uint8Array|undefined}
4572
+ * @returns {Uint8Array | undefined}
4348
4573
  */
4349
4574
  get secret() {
4350
4575
  const ret = wasm.signingrantdeeplink_secret(this.__wbg_ptr);
@@ -4522,6 +4747,22 @@ class SignupGrantDeepLink {
4522
4747
  const ptr = this.__destroy_into_raw();
4523
4748
  wasm.__wbg_signupgrantdeeplink_free(ptr, 0);
4524
4749
  }
4750
+ /**
4751
+ * Negotiated approval format: `bareGrant` or `signedApprovalV1`.
4752
+ * @returns {string}
4753
+ */
4754
+ get approvalFormat() {
4755
+ let deferred1_0;
4756
+ let deferred1_1;
4757
+ try {
4758
+ const ret = wasm.signupgrantdeeplink_approvalFormat(this.__wbg_ptr);
4759
+ deferred1_0 = ret[0];
4760
+ deferred1_1 = ret[1];
4761
+ return getStringFromWasm0(ret[0], ret[1]);
4762
+ } finally {
4763
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
4764
+ }
4765
+ }
4525
4766
  /**
4526
4767
  * Base HTTP relay inbox URL used by this auth request.
4527
4768
  *
@@ -4586,6 +4827,19 @@ class SignupGrantDeepLink {
4586
4827
  const ret = wasm.signupgrantdeeplink_clientPublicKey(this.__wbg_ptr);
4587
4828
  return PublicKey.__wrap(ret);
4588
4829
  }
4830
+ /**
4831
+ * The 32-byte ephemeral HPKE public key from `epk`, or `undefined`
4832
+ * for a shared-secret link. The relay channel ID is the unpadded
4833
+ * base64url encoding of BLAKE3("pubky-grant-relay-channel-v1" || 0x00 || epk),
4834
+ * where `||` concatenates bytes and `epk` is the raw 32-byte public key.
4835
+ *
4836
+ * @returns {Uint8Array|undefined}
4837
+ * @returns {Uint8Array | undefined}
4838
+ */
4839
+ get ephemeralPublicKey() {
4840
+ const ret = wasm.signupgrantdeeplink_ephemeralPublicKey(this.__wbg_ptr);
4841
+ return ret;
4842
+ }
4589
4843
  /**
4590
4844
  * Homeserver public key requested for account creation.
4591
4845
  *
@@ -4615,12 +4869,11 @@ class SignupGrantDeepLink {
4615
4869
  return SignupGrantDeepLink.__wrap(ret[0]);
4616
4870
  }
4617
4871
  /**
4618
- * Relay channel secret embedded in the deeplink.
4872
+ * Shared relay secret, or `undefined` for an HPKE link.
4873
+ * Keep this secret confidential until the flow completes or is abandoned.
4619
4874
  *
4620
- * Treat this as sensitive temporary auth-flow material.
4621
- *
4622
- * @returns {Uint8Array}
4623
- * @returns {Uint8Array}
4875
+ * @returns {Uint8Array|undefined}
4876
+ * @returns {Uint8Array | undefined}
4624
4877
  */
4625
4878
  get secret() {
4626
4879
  const ret = wasm.signupgrantdeeplink_secret(this.__wbg_ptr);
@@ -4984,7 +5237,8 @@ exports.setLogLevel = setLogLevel;
4984
5237
  /**
4985
5238
  * Validate and normalize a capabilities string.
4986
5239
  *
4987
- * - Normalizes action order (`wr` -> `rw`)
5240
+ * - Normalizes action order (`wr` -> `rw`, `ewr` -> `rwe`).
5241
+ * - `e` requests scoped encryption/decryption keys in V1 grant approvals.
4988
5242
  * - Throws `InvalidInput` identifying the first malformed entry.
4989
5243
  *
4990
5244
  * @param {string} input
@@ -5015,16 +5269,27 @@ function validateCapabilities(input) {
5015
5269
  }
5016
5270
  }
5017
5271
  exports.validateCapabilities = validateCapabilities;
5272
+
5273
+ /**
5274
+ * Verify both signatures, grant binding, and grant validity against the local clock.
5275
+ * Allows 30 seconds of future clock skew by default; never extends grant expiry.
5276
+ * Does not check replay, application authorization, or homeserver revocation.
5277
+ * @param {CustomPop} credentials
5278
+ * @param {CustomPopVerificationOptions | null} [options]
5279
+ * @returns {VerifiedCustomPop}
5280
+ */
5281
+ function verifyCustomGrantPop(credentials, options) {
5282
+ const ret = wasm.verifyCustomGrantPop(credentials, isLikeNone(options) ? 0 : addToExternrefTable0(options));
5283
+ if (ret[2]) {
5284
+ throw takeFromExternrefTable0(ret[1]);
5285
+ }
5286
+ return takeFromExternrefTable0(ret[0]);
5287
+ }
5288
+ exports.verifyCustomGrantPop = verifyCustomGrantPop;
5018
5289
  const import1 = {
5019
- __pubkyGrantIsDelegationAvailable,
5020
- __pubkyGrantCanUseDelegation,
5021
- __pubkyGrantEnsureDelegatedKey,
5022
- __pubkyGrantLoadDelegatedPublicKey,
5023
- __pubkyGrantDelegatedSign,
5024
- };
5025
- const import2 = {
5026
5290
  __pubkySessionStoreIsAvailable,
5027
5291
  __pubkySessionStorePut,
5292
+ __pubkySessionStoreMetadata,
5028
5293
  __pubkySessionStoreGet,
5029
5294
  __pubkySessionStoreList,
5030
5295
  __pubkySessionAcquire,
@@ -5036,9 +5301,10 @@ const import2 = {
5036
5301
  __pubkySessionStoreClear,
5037
5302
  __pubkySessionStoreClearAll,
5038
5303
  };
5039
- const import3 = {
5304
+ const import2 = {
5040
5305
  __pubkySessionStoreIsAvailable,
5041
5306
  __pubkySessionStorePut,
5307
+ __pubkySessionStoreMetadata,
5042
5308
  __pubkySessionStoreGet,
5043
5309
  __pubkySessionStoreList,
5044
5310
  __pubkySessionAcquire,
@@ -5050,9 +5316,10 @@ const import3 = {
5050
5316
  __pubkySessionStoreClear,
5051
5317
  __pubkySessionStoreClearAll,
5052
5318
  };
5053
- const import4 = {
5319
+ const import3 = {
5054
5320
  __pubkySessionStoreIsAvailable,
5055
5321
  __pubkySessionStorePut,
5322
+ __pubkySessionStoreMetadata,
5056
5323
  __pubkySessionStoreGet,
5057
5324
  __pubkySessionStoreList,
5058
5325
  __pubkySessionAcquire,
@@ -5064,9 +5331,10 @@ const import4 = {
5064
5331
  __pubkySessionStoreClear,
5065
5332
  __pubkySessionStoreClearAll,
5066
5333
  };
5067
- const import5 = {
5334
+ const import4 = {
5068
5335
  __pubkySessionStoreIsAvailable,
5069
5336
  __pubkySessionStorePut,
5337
+ __pubkySessionStoreMetadata,
5070
5338
  __pubkySessionStoreGet,
5071
5339
  __pubkySessionStoreList,
5072
5340
  __pubkySessionAcquire,
@@ -5078,7 +5346,18 @@ const import5 = {
5078
5346
  __pubkySessionStoreClear,
5079
5347
  __pubkySessionStoreClearAll,
5080
5348
  };
5349
+ const import5 = {
5350
+ __pubkyGrantIsDelegationAvailable,
5351
+ __pubkyGrantCanUseDelegation,
5352
+ __pubkyGrantEnsureDelegatedKey,
5353
+ __pubkyGrantLoadDelegatedPublicKey,
5354
+ __pubkyGrantDelegatedSign,
5355
+ };
5081
5356
  const import6 = {
5357
+ __pubkyRecoveredEncryptionKeys,
5358
+ __pubkyImportEncryptionCryptoKey,
5359
+ };
5360
+ const import7 = {
5082
5361
  __pubkyGrantIsDelegationAvailable,
5083
5362
  __pubkyGrantCanUseDelegation,
5084
5363
  __pubkyGrantEnsureDelegatedKey,
@@ -5104,7 +5383,7 @@ function __wbg_get_imports() {
5104
5383
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
5105
5384
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
5106
5385
  },
5107
- __wbg___pubkyGrantDelegatedSign_3145fede89a925f4: function(arg0, arg1, arg2, arg3) {
5386
+ __wbg___pubkyGrantDelegatedSign_45b22729433b1c79: function(arg0, arg1, arg2, arg3) {
5108
5387
  let deferred0_0;
5109
5388
  let deferred0_1;
5110
5389
  let deferred1_0;
@@ -5121,7 +5400,7 @@ function __wbg_get_imports() {
5121
5400
  wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
5122
5401
  }
5123
5402
  },
5124
- __wbg___pubkyGrantEnsureDelegatedKey_1067334e491ee176: function(arg0, arg1) {
5403
+ __wbg___pubkyGrantEnsureDelegatedKey_4f1cc4ccb5acf4e8: function(arg0, arg1) {
5125
5404
  let v0;
5126
5405
  if (arg0 !== 0) {
5127
5406
  v0 = getStringFromWasm0(arg0, arg1);
@@ -5130,7 +5409,7 @@ function __wbg_get_imports() {
5130
5409
  const ret = __pubkyGrantEnsureDelegatedKey(v0);
5131
5410
  return ret;
5132
5411
  },
5133
- __wbg___pubkyGrantLoadDelegatedPublicKey_162cec02a5e4b65f: function(arg0, arg1) {
5412
+ __wbg___pubkyGrantLoadDelegatedPublicKey_6591f0667321395a: function(arg0, arg1) {
5134
5413
  let deferred0_0;
5135
5414
  let deferred0_1;
5136
5415
  try {
@@ -5142,14 +5421,18 @@ function __wbg_get_imports() {
5142
5421
  wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
5143
5422
  }
5144
5423
  },
5145
- __wbg___pubkySessionAcquire_9cbf23c9cb4de2dd: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
5424
+ __wbg___pubkyRecoveredEncryptionKeys_7375e79d20b5b126: function(arg0, arg1, arg2, arg3, arg4) {
5425
+ const ret = __pubkyRecoveredEncryptionKeys(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3), EncryptionKeys.__wrap(arg4));
5426
+ return ret;
5427
+ },
5428
+ __wbg___pubkySessionAcquire_356f865b2001b846: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
5146
5429
  const ret = __pubkySessionAcquire(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3), arg4 !== 0);
5147
5430
  return ret;
5148
5431
  }, arguments); },
5149
- __wbg___pubkySessionRelease_61113613898f8369: function(arg0) {
5432
+ __wbg___pubkySessionRelease_3195014f66dee6ac: function(arg0) {
5150
5433
  __pubkySessionRelease(arg0 >>> 0);
5151
5434
  },
5152
- __wbg___pubkySessionStoreGet_bf3d995400545463: function(arg0, arg1) {
5435
+ __wbg___pubkySessionStoreGet_618e59d7f6ad6c2a: function(arg0, arg1) {
5153
5436
  let deferred0_0;
5154
5437
  let deferred0_1;
5155
5438
  try {
@@ -5161,23 +5444,35 @@ function __wbg_get_imports() {
5161
5444
  wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
5162
5445
  }
5163
5446
  },
5164
- __wbg___pubkySessionStorePut_a1073209d38242cc: function(arg0, arg1) {
5447
+ __wbg___pubkySessionStoreMetadata_a69b8b6778109b6e: function(arg0, arg1) {
5448
+ let deferred0_0;
5449
+ let deferred0_1;
5450
+ try {
5451
+ deferred0_0 = arg0;
5452
+ deferred0_1 = arg1;
5453
+ const ret = __pubkySessionStoreMetadata(getStringFromWasm0(arg0, arg1));
5454
+ return ret;
5455
+ } finally {
5456
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
5457
+ }
5458
+ },
5459
+ __wbg___pubkySessionStorePut_aa2189e6e7fdc513: function(arg0, arg1) {
5165
5460
  const ret = __pubkySessionStorePut(arg0, arg1 >>> 0);
5166
5461
  return ret;
5167
5462
  },
5168
- __wbg___pubkySessionWait_21c5e8e3be5de6ff: function(arg0) {
5463
+ __wbg___pubkySessionWait_a67a8077a61d91d1: function(arg0) {
5169
5464
  const ret = __pubkySessionWait(arg0 >>> 0);
5170
5465
  return ret;
5171
5466
  },
5172
- __wbg___pubkySharedSessionLoad_5cd9c74d47bf2afd: function(arg0) {
5467
+ __wbg___pubkySharedSessionLoad_4190e716399b75e0: function(arg0) {
5173
5468
  const ret = __pubkySharedSessionLoad(arg0 >>> 0);
5174
5469
  return ret;
5175
5470
  },
5176
- __wbg___pubkySharedSessionRemove_56831ee1430cf7ad: function(arg0) {
5471
+ __wbg___pubkySharedSessionRemove_f037c015c530dc4d: function(arg0) {
5177
5472
  const ret = __pubkySharedSessionRemove(arg0 >>> 0);
5178
5473
  return ret;
5179
5474
  },
5180
- __wbg___pubkySharedSessionStore_beb384d555af114b: function(arg0, arg1) {
5475
+ __wbg___pubkySharedSessionStore_3e370536d89738a5: function(arg0, arg1) {
5181
5476
  const ret = __pubkySharedSessionStore(arg0 >>> 0, arg1);
5182
5477
  return ret;
5183
5478
  },
@@ -5330,6 +5625,10 @@ function __wbg_get_imports() {
5330
5625
  const ret = arg0.done;
5331
5626
  return ret;
5332
5627
  },
5628
+ __wbg_encryptionkeys_new: function(arg0) {
5629
+ const ret = EncryptionKeys.__wrap(arg0);
5630
+ return ret;
5631
+ },
5333
5632
  __wbg_enqueue_6cb545d22db14f33: function() { return handleError(function (arg0, arg1) {
5334
5633
  arg0.enqueue(arg1);
5335
5634
  }, arguments); },
@@ -5348,11 +5647,11 @@ function __wbg_get_imports() {
5348
5647
  const ret = Event.__wrap(arg0);
5349
5648
  return ret;
5350
5649
  },
5351
- __wbg_fetch_1182c189ace424aa: function(arg0) {
5650
+ __wbg_fetch_7a37768ab67c5bd8: function(arg0) {
5352
5651
  const ret = fetch(arg0);
5353
5652
  return ret;
5354
5653
  },
5355
- __wbg_fetch_7a37768ab67c5bd8: function(arg0) {
5654
+ __wbg_fetch_8ce5bc1de67f3146: function(arg0) {
5356
5655
  const ret = fetch(arg0);
5357
5656
  return ret;
5358
5657
  },
@@ -5435,6 +5734,16 @@ function __wbg_get_imports() {
5435
5734
  const ret = result;
5436
5735
  return ret;
5437
5736
  },
5737
+ __wbg_instanceof_CryptoKey_98e09fab6f03bfea: function(arg0) {
5738
+ let result;
5739
+ try {
5740
+ result = arg0 instanceof CryptoKey;
5741
+ } catch (_) {
5742
+ result = false;
5743
+ }
5744
+ const ret = result;
5745
+ return ret;
5746
+ },
5438
5747
  __wbg_instanceof_Error_fe6fa771c78ee4cf: function(arg0) {
5439
5748
  let result;
5440
5749
  try {
@@ -5508,6 +5817,10 @@ function __wbg_get_imports() {
5508
5817
  __wbg_log_d4082f66a52944c3: function(arg0, arg1, arg2, arg3) {
5509
5818
  console.log(arg0, arg1, arg2, arg3);
5510
5819
  },
5820
+ __wbg_message_1cbc5bc03dcf1dee: function(arg0) {
5821
+ const ret = arg0.message;
5822
+ return ret;
5823
+ },
5511
5824
  __wbg_msCrypto_bd5a034af96bcba6: function(arg0) {
5512
5825
  const ret = arg0.msCrypto;
5513
5826
  return ret;
@@ -5798,17 +6111,17 @@ function __wbg_get_imports() {
5798
6111
  console.warn(arg0, arg1, arg2, arg3);
5799
6112
  },
5800
6113
  __wbindgen_generic_0000000000000001: function(arg0, arg1) {
5801
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 129, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
6114
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 125, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
5802
6115
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_6fb33c0245079662___convert__closures_____invoke___wasm_bindgen_6fb33c0245079662___JsValue__core_608f92abc48d28da___result__Result_____wasm_bindgen_6fb33c0245079662___JsError___true_);
5803
6116
  return ret;
5804
6117
  },
5805
6118
  __wbindgen_generic_0000000000000002: function(arg0, arg1) {
5806
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 267, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
6119
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 284, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
5807
6120
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_6fb33c0245079662___convert__closures_____invoke___wasm_bindgen_6fb33c0245079662___JsValue______true_);
5808
6121
  return ret;
5809
6122
  },
5810
6123
  __wbindgen_generic_0000000000000003: function(arg0, arg1) {
5811
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 269, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
6124
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 282, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
5812
6125
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_6fb33c0245079662___convert__closures_____invoke_______true_);
5813
6126
  return ret;
5814
6127
  },
@@ -5847,14 +6160,14 @@ function __wbg_get_imports() {
5847
6160
  __wbindgen_generic_000000000000000a: function(arg0, arg1) {
5848
6161
  var v0 = getArrayJsValueFromWasm0(arg0, arg1);
5849
6162
  wasm.__wbindgen_free(arg0, arg1 * 4, 4);
5850
- // Cast intrinsic for `Vector(RustStruct { name: "GrantInfo", unique_crate_identifier: "pubky-wasm-7d6e91dad805c24f" }) -> Externref`.
6163
+ // Cast intrinsic for `Vector(RustStruct { name: "GrantInfo", unique_crate_identifier: "pubky-wasm-a72b7beb1b0b81b7" }) -> Externref`.
5851
6164
  const ret = v0;
5852
6165
  return ret;
5853
6166
  },
5854
6167
  __wbindgen_generic_000000000000000b: function(arg0, arg1) {
5855
6168
  var v0 = getArrayJsValueFromWasm0(arg0, arg1);
5856
6169
  wasm.__wbindgen_free(arg0, arg1 * 4, 4);
5857
- // Cast intrinsic for `Vector(RustStruct { name: "StoredSessionInfo", unique_crate_identifier: "pubky-wasm-7d6e91dad805c24f" }) -> Externref`.
6170
+ // Cast intrinsic for `Vector(RustStruct { name: "StoredSessionInfo", unique_crate_identifier: "pubky-wasm-a72b7beb1b0b81b7" }) -> Externref`.
5858
6171
  const ret = v0;
5859
6172
  return ret;
5860
6173
  },
@@ -5871,12 +6184,13 @@ function __wbg_get_imports() {
5871
6184
  return {
5872
6185
  __proto__: null,
5873
6186
  "./pubky_bg.js": import0,
5874
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline1.js": import1,
5875
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import2,
5876
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import3,
5877
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import4,
5878
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import5,
5879
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline1.js": import6,
6187
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline0.js": import1,
6188
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline0.js": import2,
6189
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline0.js": import3,
6190
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline0.js": import4,
6191
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline1.js": import5,
6192
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline2.js": import6,
6193
+ "./snippets/pubky-wasm-a72b7beb1b0b81b7/inline1.js": import7,
5880
6194
  };
5881
6195
  }
5882
6196
 
@@ -5937,6 +6251,9 @@ const CookieSessionFinalization = (typeof FinalizationRegistry === 'undefined')
5937
6251
  const DirectSignupDeepLinkFinalization = (typeof FinalizationRegistry === 'undefined')
5938
6252
  ? { register: () => {}, unregister: () => {} }
5939
6253
  : new FinalizationRegistry(ptr => wasm.__wbg_directsignupdeeplink_free(ptr, 1));
6254
+ const EncryptionKeysFinalization = (typeof FinalizationRegistry === 'undefined')
6255
+ ? { register: () => {}, unregister: () => {} }
6256
+ : new FinalizationRegistry(ptr => wasm.__wbg_encryptionkeys_free(ptr, 1));
5940
6257
  const EventFinalization = (typeof FinalizationRegistry === 'undefined')
5941
6258
  ? { register: () => {}, unregister: () => {} }
5942
6259
  : new FinalizationRegistry(ptr => wasm.__wbg_event_free(ptr, 1));