@oxy.so/core 7.13.0 → 7.14.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.
Files changed (57) hide show
  1. package/dist/cjs/api/follows.js +12 -11
  2. package/dist/cjs/api/users.js +1 -1
  3. package/dist/cjs/crypto/aead.js +3 -3
  4. package/dist/cjs/crypto/contentHash.js +4 -4
  5. package/dist/cjs/crypto/identityDerivations.js +5 -5
  6. package/dist/cjs/crypto/identityLink.js +3 -3
  7. package/dist/cjs/crypto/identityProof.js +3 -3
  8. package/dist/cjs/crypto/index.js +2 -2
  9. package/dist/cjs/crypto/kdf.js +3 -3
  10. package/dist/cjs/crypto/polyfill.js +13 -13
  11. package/dist/cjs/crypto/recoveryPhrase.js +12 -12
  12. package/dist/cjs/crypto/registrationPow.js +4 -4
  13. package/dist/cjs/i18n/locales/compact/es-ES.generated.js +1 -1
  14. package/dist/cjs/i18n/locales/en-US.js +0 -2
  15. package/dist/cjs/i18n/locales/es-ES.js +0 -2
  16. package/dist/cjs/index.js +2 -2
  17. package/dist/cjs/utils/identityCacheSweep.js +9 -1
  18. package/dist/esm/api/follows.js +12 -11
  19. package/dist/esm/api/users.js +2 -2
  20. package/dist/esm/crypto/aead.js +1 -1
  21. package/dist/esm/crypto/contentHash.js +2 -2
  22. package/dist/esm/crypto/identityDerivations.js +2 -2
  23. package/dist/esm/crypto/identityLink.js +2 -2
  24. package/dist/esm/crypto/identityProof.js +2 -2
  25. package/dist/esm/crypto/index.js +2 -2
  26. package/dist/esm/crypto/kdf.js +2 -2
  27. package/dist/esm/crypto/polyfill.js +13 -13
  28. package/dist/esm/crypto/recoveryPhrase.js +1 -1
  29. package/dist/esm/crypto/registrationPow.js +3 -3
  30. package/dist/esm/i18n/locales/compact/es-ES.generated.js +1 -1
  31. package/dist/esm/i18n/locales/en-US.js +0 -2
  32. package/dist/esm/i18n/locales/es-ES.js +0 -2
  33. package/dist/esm/index.js +2 -2
  34. package/dist/esm/utils/identityCacheSweep.js +8 -1
  35. package/dist/types/crypto/polyfill.d.ts +13 -13
  36. package/dist/types/crypto/registrationPow.d.ts +1 -1
  37. package/dist/types/i18n/locales/en-US.d.ts +0 -2
  38. package/dist/types/i18n/locales/es-ES.d.ts +0 -2
  39. package/dist/types/utils/identityCacheSweep.d.ts +5 -0
  40. package/package.json +8 -8
  41. package/src/api/follows.ts +21 -12
  42. package/src/api/users.ts +6 -2
  43. package/src/crypto/aead.ts +1 -1
  44. package/src/crypto/contentHash.ts +2 -2
  45. package/src/crypto/identityDerivations.ts +2 -2
  46. package/src/crypto/identityLink.ts +2 -2
  47. package/src/crypto/identityProof.ts +2 -2
  48. package/src/crypto/index.ts +2 -2
  49. package/src/crypto/kdf.ts +2 -2
  50. package/src/crypto/polyfill.ts +13 -13
  51. package/src/crypto/recoveryPhrase.ts +1 -1
  52. package/src/crypto/registrationPow.ts +3 -3
  53. package/src/i18n/locales/compact/es-ES.generated.ts +1 -1
  54. package/src/i18n/locales/en-US.ts +0 -2
  55. package/src/i18n/locales/es-ES.ts +0 -2
  56. package/src/index.ts +2 -2
  57. package/src/utils/identityCacheSweep.ts +9 -1
@@ -1895,7 +1895,6 @@ const dict = {
1895
1895
  transfer: 'Transfer ownership',
1896
1896
  },
1897
1897
  errors: {
1898
- emailRequired: 'Enter an email to invite.',
1899
1898
  emailInvalid: 'Enter a valid email address, like name@example.com.',
1900
1899
  missingAccount: 'No account selected.',
1901
1900
  noPermission: 'You do not have permission to view members.',
@@ -2332,7 +2331,6 @@ const dict = {
2332
2331
  newLabel: 'New password',
2333
2332
  repeatLabel: 'Repeat the password',
2334
2333
  tooShort: 'Use at least {{min}} characters.',
2335
- tooLong: 'Use at most {{max}} characters.',
2336
2334
  mismatch: "The passwords don't match.",
2337
2335
  signOutOthers: 'Sign out everywhere else',
2338
2336
  save: 'Save password',
@@ -1895,7 +1895,6 @@ const dict = {
1895
1895
  transfer: 'Transferir propiedad',
1896
1896
  },
1897
1897
  errors: {
1898
- emailRequired: 'Introduce un email para invitar.',
1899
1898
  emailInvalid: 'Introduce un email válido, como nombre@ejemplo.com.',
1900
1899
  missingAccount: 'Ninguna cuenta seleccionada.',
1901
1900
  noPermission: 'No tienes permiso para ver los miembros.',
@@ -2332,7 +2331,6 @@ const dict = {
2332
2331
  newLabel: 'Contraseña nueva',
2333
2332
  repeatLabel: 'Repite la contraseña',
2334
2333
  tooShort: 'Usa al menos {{min}} caracteres.',
2335
- tooLong: 'Usa como máximo {{max}} caracteres.',
2336
2334
  mismatch: 'Las contraseñas no coinciden.',
2337
2335
  signOutOthers: 'Cerrar sesión en todos los demás sitios',
2338
2336
  save: 'Guardar contraseña',
package/dist/esm/index.js CHANGED
@@ -22,8 +22,8 @@
22
22
  // The crypto shim MUST stay the first import. The root itself ships no crypto
23
23
  // library (`__tests__/rootEntryGraph.test.ts`), but `@oxy.so/protocol`'s root
24
24
  // entry (reached by the OAuth/PKCE and device helpers) does, and Metro does not
25
- // tree-shake: on Hermes, `@noble/hashes` would capture an absent
26
- // `globalThis.crypto` at evaluation. See the note in `./crypto/polyfill`.
25
+ // tree-shake: on Hermes, the shim has to exist before any of it evaluates. See
26
+ // the note in `./crypto/polyfill`.
27
27
  import './crypto/polyfill.js';
28
28
  // ---------------------------------------------------------------------------
29
29
  // API client
@@ -64,12 +64,19 @@ export const OXY_IDENTITY_CACHE_PREFIXES = [
64
64
  // `resolveProfile` — keyed by fediverse handle in the query payload.
65
65
  'GET:/profiles/resolve',
66
66
  ];
67
+ /**
68
+ * The path `getUserById` reads, its id encoded. Built in one place so the
69
+ * request and every key that evicts it cannot spell the id differently.
70
+ */
71
+ export function oxyUserByIdPath(userId) {
72
+ return `/users/${encodeURIComponent(userId)}`;
73
+ }
67
74
  /**
68
75
  * Build the exact cache key `getUserById` reads under. The only identity key
69
76
  * derivable from a user id, so the only one that does not need a prefix sweep.
70
77
  */
71
78
  export function oxyUserByIdCacheKey(userId) {
72
- return `GET:/users/${userId}`;
79
+ return `GET:${oxyUserByIdPath(userId)}`;
73
80
  }
74
81
  /**
75
82
  * Sweep an `OxyServices` GET response cache of everything that could carry the
@@ -17,20 +17,20 @@
17
17
  * global WebCrypto, and a `require('node:crypto')` here made web bundlers ship
18
18
  * Node's crypto, stream and buffer polyfills (~600 KB) to every browser.
19
19
  *
20
- * # Evaluation order — this module must import NOTHING that can capture the global
20
+ * # Evaluation order — this module must import NOTHING that reaches a crypto library
21
21
  *
22
- * `@noble/hashes` 1.x reads `globalThis.crypto` exactly once, when its
23
- * `crypto.js` is evaluated, and `randomBytes` uses that captured binding for
24
- * the lifetime of the app. ES imports are evaluated before the importing
25
- * module's body, so every module this file imports runs BEFORE the shim below
26
- * is installed. This file used to import `@oxy.so/protocol`'s root entry,
27
- * which reaches `@noble/curves` → `@noble/hashes` through the envelope signer:
28
- * on Hermes (no `globalThis.crypto` at startup) noble captured `undefined`
29
- * before the shim existed, and every identity creation on Android failed with
30
- * `crypto.getRandomValues must be defined`. Its imports are therefore limited
31
- * to `buffer` and `@oxy.so/protocol/random`, both free of crypto libraries,
32
- * and `index.ts` imports this file first. `__tests__/polyfillOrder.test.ts`
33
- * guards both halves.
22
+ * ES imports are evaluated before the importing module's body, so every module
23
+ * this file imports runs BEFORE the shim below is installed. This file used to
24
+ * import `@oxy.so/protocol`'s root entry, which reaches `@noble/curves` →
25
+ * `@noble/hashes` through the envelope signer, and `@noble/hashes` 1.x read
26
+ * `globalThis.crypto` once, at evaluation: on Hermes (no `globalThis.crypto` at
27
+ * startup) it captured `undefined` before the shim existed, and every identity
28
+ * creation on Android failed with `crypto.getRandomValues must be defined`.
29
+ * Noble 2.x reads the global on every call, so that capture is gone, but the
30
+ * shim still has to exist before any module body that reads it runs. Its
31
+ * imports are therefore limited to `buffer` and `@oxy.so/protocol/random`,
32
+ * both free of crypto libraries, and `index.ts` imports this file first.
33
+ * `__tests__/polyfillOrder.test.ts` guards both halves.
34
34
  *
35
35
  */
36
36
  import { Buffer } from 'buffer';
@@ -11,7 +11,7 @@ import './polyfill';
11
11
  * Grind a nonce for `publicKey`/`timestamp` that clears
12
12
  * {@link REGISTRATION_POW_DIFFICULTY_BITS}, returned as a decimal counter.
13
13
  *
14
- * Hashes with `@noble/hashes/sha256` — pure JS, synchronous, identical output
14
+ * Hashes with `@noble/hashes/sha2.js`'s `sha256` — pure JS, synchronous, identical output
15
15
  * on web/Node/React Native with no native module and no per-attempt bridge
16
16
  * crossing (see the difficulty constant's own doc for why that distinction is
17
17
  * the whole point). Nonces are sequential (0, 1, 2, …) rather than random: the
@@ -1886,7 +1886,6 @@ declare const dict: {
1886
1886
  transfer: string;
1887
1887
  };
1888
1888
  errors: {
1889
- emailRequired: string;
1890
1889
  emailInvalid: string;
1891
1890
  missingAccount: string;
1892
1891
  noPermission: string;
@@ -2323,7 +2322,6 @@ declare const dict: {
2323
2322
  newLabel: string;
2324
2323
  repeatLabel: string;
2325
2324
  tooShort: string;
2326
- tooLong: string;
2327
2325
  mismatch: string;
2328
2326
  signOutOthers: string;
2329
2327
  save: string;
@@ -1886,7 +1886,6 @@ declare const dict: {
1886
1886
  transfer: string;
1887
1887
  };
1888
1888
  errors: {
1889
- emailRequired: string;
1890
1889
  emailInvalid: string;
1891
1890
  missingAccount: string;
1892
1891
  noPermission: string;
@@ -2323,7 +2322,6 @@ declare const dict: {
2323
2322
  newLabel: string;
2324
2323
  repeatLabel: string;
2325
2324
  tooShort: string;
2326
- tooLong: string;
2327
2325
  mismatch: string;
2328
2326
  signOutOthers: string;
2329
2327
  save: string;
@@ -65,6 +65,11 @@ export interface OxyIdentityCacheEvictor {
65
65
  * reads whose key cannot be derived from a user id. Swept wholesale.
66
66
  */
67
67
  export declare const OXY_IDENTITY_CACHE_PREFIXES: readonly string[];
68
+ /**
69
+ * The path `getUserById` reads, its id encoded. Built in one place so the
70
+ * request and every key that evicts it cannot spell the id differently.
71
+ */
72
+ export declare function oxyUserByIdPath(userId: string): string;
68
73
  /**
69
74
  * Build the exact cache key `getUserById` reads under. The only identity key
70
75
  * derivable from a user id, so the only one that does not need a prefix sweep.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "7.13.0",
3
+ "version": "7.14.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -160,7 +160,7 @@
160
160
  "license": "SEE LICENSE IN LICENSE",
161
161
  "homepage": "https://oxy.so",
162
162
  "engines": {
163
- "node": ">=20.0.0"
163
+ "node": "^20.19.0 || >=22.12.0"
164
164
  },
165
165
  "scripts": {
166
166
  "build": "bun run check:compact-locales && node scripts/build-workspace-deps.mjs @oxy.so/telemetry && bun run build:cjs && bun run build:esm && bun run build:types && node scripts/mark-module-formats.mjs && node -e \"const p='../app-preset/metro/source-stamp.js';require('node:fs').existsSync(p)&&require(p).writeStamp(process.cwd())\"",
@@ -179,12 +179,12 @@
179
179
  "test:bun": "cd bun-tests && bun test"
180
180
  },
181
181
  "dependencies": {
182
- "@noble/ciphers": "^1.3.0",
183
- "@noble/hashes": "^1.8.0",
182
+ "@noble/ciphers": "^2.4.0",
183
+ "@noble/hashes": "^2.4.0",
184
184
  "@oxy.so/contracts": "6.5.0",
185
- "@oxy.so/protocol": "^1.5.1",
185
+ "@oxy.so/protocol": "^1.6.0",
186
186
  "@oxy.so/telemetry": "^2.1.0",
187
- "@scure/bip39": "^1.6.0",
187
+ "@scure/bip39": "^2.4.0",
188
188
  "buffer": "^6.0.3",
189
189
  "express-rate-limit": "^8.6.0",
190
190
  "helmet": "^8.0.0",
@@ -195,7 +195,7 @@
195
195
  "zod": "^4.6.7"
196
196
  },
197
197
  "peerDependencies": {
198
- "@react-native-async-storage/async-storage": "<3.0.0",
198
+ "@react-native-async-storage/async-storage": "<4.0.0",
199
199
  "expo-crypto": "<59.0.0",
200
200
  "expo-secure-store": "<59.0.0",
201
201
  "express": "^4.0.0",
@@ -223,7 +223,7 @@
223
223
  }
224
224
  },
225
225
  "devDependencies": {
226
- "@react-native-async-storage/async-storage": "^2.2.0",
226
+ "@react-native-async-storage/async-storage": "^3.1.1",
227
227
  "@types/express": "^4.17.25",
228
228
  "@types/invariant": "^2.2.34",
229
229
  "@types/node": "^20.19.43",
@@ -27,8 +27,7 @@ import {
27
27
  buildUrl,
28
28
  type FollowGraphParams,
29
29
  } from '../utils/apiUtils';
30
-
31
- const enc = encodeURIComponent;
30
+ import { oxyUserByIdCacheKey, oxyUserByIdPath } from '../utils/identityCacheSweep';
32
31
 
33
32
  const GRAPH_TTL = 2 * 60 * 1000;
34
33
  const STATUS_TTL = 60 * 1000;
@@ -132,7 +131,7 @@ export class FollowsApi {
132
131
  async follow(userId: string): Promise<FollowMutationResult> {
133
132
  const result = await this.ctx.request<FollowMutationResult>(
134
133
  'POST',
135
- `/users/${enc(userId)}/follow`,
134
+ `${oxyUserByIdPath(userId)}/follow`,
136
135
  undefined,
137
136
  { cache: false },
138
137
  );
@@ -157,7 +156,7 @@ export class FollowsApi {
157
156
  async unfollow(userId: string): Promise<FollowMutationResult> {
158
157
  const result = await this.ctx.request<FollowMutationResult>(
159
158
  'DELETE',
160
- `/users/${enc(userId)}/follow`,
159
+ `${oxyUserByIdPath(userId)}/follow`,
161
160
  undefined,
162
161
  { cache: false },
163
162
  );
@@ -180,7 +179,7 @@ export class FollowsApi {
180
179
 
181
180
  /** Whether the signed-in user follows `userId`. Cached 1 minute, busted by every write. */
182
181
  async status(userId: string): Promise<{ isFollowing: boolean }> {
183
- return this.ctx.request('GET', `/users/${enc(userId)}/follow-status`, undefined, {
182
+ return this.ctx.request('GET', `${oxyUserByIdPath(userId)}/follow-status`, undefined, {
184
183
  cache: true,
185
184
  cacheTTL: STATUS_TTL,
186
185
  });
@@ -223,13 +222,19 @@ export class FollowsApi {
223
222
  * `limit`/`offset`/`sort` combination is its own cache entry (2 minutes).
224
223
  */
225
224
  async followers(userId: string, params?: FollowGraphParams): Promise<UserPage<'followers'>> {
226
- const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/followers`, params);
225
+ const { data, total, hasMore } = await this.userPage(
226
+ `${oxyUserByIdPath(userId)}/followers`,
227
+ params,
228
+ );
227
229
  return { followers: data, total, hasMore };
228
230
  }
229
231
 
230
232
  /** Who a user follows. `sort` as in `followers`. */
231
233
  async following(userId: string, params?: FollowGraphParams): Promise<UserPage<'following'>> {
232
- const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/following`, params);
234
+ const { data, total, hasMore } = await this.userPage(
235
+ `${oxyUserByIdPath(userId)}/following`,
236
+ params,
237
+ );
233
238
  return { following: data, total, hasMore };
234
239
  }
235
240
 
@@ -238,7 +243,10 @@ export class FollowsApi {
238
243
  * `userId`.
239
244
  */
240
245
  async mutuals(userId: string, params?: FollowGraphParams): Promise<UserPage<'mutuals'>> {
241
- const { data, total, hasMore } = await this.userPage(`/users/${enc(userId)}/mutuals`, params);
246
+ const { data, total, hasMore } = await this.userPage(
247
+ `${oxyUserByIdPath(userId)}/mutuals`,
248
+ params,
249
+ );
242
250
  return { mutuals: data, total, hasMore };
243
251
  }
244
252
 
@@ -445,15 +453,16 @@ export class FollowsApi {
445
453
  const keys: string[] = ['GET:/users/me/graph'];
446
454
  const prefixes: string[] = ['GET:/profiles/username/', 'GET:/profiles/resolve'];
447
455
  for (const id of targetUserIds) {
448
- keys.push(`GET:/users/${enc(id)}/follow-status`);
456
+ const user = oxyUserByIdPath(id);
457
+ keys.push(`GET:${user}/follow-status`);
449
458
  // Profiles embed the viewer-relative `relationship`.
450
- keys.push(`GET:/users/${enc(id)}`);
459
+ keys.push(oxyUserByIdCacheKey(id));
451
460
  // The target gained or lost a follower, and "followers you know" with it.
452
- prefixes.push(`GET:/users/${enc(id)}/followers`, `GET:/users/${enc(id)}/mutuals`);
461
+ prefixes.push(`GET:${user}/followers`, `GET:${user}/mutuals`);
453
462
  }
454
463
  // The viewer's own following list changed.
455
464
  const viewerId = this.ctx.oxy.session.userId;
456
- if (viewerId) prefixes.push(`GET:/users/${enc(viewerId)}/following`);
465
+ if (viewerId) prefixes.push(`GET:${oxyUserByIdPath(viewerId)}/following`);
457
466
  this.ctx.http.invalidateCache({ keys, prefixes });
458
467
  }
459
468
  }
package/src/api/users.ts CHANGED
@@ -12,7 +12,11 @@ import type { OxyContext } from '../client/context';
12
12
  import type { PaginationInfo, SearchProfilesResponse, User } from '../models/interfaces';
13
13
  import { buildQueryParams, type PaginationParams } from '../utils/apiUtils';
14
14
  import { normalizeUserIdentity, normalizeUserIdentityOrNull } from '../utils/userIdentity';
15
- import { OXY_IDENTITY_CACHE_PREFIXES, oxyUserByIdCacheKey } from '../utils/identityCacheSweep';
15
+ import {
16
+ OXY_IDENTITY_CACHE_PREFIXES,
17
+ oxyUserByIdCacheKey,
18
+ oxyUserByIdPath,
19
+ } from '../utils/identityCacheSweep';
16
20
  import {
17
21
  OXY_ACCOUNT_LIST_CACHE_KEY,
18
22
  OXY_ACCOUNT_LIST_CACHE_QUERY_PREFIX,
@@ -67,7 +71,7 @@ export class UsersApi {
67
71
  * registry-fresh read that neither serves nor overwrites a cached entry.
68
72
  */
69
73
  async get(userId: string, options?: { cache?: boolean }): Promise<User> {
70
- const user = await this.ctx.request<User>('GET', `/users/${enc(userId)}`, undefined, {
74
+ const user = await this.ctx.request<User>('GET', oxyUserByIdPath(userId), undefined, {
71
75
  cache: options?.cache ?? true,
72
76
  cacheTTL: PROFILE_TTL,
73
77
  });
@@ -23,7 +23,7 @@
23
23
  // Loading the polyfill guarantees `globalThis.crypto.getRandomValues` exists on
24
24
  // every platform (native crypto on web/Node; expo-crypto-backed shim on RN).
25
25
  import './polyfill';
26
- import { xchacha20poly1305 } from '@noble/ciphers/chacha';
26
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
27
27
 
28
28
  /** Key length for XChaCha20-Poly1305, in bytes (256-bit). */
29
29
  export const AEAD_KEY_LENGTH = 32;
@@ -6,8 +6,8 @@
6
6
  * directly: `@noble/hashes` must load after this entry's polyfill, and through
7
7
  * the ONE lazy root, so it never lands in an app's startup JavaScript.
8
8
  */
9
- import { sha256 } from '@noble/hashes/sha256';
10
- import { bytesToHex } from '@noble/hashes/utils';
9
+ import { sha256 } from '@noble/hashes/sha2.js';
10
+ import { bytesToHex } from '@noble/hashes/utils.js';
11
11
 
12
12
  export interface ContentHasher {
13
13
  update(chunk: Uint8Array): void;
@@ -11,8 +11,8 @@
11
11
  * ESM/CJS safe: static `import` only, no `require()`.
12
12
  */
13
13
 
14
- import { hmac } from '@noble/hashes/hmac';
15
- import { sha256, sha512 } from '@noble/hashes/sha2';
14
+ import { hmac } from '@noble/hashes/hmac.js';
15
+ import { sha256, sha512 } from '@noble/hashes/sha2.js';
16
16
  import {
17
17
  deriveSecp256k1PublicKey,
18
18
  normalizeSecp256k1PrivateKey,
@@ -6,8 +6,8 @@
6
6
  * confirmed, so a photographed QR cannot slip another key in.
7
7
  */
8
8
 
9
- import { sha256 } from '@noble/hashes/sha256';
10
- import { utf8ToBytes } from '@noble/hashes/utils';
9
+ import { sha256 } from '@noble/hashes/sha2.js';
10
+ import { utf8ToBytes } from '@noble/hashes/utils.js';
11
11
 
12
12
  const DOMAIN = 'oxy-identity-link-code/v1';
13
13
 
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  import './polyfill';
10
- import { sha256 } from '@noble/hashes/sha256';
11
- import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
10
+ import { sha256 } from '@noble/hashes/sha2.js';
11
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
12
12
  import { signMessage } from '@oxy.so/protocol/sign';
13
13
  import type { IdentityProof, IdentityProofClaims } from '@oxy.so/contracts';
14
14
  import {
@@ -11,8 +11,8 @@
11
11
  * common chunk, which is the cost the lazy loading exists to avoid.
12
12
  */
13
13
 
14
- // MUST stay the first import: `@noble/hashes` captures `globalThis.crypto` when
15
- // it is evaluated, so the shim has to exist before anything below reaches it.
14
+ // MUST stay the first import: the shim has to exist before any module below
15
+ // evaluates or draws randomness (see `./polyfill`).
16
16
  import './polyfill';
17
17
 
18
18
  export {
package/src/crypto/kdf.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  * ESM/CJS safe: static `import` only, no `require()`.
11
11
  */
12
12
 
13
- import { hkdf } from '@noble/hashes/hkdf';
14
- import { sha256 } from '@noble/hashes/sha256';
13
+ import { hkdf } from '@noble/hashes/hkdf.js';
14
+ import { sha256 } from '@noble/hashes/sha2.js';
15
15
 
16
16
  /**
17
17
  * Derive `length` bytes of keying material from `ikm` using HKDF-SHA256
@@ -17,20 +17,20 @@
17
17
  * global WebCrypto, and a `require('node:crypto')` here made web bundlers ship
18
18
  * Node's crypto, stream and buffer polyfills (~600 KB) to every browser.
19
19
  *
20
- * # Evaluation order — this module must import NOTHING that can capture the global
20
+ * # Evaluation order — this module must import NOTHING that reaches a crypto library
21
21
  *
22
- * `@noble/hashes` 1.x reads `globalThis.crypto` exactly once, when its
23
- * `crypto.js` is evaluated, and `randomBytes` uses that captured binding for
24
- * the lifetime of the app. ES imports are evaluated before the importing
25
- * module's body, so every module this file imports runs BEFORE the shim below
26
- * is installed. This file used to import `@oxy.so/protocol`'s root entry,
27
- * which reaches `@noble/curves` → `@noble/hashes` through the envelope signer:
28
- * on Hermes (no `globalThis.crypto` at startup) noble captured `undefined`
29
- * before the shim existed, and every identity creation on Android failed with
30
- * `crypto.getRandomValues must be defined`. Its imports are therefore limited
31
- * to `buffer` and `@oxy.so/protocol/random`, both free of crypto libraries,
32
- * and `index.ts` imports this file first. `__tests__/polyfillOrder.test.ts`
33
- * guards both halves.
22
+ * ES imports are evaluated before the importing module's body, so every module
23
+ * this file imports runs BEFORE the shim below is installed. This file used to
24
+ * import `@oxy.so/protocol`'s root entry, which reaches `@noble/curves` →
25
+ * `@noble/hashes` through the envelope signer, and `@noble/hashes` 1.x read
26
+ * `globalThis.crypto` once, at evaluation: on Hermes (no `globalThis.crypto` at
27
+ * startup) it captured `undefined` before the shim existed, and every identity
28
+ * creation on Android failed with `crypto.getRandomValues must be defined`.
29
+ * Noble 2.x reads the global on every call, so that capture is gone, but the
30
+ * shim still has to exist before any module body that reads it runs. Its
31
+ * imports are therefore limited to `buffer` and `@oxy.so/protocol/random`,
32
+ * both free of crypto libraries, and `index.ts` imports this file first.
33
+ * `__tests__/polyfillOrder.test.ts` guards both halves.
34
34
  *
35
35
  */
36
36
 
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  import { generateMnemonic, mnemonicToSeed, validateMnemonic } from '@scure/bip39';
17
- import { wordlist } from '@scure/bip39/wordlists/english';
17
+ import { wordlist } from '@scure/bip39/wordlists/english.js';
18
18
  import { KeyManager } from './keyManager';
19
19
  import { hkdfSha256 } from './kdf';
20
20
 
@@ -8,8 +8,8 @@
8
8
  */
9
9
 
10
10
  import './polyfill';
11
- import { sha256 } from '@noble/hashes/sha256';
12
- import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
11
+ import { sha256 } from '@noble/hashes/sha2.js';
12
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
13
13
  import {
14
14
  meetsRegistrationPowDifficulty,
15
15
  registrationPowMessage,
@@ -36,7 +36,7 @@ function yieldToEventLoop(): Promise<void> {
36
36
  * Grind a nonce for `publicKey`/`timestamp` that clears
37
37
  * {@link REGISTRATION_POW_DIFFICULTY_BITS}, returned as a decimal counter.
38
38
  *
39
- * Hashes with `@noble/hashes/sha256` — pure JS, synchronous, identical output
39
+ * Hashes with `@noble/hashes/sha2.js`'s `sha256` — pure JS, synchronous, identical output
40
40
  * on web/Node/React Native with no native module and no per-attempt bridge
41
41
  * crossing (see the difficulty constant's own doc for why that distinction is
42
42
  * the whole point). Nonces are sequential (0, 1, 2, …) rather than random: the