@scalebun/react-native 1.11.1 → 2.0.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 (68) hide show
  1. package/android/build.gradle +8 -0
  2. package/android/src/androidTest/java/com/scalebun/rn/ota/ScaleBunOtaVerifierInstrumentedTest.kt +112 -0
  3. package/android/src/main/java/com/scalebun/rn/ota/OtaProtocol.kt +244 -0
  4. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaKeyRegistry.kt +100 -0
  5. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +115 -60
  6. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaReleaseVerifier.kt +129 -0
  7. package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +18 -0
  8. package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +4 -8
  9. package/dist/scalebun.full.js +127 -422
  10. package/dist/scalebun.slim.js +127 -422
  11. package/ios/Ota/OtaProtocol.swift +242 -0
  12. package/ios/Ota/OtaSlotManager.swift +26 -0
  13. package/ios/Ota/ScaleBunOtaBridge.mm +6 -3
  14. package/ios/Ota/ScaleBunOtaKeyRegistry.swift +147 -0
  15. package/ios/Ota/ScaleBunOtaModule.swift +125 -53
  16. package/ios/Ota/ScaleBunOtaReleaseVerifier.swift +115 -0
  17. package/ios/Ota/ScaleBunOtaVerifierTests.swift +92 -0
  18. package/lib/commonjs/core/config/schema.js +3 -12
  19. package/lib/commonjs/core/constants/version.js +1 -1
  20. package/lib/commonjs/features/network/NetworkFeature.js +39 -5
  21. package/lib/commonjs/features/network/thirdParty.js +90 -0
  22. package/lib/commonjs/features/ota/OtaOrchestrator.js +108 -60
  23. package/lib/commonjs/public/ScaleBunFacade.js +5 -41
  24. package/lib/module/core/config/schema.js +3 -12
  25. package/lib/module/core/constants/version.js +1 -1
  26. package/lib/module/features/network/NetworkFeature.js +38 -4
  27. package/lib/module/features/network/thirdParty.js +83 -0
  28. package/lib/module/features/ota/OtaOrchestrator.js +108 -60
  29. package/lib/module/public/ScaleBunFacade.js +5 -41
  30. package/lib/typescript/core/config/schema.d.ts +0 -3
  31. package/lib/typescript/core/constants/version.d.ts +1 -1
  32. package/lib/typescript/features/network/index.d.ts +18 -0
  33. package/lib/typescript/features/network/thirdParty.d.ts +65 -0
  34. package/lib/typescript/features/ota/OtaOrchestrator.d.ts +5 -8
  35. package/lib/typescript/features/ota/OtaTypes.d.ts +22 -0
  36. package/lib/typescript/public/ScaleBunFacade.d.ts +5 -11
  37. package/lib/typescript/specs/NativeScaleBunOta.d.ts +30 -20
  38. package/package.json +3 -13
  39. package/scalebun-react-native.podspec +4 -0
  40. package/src/core/config/schema.ts +3 -9
  41. package/src/core/constants/version.ts +1 -1
  42. package/src/features/network/NetworkFeature.ts +41 -4
  43. package/src/features/network/index.ts +18 -0
  44. package/src/features/network/thirdParty.ts +92 -0
  45. package/src/features/ota/OtaOrchestrator.ts +116 -70
  46. package/src/features/ota/OtaTypes.ts +23 -1
  47. package/src/public/ScaleBunFacade.ts +5 -53
  48. package/src/specs/NativeScaleBunOta.ts +32 -20
  49. package/lib/commonjs/features/ota/crypto/builtinVerifier.js +0 -248
  50. package/lib/commonjs/features/ota/crypto/loadEd25519.js +0 -40
  51. package/lib/commonjs/features/ota/crypto/loadSha512.js +0 -40
  52. package/lib/commonjs/features/ota/crypto/nativeVerifier.js +0 -121
  53. package/lib/commonjs/features/ota/signature.js +0 -175
  54. package/lib/module/features/ota/crypto/builtinVerifier.js +0 -240
  55. package/lib/module/features/ota/crypto/loadEd25519.js +0 -34
  56. package/lib/module/features/ota/crypto/loadSha512.js +0 -34
  57. package/lib/module/features/ota/crypto/nativeVerifier.js +0 -113
  58. package/lib/module/features/ota/signature.js +0 -169
  59. package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +0 -53
  60. package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +0 -30
  61. package/lib/typescript/features/ota/crypto/loadSha512.d.ts +0 -15
  62. package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +0 -35
  63. package/lib/typescript/features/ota/signature.d.ts +0 -81
  64. package/src/features/ota/crypto/builtinVerifier.ts +0 -257
  65. package/src/features/ota/crypto/loadEd25519.ts +0 -41
  66. package/src/features/ota/crypto/loadSha512.ts +0 -35
  67. package/src/features/ota/crypto/nativeVerifier.ts +0 -117
  68. package/src/features/ota/signature.ts +0 -206
@@ -25,6 +25,15 @@ export interface Spec extends TurboModule {
25
25
  */
26
26
  getActiveBundle(): string | null;
27
27
 
28
+ /**
29
+ * JSON array (string) of the trusted key IDs embedded in the app package, e.g.
30
+ * `["production-2026-01"]`. Reported on OTA check-in for SDK trust coverage
31
+ * (CICDPAGE §12) so the dashboard can measure key-rotation adoption. This is
32
+ * telemetry ONLY — the server never trusts a key because a device claims it.
33
+ * Empty array when no keys are embedded.
34
+ */
35
+ getTrustedKeyIds(): string;
36
+
28
37
  /**
29
38
  * Returns JSON with current slot state:
30
39
  * { current: { bundleId, version, sha256, installedAt } | null,
@@ -87,28 +96,31 @@ export interface Spec extends TurboModule {
87
96
  restartApp(): void;
88
97
 
89
98
  /**
90
- * Verify a detached ed25519 signature in NATIVE code.
91
- *
92
- * Exists because the JS verifier runs inside the very bundle it protects
93
- * an attacker who lands one malicious bundle can neuter a JS check for every
94
- * update after it. Platform crypto is outside the bundle's reach, and it also
95
- * removes the runtime dependency on the optional @noble peers wherever the OS
96
- * provides ed25519 (iOS 13+ CryptoKit everywhere; Android API 33+).
99
+ * Verify a SB-OTA-RSA-SHA256-V1 release envelope in NATIVE code, with no side
100
+ * effects. `envelopeJson` is the JSON `{ protocol, keyId, signedPayload,
101
+ * signature, downloadUrl? }` from the check response. Verification uses the
102
+ * trusted RSA public keys EMBEDDED in the app package (never a key from JS or
103
+ * the server) and platform `SHA256withRSA` (present on every supported API
104
+ * level no API-33 floor).
97
105
  *
98
- * All three arguments are lowercase hex, pre-validated by the JS caller:
99
- * the 32-byte bundle SHA-256 (the signature is over its RAW BYTES, matching
100
- * the CLI's signing contract), the 64-byte signature, the 32-byte public key.
101
- *
102
- * Resolves a verdict STRING, not a boolean, because "the signature is wrong"
103
- * and "this OS cannot check" must never collapse into one value:
104
- * 'valid' — signature verifies under the key
105
- * 'invalid' — it does not; the caller must refuse the bundle
106
- * 'unavailable' — this OS level has no ed25519 (Android < 33); the caller
107
- * falls back to the JS verifier
108
- * A rejected promise is machinery failure and the caller treats it as
109
- * 'unavailable'never as 'valid'.
106
+ * Resolves a verdict JSON string: `{ ok: boolean, code?: string, message?,
107
+ * keyId?, releaseId?, bundleId?, releaseSequence?, artifactSha256?,
108
+ * artifactSize?, resultArtifactSha256?, fingerprint? }`. A failed check is
109
+ * `ok:false` with a stable `code`; a rejected promise is machinery failure and
110
+ * the caller must treat it as a failure, never as valid.
111
+ */
112
+ verifyRelease(envelopeJson: string): Promise<string>;
113
+
114
+ /**
115
+ * Native-ENFORCED install: verify the RSA signature (as `verifyRelease`), then
116
+ * download the artifact, enforce the SIGNED size + SHA-256, and stage it into
117
+ * the `next` slot all natively. JavaScript cannot stage bytes that were not
118
+ * proven authentic (once trusted keys are embedded, the legacy `stageBundle`
119
+ * path refuses). Does not activate; call `applyUpdate` to promote per the
120
+ * install mode. Resolves the same verdict JSON as `verifyRelease` (`ok:true`
121
+ * once staged).
110
122
  */
111
- verifyEd25519(messageHex: string, signatureHex: string, publicKeyHex: string): Promise<string>;
123
+ verifyAndStageRelease(envelopeJson: string): Promise<string>;
112
124
  }
113
125
 
114
126
  /**
@@ -1,248 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports._resetBuiltinVerifier = _resetBuiltinVerifier;
7
- exports.decodeSignature = decodeSignature;
8
- exports.getBuiltinVerifier = getBuiltinVerifier;
9
- var _loadEd = require("./loadEd25519");
10
- var _loadSha = require("./loadSha512");
11
- var _internalLogger = require("../../../core/logger/internalLogger");
12
- /**
13
- * Built-in ed25519 verifier, assembled from OPTIONAL peer dependencies.
14
- *
15
- * WHAT THIS REPLACES. Pinning `ota.publicSigningKey` used to require the host
16
- * app to write its own `verifySignature` — roughly a hundred lines of hex and
17
- * base64 decoding wrapped around a crypto library. That put the single most
18
- * security-critical operation in the product, the one deciding whether remote
19
- * code is authentic before it executes, in code the SDK could neither test nor
20
- * audit. Three failure modes all landed on the app author:
21
- *
22
- * - A verifier that always returns `true` silently disables the feature, and
23
- * nothing can detect it.
24
- * - @noble's hash-provider property moved between major versions, so a
25
- * routine dependency bump made every update fail closed with no signal
26
- * pointing at the cause.
27
- * - Hand-written base64 decoders tend to omit base64url, so a signature
28
- * containing `-` or `_` is rejected as a forgery.
29
- *
30
- * All three are now the SDK's problem, which is where they belong. A host that
31
- * installs `@noble/ed25519` and `@noble/hashes` gets verification by pinning a
32
- * key and writing no code at all.
33
- *
34
- * WHY OPTIONAL AND NOT A HARD DEPENDENCY. Most apps never adopt bundle
35
- * signing, and they should not carry curve arithmetic they will not run. The
36
- * `verifySignature` hook remains supported and still WINS over this, for teams
37
- * with their own crypto policy or a native implementation to delegate to.
38
- *
39
- * WHAT THIS DOES NOT FIX. Verification still happens in JS, inside the very
40
- * bundle it protects. An attacker who lands one malicious bundle by other means
41
- * can neuter the check for every update after it. Closing that needs native
42
- * verification — CryptoKit on iOS (13.4+, already the deployment target) and
43
- * `Signature.getInstance("Ed25519")` on Android API 33+, with a fallback below.
44
- */
45
-
46
- const ED25519_SIGNATURE_BYTES = 64;
47
- const ED25519_PUBLIC_KEY_BYTES = 32;
48
-
49
- /** Hex → bytes. Returns null rather than throwing; callers treat null as "reject". */
50
- function hexToBytes(hex) {
51
- const clean = hex.trim().toLowerCase().replace(/^0x/, '');
52
- if (clean.length === 0 || clean.length % 2 !== 0 || /[^0-9a-f]/.test(clean)) return null;
53
- const out = new Uint8Array(clean.length / 2);
54
- for (let i = 0; i < out.length; i++) {
55
- out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
56
- }
57
- return out;
58
- }
59
- const B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
60
-
61
- /**
62
- * Base64 → bytes, accepting base64url as well.
63
- *
64
- * The `-`/`_` handling is the point. A verifier that omits it rejects a
65
- * perfectly valid signature as a forgery the moment the signing side emits
66
- * base64url, and the resulting symptom — updates silently stop installing —
67
- * looks nothing like a decoding bug.
68
- */
69
- function base64ToBytes(b64) {
70
- const s = b64.trim().replace(/-/g, '+').replace(/_/g, '/').replace(/=+$/, '');
71
- if (s.length === 0) return null;
72
- const out = [];
73
- let buffer = 0;
74
- let bits = 0;
75
- for (const ch of s) {
76
- const idx = B64_ALPHABET.indexOf(ch);
77
- if (idx === -1) return null;
78
- buffer = buffer << 6 | idx;
79
- bits += 6;
80
- if (bits >= 8) {
81
- bits -= 8;
82
- out.push(buffer >> bits & 0xff);
83
- }
84
- }
85
- return Uint8Array.from(out);
86
- }
87
-
88
- /**
89
- * Decode a detached signature that may arrive hex- or base64-encoded.
90
- *
91
- * Tries the unambiguous case first: 128 hex characters is exactly 64 bytes and
92
- * cannot be anything else. Otherwise base64, then hex as a last resort. Only a
93
- * result of exactly 64 bytes is accepted, so a string that decodes under the
94
- * wrong scheme is rejected rather than fed to the curve code as garbage.
95
- */
96
- function decodeSignature(sig) {
97
- const trimmed = sig.trim();
98
- if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
99
- const hex = hexToBytes(trimmed);
100
- if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
101
- }
102
- const b64 = base64ToBytes(trimmed);
103
- if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
104
- const hex = hexToBytes(trimmed);
105
- if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
106
- return null;
107
- }
108
-
109
- /**
110
- * Give @noble its SHA-512 provider.
111
- *
112
- * The property it reads moved between major versions — v2 wants
113
- * `etc.sha512Sync`, v3 wants `hashes.sha512` — so both are populated when
114
- * present. Getting this wrong does not fail loudly: verification simply throws
115
- * from inside the curve code and the outer catch converts it to "rejected",
116
- * which is indistinguishable from an actual forgery. That ambiguity is exactly
117
- * what made this a bad thing to ask app authors to maintain.
118
- */
119
- function wireSha512(ed, sha512) {
120
- // VARIADIC ON PURPOSE. @noble calls its hash provider with several byte
121
- // arrays to be hashed as one concatenated message. A wrapper taking a
122
- // single argument hashes only the first, silently produces the wrong
123
- // digest, and every genuine signature is then reported as INVALID — no
124
- // error, no exception, just an authenticity check that always says no.
125
- // Verified against a real CLI-produced signature; the single-argument form
126
- // failed every positive case while passing every negative one, which is
127
- // the most misleading shape a bug of this kind can take.
128
- const concat = parts => {
129
- if (parts.length === 1) return parts[0];
130
- let total = 0;
131
- for (const p of parts) total += p.length;
132
- const out = new Uint8Array(total);
133
- let off = 0;
134
- for (const p of parts) {
135
- out.set(p, off);
136
- off += p.length;
137
- }
138
- return out;
139
- };
140
- const sync = (...msgs) => sha512(concat(msgs));
141
- const asyncFn = async (...msgs) => sha512(concat(msgs));
142
-
143
- // EACH BRANCH GETS ITS OWN try. Wrapping both in one is a trap, and it cost
144
- // an afternoon: under v3 the `hashes` assignment succeeds and the `etc`
145
- // object is FROZEN, so the v2-shaped assignment throws "object is not
146
- // extensible" — discarding a verifier that was, by then, already correctly
147
- // wired. Only "neither shape took" means unavailable.
148
- let wired = false;
149
- try {
150
- if (ed.hashes && typeof ed.hashes === 'object') {
151
- if (typeof ed.hashes.sha512 !== 'function') ed.hashes.sha512 = sync;
152
- if (typeof ed.hashes.sha512Async !== 'function') ed.hashes.sha512Async = asyncFn;
153
- wired = typeof ed.hashes.sha512 === 'function';
154
- }
155
- } catch {
156
- /* frozen or absent — try the other shape */
157
- }
158
- try {
159
- if (ed.etc && typeof ed.etc === 'object') {
160
- if (typeof ed.etc.sha512Sync !== 'function') ed.etc.sha512Sync = sync;
161
- if (typeof ed.etc.sha512Async !== 'function') ed.etc.sha512Async = asyncFn;
162
- wired = wired || typeof ed.etc.sha512Sync === 'function';
163
- }
164
- } catch {
165
- /* frozen or absent — `wired` already records whether the other shape took */
166
- }
167
- return wired;
168
- }
169
- let resolved;
170
-
171
- /**
172
- * The built-in verifier, or null when the optional deps are not installed.
173
- * Resolution is cached, including the negative result — a missing dependency
174
- * does not become present at runtime, and retrying the require on every update
175
- * check would be pure overhead.
176
- */
177
- function getBuiltinVerifier() {
178
- if (resolved !== undefined) return resolved;
179
- const ed = (0, _loadEd.loadEd25519)();
180
- const sha512 = (0, _loadSha.loadSha512)();
181
- if (!ed || !sha512) {
182
- resolved = null;
183
- return null;
184
- }
185
- if (!wireSha512(ed, sha512)) {
186
- // A shape neither branch matched — a @noble major this SDK predates.
187
- // Report "no verifier", which is actionable, rather than letting every
188
- // update fail deep inside the curve code with an opaque error.
189
- _internalLogger.logger.error('[OTA] @noble/ed25519 is installed but its SHA-512 provider could not be ' + 'wired (unrecognised version shape). Supply `ota.verifySignature` ' + 'yourself, or pin a @noble/ed25519 version this SDK supports.');
190
- resolved = null;
191
- return null;
192
- }
193
- resolved = async ({
194
- messageHex,
195
- signature,
196
- publicKey
197
- }) => {
198
- const message = hexToBytes(messageHex);
199
- const pub = hexToBytes(publicKey);
200
- const sig = decodeSignature(signature);
201
- if (!message) {
202
- _internalLogger.logger.error('[OTA] Bundle SHA-256 is not valid hex — refusing to stage.');
203
- return false;
204
- }
205
- if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
206
- // Worth naming precisely: a mistyped or wrongly-encoded key would
207
- // otherwise present as "every update is a forgery".
208
- _internalLogger.logger.error(`[OTA] ota.publicSigningKey must be ${ED25519_PUBLIC_KEY_BYTES} bytes of hex ` + `(${ED25519_PUBLIC_KEY_BYTES * 2} characters) — got ` + `${pub ? `${pub.length} bytes` : 'a value that is not hex'}.`);
209
- return false;
210
- }
211
- if (!sig) {
212
- _internalLogger.logger.error('[OTA] Bundle signature is not a 64-byte hex/base64 value.');
213
- return false;
214
- }
215
-
216
- // @noble THROWS rather than returning false for bytes that are not a
217
- // well-formed point — which is most forgeries, since a tampered
218
- // signature usually decodes to garbage rather than to a valid-but-wrong
219
- // point. Swallowing that here is deliberate: from the SDK's side a
220
- // malformed signature IS a rejection, and letting it escape would
221
- // surface as `verifier_threw`, the outcome reserved for a HOST verifier
222
- // misbehaving. Conflating "this bundle is forged" with "your verifier
223
- // is broken" would point every investigation in the wrong direction.
224
- try {
225
- // SYNC FIRST, and this is not a style preference. @noble's
226
- // `verifyAsync` reaches for `crypto.subtle` — "crypto.subtle must be
227
- // defined, consider polyfill" — which React Native does not provide
228
- // and neither does a plain Node test environment. The sync `verify`
229
- // uses the SHA-512 provider wired above and needs no WebCrypto, so
230
- // it is the path that actually works on a phone. Verification is a
231
- // few hundred microseconds over a 32-byte digest, once per update
232
- // check, so there is nothing to gain from the async form anyway.
233
- if (typeof ed.verify === 'function') return ed.verify(sig, message, pub);
234
- if (typeof ed.verifyAsync === 'function') return await ed.verifyAsync(sig, message, pub);
235
- return false;
236
- } catch {
237
- return false;
238
- }
239
- };
240
- __DEV__ && _internalLogger.logger.debug('[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).');
241
- return resolved;
242
- }
243
-
244
- /** Test seam — clears the cached resolution. */
245
- function _resetBuiltinVerifier() {
246
- resolved = undefined;
247
- }
248
- //# sourceMappingURL=builtinVerifier.js.map
@@ -1,40 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.loadEd25519 = loadEd25519;
7
- /**
8
- * Isolated lazy loader for the OPTIONAL `@noble/ed25519` dep.
9
- *
10
- * WHY ITS OWN FILE: Metro's dependency collector emits a dependency map one
11
- * entry short for a module containing TWO OR MORE different-string inline
12
- * `require()` calls, so the require indices desync and `_dependencyMap[N]`
13
- * reads `undefined` ("Requiring unknown module 'undefined'"). Exactly ONE
14
- * inline require per module avoids the miscount. `loadSha512` is a sibling file
15
- * for the same reason — see `push/adapters/loadNotifee.ts`, which hit this first.
16
- *
17
- * The string must still be STATICALLY resolvable at bundle time; hosts that do
18
- * not install it stub it via `withScaleBun`.
19
- */
20
-
21
- /** The subset of the @noble/ed25519 surface this SDK uses. */
22
-
23
- function loadEd25519() {
24
- try {
25
- const mod = require('@noble/ed25519');
26
- const ed = mod?.default ?? mod;
27
- // Validate the API actually used, not mere truthiness: when the package
28
- // is absent `withScaleBun` resolves it to an EMPTY STUB MODULE, which is
29
- // a perfectly truthy object. A stub must read as "unavailable", because
30
- // treating it as available would make every signed update fail with a
31
- // crypto error instead of the actionable "no verifier" message.
32
- if (typeof ed?.verifyAsync === 'function' || typeof ed?.verify === 'function') {
33
- return ed;
34
- }
35
- return null;
36
- } catch {
37
- return null;
38
- }
39
- }
40
- //# sourceMappingURL=loadEd25519.js.map
@@ -1,40 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.loadSha512 = loadSha512;
7
- /**
8
- * Isolated lazy loader for the OPTIONAL `@noble/hashes` SHA-512.
9
- *
10
- * Separate file from `loadEd25519` because Metro miscounts a module holding two
11
- * different-string inline `require()` calls — see the comment there.
12
- *
13
- * ed25519 is defined in terms of SHA-512, and React Native has no native
14
- * SHA-512, so @noble/ed25519 cannot verify anything until a hash provider is
15
- * wired into it. Which property it expects differs by major version, so the
16
- * wiring lives in `builtinVerifier`, not here; this module only obtains the
17
- * function.
18
- */
19
-
20
- function loadSha512() {
21
- try {
22
- // The `.js` SUFFIX IS REQUIRED, not stylistic. @noble/hashes v2 declares
23
- // its exports map with extensions — `"./sha2.js"`, not `"./sha2"` — so
24
- // the extensionless form misses the map entirely. Node still resolves it
25
- // by falling back to file-based lookup, which is why it passes here,
26
- // but Metro prints
27
- // "Attempted to import … not listed in the exports … Falling back to
28
- // file-based resolution"
29
- // on every bundle, and that fallback is exactly what a stricter resolver
30
- // (or a future Metro) drops. Caught while bundling a real Expo app.
31
- const mod = require('@noble/hashes/sha2.js');
32
- const sha512 = mod?.sha512 ?? mod?.default?.sha512;
33
- // A stubbed module yields undefined here, which correctly reads as
34
- // "unavailable" rather than throwing deep inside the curve code later.
35
- return typeof sha512 === 'function' ? sha512 : null;
36
- } catch {
37
- return null;
38
- }
39
- }
40
- //# sourceMappingURL=loadSha512.js.map
@@ -1,121 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports._resetNativeVerifier = _resetNativeVerifier;
7
- exports.getNativeVerifier = getNativeVerifier;
8
- var _NativeScaleBunOta = _interopRequireDefault(require("../../../specs/NativeScaleBunOta"));
9
- var _internalLogger = require("../../../core/logger/internalLogger");
10
- var _builtinVerifier = require("./builtinVerifier");
11
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
- /**
13
- * Native ed25519 verifier — platform crypto wrapped as a SignatureVerifier.
14
- *
15
- * WHY IT EXISTS. The built-in @noble verifier runs in JS, inside the very
16
- * bundle it protects: an attacker who lands one malicious bundle can neuter a
17
- * JS check for every update after it. Platform crypto (CryptoKit on iOS,
18
- * Android's conscrypt on API 33+) sits outside the bundle's reach — and using
19
- * it also drops the runtime dependency on the optional @noble peers wherever
20
- * the OS provides ed25519.
21
- *
22
- * WHAT IT DOES NOT FIX, stated plainly: the ORCHESTRATION still lives in JS.
23
- * A hostile bundle can skip calling any verifier and drive the native staging
24
- * methods directly. Moving the crypto native shrinks the attack surface (no
25
- * more tampering with a bundled crypto lib to flip a verdict) but the full
26
- * close needs native-ENFORCED staging with a natively-pinned key — an
27
- * architectural change tracked separately, not smuggled into this one.
28
- *
29
- * VERDICT CONTRACT (mirrors the spec): the native side resolves
30
- * 'valid' | 'invalid' | 'unavailable'. 'invalid' is a definitive NO.
31
- * 'unavailable' (Android < 33, malformed input, machinery failure) means this
32
- * source cannot answer — the composed verifier below then delegates to the
33
- * @noble builtin, and when that is absent too it THROWS, which the policy
34
- * layer converts to a rejection. Every path that cannot verify refuses.
35
- */
36
-
37
- const ED25519_PUBLIC_KEY_HEX_CHARS = 64;
38
-
39
- /** bytes → lowercase hex (the native side takes clean hex only). */
40
- function bytesToHex(bytes) {
41
- let out = '';
42
- for (const b of bytes) out += b.toString(16).padStart(2, '0');
43
- return out;
44
- }
45
-
46
- /** Strict lowercase-hex normalization; null when the value is not hex. */
47
- function normalizeHex(value, expectedChars) {
48
- const clean = value.trim().toLowerCase().replace(/^0x/, '');
49
- if (clean.length !== expectedChars || /[^0-9a-f]/.test(clean)) return null;
50
- return clean;
51
- }
52
- let resolved;
53
-
54
- /**
55
- * The native-first verifier, or null when the native module (or its
56
- * verifyEd25519 method — an app running new JS against an old binary) is
57
- * absent. Cached like the builtin: module availability does not change at
58
- * runtime, and this is consulted on every update check.
59
- */
60
- function getNativeVerifier() {
61
- if (resolved !== undefined) return resolved;
62
- const native = _NativeScaleBunOta.default;
63
- if (!native || typeof native.verifyEd25519 !== 'function') {
64
- // Old binary, Jest, web/SSR — not an error, just not this source.
65
- resolved = null;
66
- return null;
67
- }
68
- resolved = async ({
69
- messageHex,
70
- signature,
71
- publicKey
72
- }) => {
73
- // All decoding and validation happens HERE, in one place, so both
74
- // platforms' native code only ever sees clean fixed-length hex. The
75
- // signature may arrive hex or base64 (the wire allows both) — reuse
76
- // the builtin's decoder rather than growing a second, subtly
77
- // different one.
78
- const message = normalizeHex(messageHex, 64);
79
- const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
80
- const sigBytes = (0, _builtinVerifier.decodeSignature)(signature);
81
- if (!message || !key || !sigBytes) {
82
- // Same messages-by-symptom philosophy as the builtin: a mistyped
83
- // key must not present as "every update is a forgery".
84
- _internalLogger.logger.error('[OTA] Signature inputs malformed (bundle hash, signature, or public key) — refusing to stage.');
85
- return false;
86
- }
87
- let verdict;
88
- try {
89
- verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
90
- } catch (err) {
91
- // A rejected promise is machinery failure, not a forgery verdict.
92
- _internalLogger.logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
93
- verdict = 'unavailable';
94
- }
95
- if (verdict === 'valid') return true;
96
- if (verdict === 'invalid') return false;
97
-
98
- // 'unavailable' (Android < 33) — fall back to the JS verifier so those
99
- // devices keep verifying rather than losing enforcement.
100
- const builtin = (0, _builtinVerifier.getBuiltinVerifier)();
101
- if (builtin) {
102
- return builtin({
103
- messageHex,
104
- signature,
105
- publicKey
106
- });
107
- }
108
- // No source can verify. Throwing (rather than returning false) keeps
109
- // the policy layer's outcome labels honest: this surfaces as machinery
110
- // failure with an actionable message, not as "the bundle is forged".
111
- throw new Error('ed25519 unavailable: this OS has no native implementation (Android < 13) and the ' + 'optional peers @noble/ed25519 + @noble/hashes are not installed.');
112
- };
113
- __DEV__ && _internalLogger.logger.debug('[OTA] Using native ed25519 verifier (platform crypto).');
114
- return resolved;
115
- }
116
-
117
- /** Test seam — clears the cached resolution. */
118
- function _resetNativeVerifier() {
119
- resolved = undefined;
120
- }
121
- //# sourceMappingURL=nativeVerifier.js.map