@oxy.so/protocol 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +675 -201
  2. package/NOTICE +7 -2
  3. package/dist/cjs/.tsbuildinfo +1 -1
  4. package/dist/cjs/auth/registrationPow.js +149 -0
  5. package/dist/cjs/index.js +9 -1
  6. package/dist/cjs/platform/crypto.js +6 -10
  7. package/dist/cjs/platform/crypto.native.js +14 -46
  8. package/dist/cjs/platform/optionalPeer.js +22 -0
  9. package/dist/cjs/platform/random.js +18 -0
  10. package/dist/cjs/platform/random.native.js +52 -0
  11. package/dist/cjs/random.js +25 -0
  12. package/dist/esm/.tsbuildinfo +1 -1
  13. package/dist/esm/auth/registrationPow.js +143 -0
  14. package/dist/esm/index.js +4 -0
  15. package/dist/esm/platform/crypto.js +4 -9
  16. package/dist/esm/platform/crypto.native.js +10 -43
  17. package/dist/esm/platform/optionalPeer.js +19 -0
  18. package/dist/esm/platform/random.js +15 -0
  19. package/dist/esm/platform/random.native.js +48 -0
  20. package/dist/esm/random.js +19 -0
  21. package/dist/types/.tsbuildinfo +1 -1
  22. package/dist/types/auth/registrationPow.d.ts +102 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/platform/crypto.d.ts +1 -7
  25. package/dist/types/platform/crypto.native.d.ts +2 -10
  26. package/dist/types/platform/optionalPeer.d.ts +9 -0
  27. package/dist/types/platform/random.d.ts +13 -0
  28. package/dist/types/platform/random.native.d.ts +33 -0
  29. package/dist/types/random.d.ts +19 -0
  30. package/package.json +21 -4
  31. package/src/__tests__/randomEntry.test.ts +107 -0
  32. package/src/__tests__/registrationPow.test.ts +73 -0
  33. package/src/auth/registrationPow.ts +146 -0
  34. package/src/index.ts +10 -0
  35. package/src/platform/crypto.native.ts +10 -48
  36. package/src/platform/crypto.ts +4 -9
  37. package/src/platform/optionalPeer.ts +23 -0
  38. package/src/platform/random.native.ts +56 -0
  39. package/src/platform/random.ts +18 -0
  40. package/src/random.ts +20 -0
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Registration proof-of-work — the message format and difficulty check a
4
+ * signup grinds a nonce against, shared verbatim between the client (which
5
+ * SOLVES it) and the server (which CHECKS it).
6
+ *
7
+ * ## Why this exists
8
+ *
9
+ * `POST /auth/register` mints an account from nothing more than a freshly
10
+ * generated secp256k1 keypair and a signature over
11
+ * `oxy:register:{publicKey}:{timestamp}` — computationally free. A PoW nonce
12
+ * makes each account cost a small, real amount of CPU time to mint, without
13
+ * asking a real signer for anything beyond what they already do locally: no
14
+ * server round trip, no third-party CAPTCHA, no extra user-visible step.
15
+ *
16
+ * ## Single source, on purpose
17
+ *
18
+ * The client must SOLVE the exact message the server CHECKS, so the message
19
+ * format and the difficulty threshold live here once, in a package both
20
+ * `@oxy.so/core` (the client SDK) and the API already depend on for
21
+ * secp256k1 — see `packages/contracts/src/username.ts`'s docblock for why
22
+ * this codebase treats "the same rule declared twice" as a bug class, not a
23
+ * style preference. This file is deliberately CRYPTO-FREE: it is pure
24
+ * string/bit arithmetic over a caller-supplied hex digest, so it needs no
25
+ * platform hashing primitive and is trivially unit-testable with fixed
26
+ * digests. Each side hashes with whatever SHA-256 it already has — Node's
27
+ * `crypto` server-side (`packages/api/src/controllers/session.controller.ts`),
28
+ * `@noble/hashes/sha256` client-side (`packages/core/src/crypto/registrationPow.ts`,
29
+ * already a `@oxy.so/core` dependency — see `crypto/kdf.ts`,
30
+ * `crypto/identityProof.ts`) — and both produce byte-identical digests over
31
+ * the same message, because SHA-256 has exactly one correct output for a
32
+ * given input regardless of implementation.
33
+ *
34
+ * ## What this is NOT
35
+ *
36
+ * Not a defense against a targeted, patient attacker: nothing stops one from
37
+ * choosing their OWN `timestamp` a few minutes in the future and grinding the
38
+ * nonce ahead of time, banking a queue of pre-solved (timestamp, nonce) pairs
39
+ * to spend inside the 5-minute signature freshness window
40
+ * (`SignatureService.isTimestampFresh` / `MAX_SIGNATURE_AGE_MS`). Closing that
41
+ * needs a server-issued, single-use challenge — a real round trip and server
42
+ * state this deliberately does not add, because the goal here is raising the
43
+ * COST of unattended bulk signup, not proving liveness. Combined with a
44
+ * per-IP rate limit on `/auth/register`, it still meaningfully raises the
45
+ * price of automated mass account creation over today's zero-cost baseline.
46
+ *
47
+ * ## Rollout: advisory only, for now
48
+ *
49
+ * `POST /auth/register`'s `powNonce` field is OPTIONAL on the wire and the
50
+ * server does not yet reject a missing or failing one — see the comment at
51
+ * the check site in `packages/api/src/controllers/session.controller.ts` for
52
+ * why (every Oxy app consumes the SDK as a published package, not this
53
+ * monorepo live, so hard-enforcing before every app has picked up a client
54
+ * new enough to send it would break their signup outright) and exactly what
55
+ * to change to flip it to a hard 400.
56
+ */
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.REGISTRATION_POW_DIFFICULTY_BITS = void 0;
59
+ exports.registrationPowMessage = registrationPowMessage;
60
+ exports.countLeadingZeroBits = countLeadingZeroBits;
61
+ exports.meetsRegistrationPowDifficulty = meetsRegistrationPowDifficulty;
62
+ /**
63
+ * Leading zero BITS contributed by one lowercase hex nibble.
64
+ *
65
+ * A hex digit is 4 bits: `'0'` contributes all 4, `'8'`–`'f'` (binary `1xxx`)
66
+ * contribute none because their own leading bit is already `1`. Spelled out
67
+ * as a table rather than derived with `Math.clz32` so the mapping is checkable
68
+ * by eye against the difficulty this gates.
69
+ */
70
+ const NIBBLE_LEADING_ZERO_BITS = {
71
+ '0': 4,
72
+ '1': 3,
73
+ '2': 2,
74
+ '3': 2,
75
+ '4': 1,
76
+ '5': 1,
77
+ '6': 1,
78
+ '7': 1,
79
+ '8': 0,
80
+ '9': 0,
81
+ a: 0,
82
+ b: 0,
83
+ c: 0,
84
+ d: 0,
85
+ e: 0,
86
+ f: 0,
87
+ };
88
+ /**
89
+ * The message a registration PoW nonce is grinded against, given the same
90
+ * `publicKey`/`timestamp` the registration signature (`oxy:register:…`)
91
+ * already commits to. A distinct prefix (`register-pow`, not `register`) so a
92
+ * solved PoW nonce can never be replayed as a registration signature input or
93
+ * vice versa — the two are unrelated preimages even for the same
94
+ * `publicKey`/`timestamp` pair.
95
+ */
96
+ function registrationPowMessage(publicKey, timestamp, nonce) {
97
+ return `oxy:register-pow:${publicKey}:${timestamp}:${nonce}`;
98
+ }
99
+ /**
100
+ * The number of leading zero bits {@link meetsRegistrationPowDifficulty}
101
+ * requires of a solved nonce's digest.
102
+ *
103
+ * 16 bits ⇒ 65,536 SHA-256 attempts on average to solve. The client grinds
104
+ * with `@noble/hashes/sha256` — a pure-JS, SYNCHRONOUS implementation with
105
+ * "identical behaviour on web, Node, and React Native with zero WebCrypto /
106
+ * native-module dependency" (its own doc comment in `crypto/kdf.ts`) — so,
107
+ * unlike a solve loop built on a platform hashing primitive that crosses a
108
+ * JS↔native bridge per call (`expo-crypto`'s `digestStringAsync`, which this
109
+ * deliberately avoids for exactly that reason), there is no per-attempt
110
+ * bridge cost to budget for. The remaining uncertainty is Hermes' lack of a
111
+ * JIT, which can make a tight pure-JS loop meaningfully slower than V8 — 16
112
+ * bits is chosen to stay a sub-second grind even under that penalty, without
113
+ * reaching for a bound high enough to make Hermes specifically the deciding
114
+ * factor. Retune this constant (it is the only place the number is declared)
115
+ * once there is real on-device telemetry across the low end of the supported
116
+ * device range.
117
+ */
118
+ exports.REGISTRATION_POW_DIFFICULTY_BITS = 16;
119
+ /**
120
+ * Count the leading zero BITS of a lowercase hex digest.
121
+ *
122
+ * Stops at the first non-zero nibble — a solved nonce only ever needs to beat
123
+ * a bound in the tens of bits, so scanning the full 256-bit digest is wasted
124
+ * work. A character outside `[0-9a-f]` (not a real digest — a caller passed
125
+ * something malformed) stops the count where it is rather than throwing: the
126
+ * caller compares the result against a difficulty, and an under-count from bad
127
+ * input correctly fails that comparison instead of crashing a request path.
128
+ */
129
+ function countLeadingZeroBits(hexDigest) {
130
+ let bits = 0;
131
+ for (const char of hexDigest.toLowerCase()) {
132
+ const nibbleZeroBits = NIBBLE_LEADING_ZERO_BITS[char];
133
+ if (nibbleZeroBits === undefined)
134
+ break;
135
+ bits += nibbleZeroBits;
136
+ if (nibbleZeroBits < 4)
137
+ break;
138
+ }
139
+ return bits;
140
+ }
141
+ /**
142
+ * Whether a SHA-256 hex digest — of {@link registrationPowMessage} — clears
143
+ * `difficultyBits` leading zero bits. The one predicate both the client's
144
+ * solve loop and the server's check call, so neither can drift from what
145
+ * "solved" means.
146
+ */
147
+ function meetsRegistrationPowDifficulty(hexDigest, difficultyBits) {
148
+ return countLeadingZeroBits(hexDigest) >= difficultyBits;
149
+ }
package/dist/cjs/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * subpath so they never enter React Native / web bundles.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.loadSharedIdentityBridge = exports.getRandomBytesRN = exports.loadAsyncStorage = exports.loadSecureStore = exports.loadExpoCrypto = exports.loadNodeCrypto = exports.isNodeJS = exports.isReactNative = exports.isAuthorizedKey = exports.verifyCheckpointSignature = exports.signCheckpoint = exports.checkpointHash = exports.checkpointSigningInput = exports.verifyInclusionProof = exports.inclusionProof = exports.buildTransparencyTreeFromHeads = exports.buildTransparencyTree = exports.transparencyLeafHash = exports.EMPTY_TRANSPARENCY_ROOT = exports.verifyAndAppend = exports.DEFAULT_CLOCK_SKEW_MS = exports.verifyEnvelope = exports.checkContinuity = exports.UNCHAINED_SEQ = exports.verifyEnvelopeSignature = exports.signEnvelope = exports.verifySignature = exports.signMessage = exports.computeRecordId = exports.sha256 = exports.signedRecordSigningInput = exports.canonicalize = void 0;
14
+ exports.loadSharedIdentityBridge = exports.getRandomBytesRN = exports.loadAsyncStorage = exports.loadSecureStore = exports.loadExpoCrypto = exports.loadNodeCrypto = exports.isNodeJS = exports.isReactNative = exports.isAuthorizedKey = exports.REGISTRATION_POW_DIFFICULTY_BITS = exports.meetsRegistrationPowDifficulty = exports.countLeadingZeroBits = exports.registrationPowMessage = exports.verifyCheckpointSignature = exports.signCheckpoint = exports.checkpointHash = exports.checkpointSigningInput = exports.verifyInclusionProof = exports.inclusionProof = exports.buildTransparencyTreeFromHeads = exports.buildTransparencyTree = exports.transparencyLeafHash = exports.EMPTY_TRANSPARENCY_ROOT = exports.verifyAndAppend = exports.DEFAULT_CLOCK_SKEW_MS = exports.verifyEnvelope = exports.checkContinuity = exports.UNCHAINED_SEQ = exports.verifyEnvelopeSignature = exports.signEnvelope = exports.verifySignature = exports.signMessage = exports.computeRecordId = exports.sha256 = exports.signedRecordSigningInput = exports.canonicalize = void 0;
15
15
  // ---------------------------------------------------------------------------
16
16
  // Envelope — canonical JSON, signing input, content address, signing/verify
17
17
  // ---------------------------------------------------------------------------
@@ -52,6 +52,14 @@ Object.defineProperty(exports, "checkpointHash", { enumerable: true, get: functi
52
52
  Object.defineProperty(exports, "signCheckpoint", { enumerable: true, get: function () { return checkpoint_1.signCheckpoint; } });
53
53
  Object.defineProperty(exports, "verifyCheckpointSignature", { enumerable: true, get: function () { return checkpoint_1.verifyCheckpointSignature; } });
54
54
  // ---------------------------------------------------------------------------
55
+ // Auth — registration proof-of-work (message format + difficulty check)
56
+ // ---------------------------------------------------------------------------
57
+ var registrationPow_1 = require("./auth/registrationPow");
58
+ Object.defineProperty(exports, "registrationPowMessage", { enumerable: true, get: function () { return registrationPow_1.registrationPowMessage; } });
59
+ Object.defineProperty(exports, "countLeadingZeroBits", { enumerable: true, get: function () { return registrationPow_1.countLeadingZeroBits; } });
60
+ Object.defineProperty(exports, "meetsRegistrationPowDifficulty", { enumerable: true, get: function () { return registrationPow_1.meetsRegistrationPowDifficulty; } });
61
+ Object.defineProperty(exports, "REGISTRATION_POW_DIFFICULTY_BITS", { enumerable: true, get: function () { return registrationPow_1.REGISTRATION_POW_DIFFICULTY_BITS; } });
62
+ // ---------------------------------------------------------------------------
55
63
  // Identity — injected verification-method resolution + authorization rule
56
64
  // ---------------------------------------------------------------------------
57
65
  var resolver_1 = require("./identity/resolver");
@@ -106,11 +106,11 @@ var __importStar = (this && this.__importStar) || (function () {
106
106
  };
107
107
  })();
108
108
  Object.defineProperty(exports, "__esModule", { value: true });
109
+ exports.getRandomBytesRN = void 0;
109
110
  exports.loadNodeCrypto = loadNodeCrypto;
110
111
  exports.loadExpoCrypto = loadExpoCrypto;
111
112
  exports.loadSecureStore = loadSecureStore;
112
113
  exports.loadAsyncStorage = loadAsyncStorage;
113
- exports.getRandomBytesRN = getRandomBytesRN;
114
114
  exports.loadSharedIdentityBridge = loadSharedIdentityBridge;
115
115
  const platform_1 = require("./platform");
116
116
  // ---------------------------------------------------------------------------
@@ -164,15 +164,11 @@ async function loadSecureStore() {
164
164
  async function loadAsyncStorage() {
165
165
  throw notReactNativeError('@react-native-async-storage/async-storage');
166
166
  }
167
- /**
168
- * Synchronous random-bytes via `expo-crypto.getRandomBytes`. Only available
169
- * in the React Native variant. The default variant throws because Node and
170
- * browsers have their own native CSPRNGs (`crypto.randomBytes` and
171
- * `crypto.getRandomValues` respectively) — callers should use those.
172
- */
173
- function getRandomBytesRN(_byteCount) {
174
- throw notReactNativeError('expo-crypto.getRandomBytes (sync)');
175
- }
167
+ // Synchronous random bytes live in the dependency-free `./random` module so
168
+ // `@oxy.so/core`'s crypto polyfill can load them through the
169
+ // `@oxy.so/protocol/random` entry without evaluating any crypto library first.
170
+ var random_1 = require("./random");
171
+ Object.defineProperty(exports, "getRandomBytesRN", { enumerable: true, get: function () { return random_1.getRandomBytesRN; } });
176
172
  // ---------------------------------------------------------------------------
177
173
  // Shared identity bridge — `@oxy.so/expo-oxy-identity` (native-only).
178
174
  //
@@ -25,7 +25,7 @@
25
25
  * Those three RN modules are declared OPTIONAL peer dependencies in
26
26
  * `package.json`. A static `import` contradicts that: an optional peer that is
27
27
  * omitted does not degrade, it fails to RESOLVE, and Metro aborts the whole
28
- * bundle. Because `@oxy.so/core`'s `crypto/polyfill` imports `@oxy.so/protocol`
28
+ * bundle. Because `@oxy.so/core` imports `@oxy.so/protocol`
29
29
  * from its root entry, this file is in the eager graph of EVERY React Native
30
30
  * app on `@oxy.so/core` — so a single undeclared optional peer broke the native
31
31
  * bundle of every app that did not happen to install it, with a resolution
@@ -53,21 +53,15 @@
53
53
  * so it stays a plain static import.
54
54
  */
55
55
  Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.getRandomBytesRN = void 0;
56
57
  exports.loadNodeCrypto = loadNodeCrypto;
57
58
  exports.loadExpoCrypto = loadExpoCrypto;
58
59
  exports.loadSecureStore = loadSecureStore;
59
60
  exports.loadAsyncStorage = loadAsyncStorage;
60
- exports.getRandomBytesRN = getRandomBytesRN;
61
61
  exports.loadSharedIdentityBridge = loadSharedIdentityBridge;
62
62
  const expo_modules_core_1 = require("expo-modules-core");
63
- let expoCryptoModule = null;
64
- let expoCryptoError;
65
- try {
66
- expoCryptoModule = require('expo-crypto');
67
- }
68
- catch (error) {
69
- expoCryptoError = error;
70
- }
63
+ const optionalPeer_1 = require("./optionalPeer");
64
+ const random_native_1 = require("./random.native");
71
65
  let secureStoreModule = null;
72
66
  let secureStoreError;
73
67
  try {
@@ -88,22 +82,6 @@ try {
88
82
  catch (error) {
89
83
  asyncStorageError = error;
90
84
  }
91
- /**
92
- * Actionable error for a missing optional peer. Carries the underlying Metro
93
- * resolution message so the failure is never silent — the `catch` above only
94
- * defers the report to the point where the capability is actually needed.
95
- */
96
- function missingOptionalPeerError(packageName, capability, cause) {
97
- const sentences = [
98
- `[oxy.protocol.crypto] '${packageName}' is not installed, so ${capability} is unavailable in this app.`,
99
- 'It is an optional peer dependency of @oxy.so/protocol that the React Native runtime needs —',
100
- `install it with \`npx expo install ${packageName}\`.`,
101
- ];
102
- if (cause instanceof Error) {
103
- sentences.push(`Underlying error: ${cause.message}`);
104
- }
105
- return new Error(sentences.join(' '));
106
- }
107
85
  // ---------------------------------------------------------------------------
108
86
  // Node `crypto` — never available in RN.
109
87
  // ---------------------------------------------------------------------------
@@ -124,17 +102,14 @@ async function loadNodeCrypto() {
124
102
  // their compilation (see expoTypes.ts).
125
103
  // ---------------------------------------------------------------------------
126
104
  async function loadExpoCrypto() {
127
- if (!expoCryptoModule) {
128
- throw missingOptionalPeerError('expo-crypto', 'React Native cryptography', expoCryptoError);
129
- }
130
- return expoCryptoModule;
105
+ return (0, random_native_1.requireExpoCrypto)('React Native cryptography');
131
106
  }
132
107
  // ---------------------------------------------------------------------------
133
108
  // expo-secure-store — RN keychain / keystore.
134
109
  // ---------------------------------------------------------------------------
135
110
  async function loadSecureStore() {
136
111
  if (!secureStoreModule) {
137
- throw missingOptionalPeerError('expo-secure-store', 'on-device identity storage', secureStoreError);
112
+ throw (0, optionalPeer_1.missingOptionalPeerError)('expo-secure-store', 'on-device identity storage', secureStoreError);
138
113
  }
139
114
  return secureStoreModule;
140
115
  }
@@ -143,27 +118,20 @@ async function loadSecureStore() {
143
118
  // ---------------------------------------------------------------------------
144
119
  async function loadAsyncStorage() {
145
120
  if (!asyncStorageModule) {
146
- throw missingOptionalPeerError('@react-native-async-storage/async-storage', 'device/session persistence', asyncStorageError);
121
+ throw (0, optionalPeer_1.missingOptionalPeerError)('@react-native-async-storage/async-storage', 'device/session persistence', asyncStorageError);
147
122
  }
148
123
  // Mirror the shape callers historically used (`module.default.<method>`)
149
124
  // so the call sites don't have to know whether the underlying module
150
125
  // ships ESM or CJS-with-default.
151
126
  return { default: asyncStorageModule };
152
127
  }
153
- /**
154
- * Synchronous random-bytes via `expo-crypto.getRandomBytes`.
155
- *
156
- * Synchronous by contract: `@oxy.so/core`'s crypto polyfill uses this to back
157
- * `globalThis.crypto.getRandomValues`, which cannot await. That is why
158
- * `expo-crypto` is resolved with a synchronous `require` at module scope rather
159
- * than a dynamic `import()`.
160
- */
161
- function getRandomBytesRN(byteCount) {
162
- if (!expoCryptoModule) {
163
- throw missingOptionalPeerError('expo-crypto', 'the React Native CSPRNG (crypto.getRandomValues)', expoCryptoError);
164
- }
165
- return expoCryptoModule.getRandomBytes(byteCount);
166
- }
128
+ // Synchronous random bytes (and the single `expo-crypto` resolution) live in
129
+ // the dependency-free `./random.native` module, so `@oxy.so/core`'s crypto
130
+ // polyfill can load them through `@oxy.so/protocol/random` without evaluating
131
+ // any crypto library first. The explicit `.native` specifier keeps tsc and
132
+ // Metro pointed at the same file.
133
+ var random_native_2 = require("./random.native");
134
+ Object.defineProperty(exports, "getRandomBytesRN", { enumerable: true, get: function () { return random_native_2.getRandomBytesRN; } });
167
135
  // ---------------------------------------------------------------------------
168
136
  // Shared identity bridge — `@oxy.so/expo-oxy-identity` (native-only, OPTIONAL).
169
137
  //
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.missingOptionalPeerError = missingOptionalPeerError;
4
+ /**
5
+ * Actionable error for a missing optional React Native peer.
6
+ *
7
+ * Shared by the React Native platform variants (`crypto.native.ts`,
8
+ * `random.native.ts`). It carries the underlying Metro resolution message so
9
+ * the failure is never silent: the variants' `try { require(...) } catch` only
10
+ * defers the report to the point where the capability is actually used.
11
+ */
12
+ function missingOptionalPeerError(packageName, capability, cause) {
13
+ const sentences = [
14
+ `[oxy.protocol.crypto] '${packageName}' is not installed, so ${capability} is unavailable in this app.`,
15
+ 'It is an optional peer dependency of @oxy.so/protocol that the React Native runtime needs —',
16
+ `install it with \`npx expo install ${packageName}\`.`,
17
+ ];
18
+ if (cause instanceof Error) {
19
+ sentences.push(`Underlying error: ${cause.message}`);
20
+ }
21
+ return new Error(sentences.join(' '));
22
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * Synchronous CSPRNG source — default variant (Node.js, browsers, bundlers).
4
+ *
5
+ * Companion to `./random.native.ts`, which Metro substitutes on iOS / Android.
6
+ * Node and browsers own a native CSPRNG (`node:crypto`, `globalThis.crypto`),
7
+ * so `getRandomBytesRN` only exists here to keep both variants' surfaces
8
+ * identical, and throws if anything reaches it outside React Native.
9
+ *
10
+ * Like its sibling, this module imports nothing: it is part of the
11
+ * `@oxy.so/protocol/random` entry that `@oxy.so/core`'s crypto polyfill loads
12
+ * BEFORE any crypto library is evaluated.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getRandomBytesRN = getRandomBytesRN;
16
+ function getRandomBytesRN(_byteCount) {
17
+ throw new Error("[oxy.protocol.crypto] Tried to load 'expo-crypto.getRandomBytes (sync)' outside React Native. This module is only available in a React Native runtime; bundling routed this consumer to the default (Node/web) variant. This indicates a missing platform gate (`isReactNative()`) in the calling code.");
18
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * Synchronous CSPRNG source — React Native variant.
4
+ *
5
+ * Companion to `./random.ts`; Metro substitutes this file on iOS / Android
6
+ * (see `./crypto.ts` for how the `.native` split works).
7
+ *
8
+ * This module is deliberately tiny and imports NOTHING but `expo-crypto`: it
9
+ * backs `@oxy.so/core`'s `globalThis.crypto.getRandomValues` polyfill, which
10
+ * must be fully installed before any module that captures `globalThis.crypto`
11
+ * at evaluation time (`@noble/hashes` 1.x's `crypto.js` does exactly that) is
12
+ * evaluated. Reaching a crypto library from here would let that library
13
+ * capture the missing global first and throw
14
+ * `crypto.getRandomValues must be defined` for the lifetime of the app.
15
+ *
16
+ * `expo-crypto` is an OPTIONAL peer, so it is resolved with a string-literal
17
+ * `require` inside a `try` (Metro's optional-dependency form; see
18
+ * `./crypto.native.ts`), and the load stays synchronous because
19
+ * `getRandomValues` cannot await.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.requireExpoCrypto = requireExpoCrypto;
23
+ exports.getRandomBytesRN = getRandomBytesRN;
24
+ const optionalPeer_1 = require("./optionalPeer");
25
+ let expoCryptoModule = null;
26
+ let expoCryptoError;
27
+ try {
28
+ expoCryptoModule = require('expo-crypto');
29
+ }
30
+ catch (error) {
31
+ expoCryptoError = error;
32
+ }
33
+ /**
34
+ * The resolved `expo-crypto` module, or an actionable error naming the missing
35
+ * peer and the capability that needed it. Shared with `./crypto.native.ts` so
36
+ * the optional peer is resolved in exactly one place.
37
+ */
38
+ function requireExpoCrypto(capability) {
39
+ if (!expoCryptoModule) {
40
+ throw (0, optionalPeer_1.missingOptionalPeerError)('expo-crypto', capability, expoCryptoError);
41
+ }
42
+ return expoCryptoModule;
43
+ }
44
+ /**
45
+ * Synchronous random bytes via `expo-crypto.getRandomBytes`.
46
+ *
47
+ * Synchronous by contract: `@oxy.so/core`'s crypto polyfill uses this to back
48
+ * `globalThis.crypto.getRandomValues`, which cannot await.
49
+ */
50
+ function getRandomBytesRN(byteCount) {
51
+ return requireExpoCrypto('the React Native CSPRNG (crypto.getRandomValues)').getRandomBytes(byteCount);
52
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * `@oxy.so/protocol/random` — the platform randomness source, and nothing else.
4
+ *
5
+ * `@oxy.so/core`'s crypto polyfill installs `globalThis.crypto.getRandomValues`
6
+ * on hosts that lack it (React Native / Hermes). `@noble/hashes` 1.x captures
7
+ * `globalThis.crypto` ONCE, when its `crypto.js` is evaluated, so the polyfill
8
+ * has to be installed before any `@noble/*` module is evaluated. ES imports are
9
+ * evaluated before the importing module's body, so whatever the polyfill
10
+ * imports is evaluated first: importing the ROOT entry (which reaches
11
+ * `@noble/curves` through the envelope signer) let noble capture `undefined`
12
+ * and broke identity creation on Android.
13
+ *
14
+ * This entry therefore reaches no crypto library, no `@oxy.so/*` package and no
15
+ * third-party module other than the optional `expo-crypto` peer (RN variant
16
+ * only). `src/__tests__/randomEntry.test.ts` walks its module graph to keep it
17
+ * that way.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.getRandomBytesRN = exports.isReactNative = exports.isNodeJS = void 0;
21
+ var platform_1 = require("./platform/platform");
22
+ Object.defineProperty(exports, "isNodeJS", { enumerable: true, get: function () { return platform_1.isNodeJS; } });
23
+ Object.defineProperty(exports, "isReactNative", { enumerable: true, get: function () { return platform_1.isReactNative; } });
24
+ var random_1 = require("./platform/random");
25
+ Object.defineProperty(exports, "getRandomBytesRN", { enumerable: true, get: function () { return random_1.getRandomBytesRN; } });