diff --git a/.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch b/.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch new file mode 100644 index 0000000000..4180c62201 --- /dev/null +++ b/.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch @@ -0,0 +1,205 @@ +diff --git a/.DS_Store b/.DS_Store +new file mode 100644 +index 0000000000000000000000000000000000000000..9211d3bfe85cf15dbed0f66b9014a322c5f36ceb +--- /dev/null ++++ b/.DS_Store +@@ -0,0 +1 @@ ++ +\ No newline at end of file +diff --git a/dist/index.js b/dist/index.js +index 4a914633a13067af05838ab582a9dd41b53306ba..68df133248e9a1d067735ad1915f64afd4563057 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -4,6 +4,27 @@ var secp256k1 = require('@noble/curves/secp256k1'); + var mod = require('@noble/curves/abstract/modular'); + var utils = require('@noble/curves/abstract/utils'); + ++// Prefer native Nitro implementation when available ++let pointAddScalarNative; ++try { ++ const NativeAvalabsCrypto = require('react-native-nitro-avalabs-crypto'); ++ pointAddScalarNative = NativeAvalabsCrypto && NativeAvalabsCrypto.pointAddScalar; ++} catch (_) { ++ pointAddScalarNative = undefined; ++} ++ ++// Native sign/verify (Nitro) support ++let signNative, verifyNative, signSchnorrNative, verifySchnorrNative; ++try { ++ const NativeAvalabsCrypto = require('react-native-nitro-avalabs-crypto'); ++ signNative = NativeAvalabsCrypto && NativeAvalabsCrypto.sign; ++ verifyNative = NativeAvalabsCrypto && NativeAvalabsCrypto.verify; ++ signSchnorrNative = NativeAvalabsCrypto && NativeAvalabsCrypto.signSchnorr; ++ verifySchnorrNative = NativeAvalabsCrypto && NativeAvalabsCrypto.verifySchnorr; ++} catch (_) { ++ signNative = verifyNative = signSchnorrNative = verifySchnorrNative = undefined; ++} ++ + function _interopNamespaceDefault(e) { + var n = Object.create(null); + if (e) { +@@ -176,6 +197,31 @@ function _privateNegate(privateKey) { + } + + function _pointAddScalar(p, tweak, isCompressed) { ++ // Try native (RN Nitro) fast-path first ++ if (typeof pointAddScalarNative === 'function') { ++ try { ++ const out = pointAddScalarNative(p, tweak, isCompressed); ++ if (out.length === 0) { ++ throw new Error("Tweaked point at infinity"); ++ } ++ ++ const con = console; ++ con.log('pointAddScalarNative output:', out); ++ ++ // Native returns ArrayBuffer or Uint8Array ++ return out instanceof ArrayBuffer ? new Uint8Array(out) : out; ++ } catch (e) { /* fall back to JS path below */ ++ if (e instanceof Error && e.message === "Tweaked point at infinity") { ++ throw e; ++ } ++ const con = console; ++ con.log('pointAddScalarNative failed, using JS fallback', e); ++ } ++ } else { ++ const con = console; ++ con.log('pointAddScalarNative not available, using JS fallback'); ++ } ++ // Fallback to JS implementation + const P = fromHex(p); + const t = normalizeScalar(tweak); + // multiplyAndAddUnsafe(P, scalar, 1) = P + scalar*G +@@ -361,6 +407,18 @@ function sign(h, d, e) { + if (!isExtraData(e)) { + throw new Error(THROW_BAD_EXTRA_DATA); + } ++ ++ // Try native first ++ if (typeof signNative === 'function') { ++ try { ++ const result = signNative(d, h); ++ return result instanceof ArrayBuffer ? new Uint8Array(result) : result; ++ } catch (_) { ++ // fallback ++ } ++ } ++ ++ // JS fallback + return secp256k1.secp256k1.sign(h, d, { extraEntropy: e }).toCompactRawBytes(); + } + +@@ -381,7 +439,7 @@ function signRecoverable(h, d, e) { + }; + } + +-function signSchnorr(h, d, e) { ++function signSchnorr(h, d, e, forceJs = false) { + if (!isPrivate(d)) { + throw new Error(THROW_BAD_PRIVATE); + } +@@ -391,6 +449,18 @@ function signSchnorr(h, d, e) { + if (!isExtraData(e)) { + throw new Error(THROW_BAD_EXTRA_DATA); + } ++ ++ // Try native first ++ if (typeof signSchnorrNative === 'function' && !forceJs) { ++ try { ++ const result = signSchnorrNative(h, d, e); ++ return result instanceof ArrayBuffer ? new Uint8Array(result) : result; ++ } catch (_) { ++ // fallback to JS implementation ++ } ++ } ++ ++ // JS fallback + return secp256k1.schnorr.sign(h, d, e); + } + +@@ -428,10 +498,21 @@ function verify(h, Q, signature, strict) { + if (!isHash(h)) { + throw new Error(THROW_BAD_SCALAR); + } ++ ++ // Try native verify first ++ if (typeof verifyNative === 'function') { ++ try { ++ return !!verifyNative(Q, h, signature); ++ } catch (_) { ++ // fallback ++ } ++ } ++ ++ // JS fallback + return secp256k1.secp256k1.verify(signature, h, Q, { lowS: strict }); + } + +-function verifySchnorr(h, Q, signature) { ++function verifySchnorr(h, Q, signature, forceJs = false) { + if (!isXOnlyPoint(Q)) { + throw new Error(THROW_BAD_POINT); + } +@@ -441,6 +522,17 @@ function verifySchnorr(h, Q, signature) { + if (!isHash(h)) { + throw new Error(THROW_BAD_SCALAR); + } ++ ++ // Try native verify first ++ if (typeof verifySchnorrNative === 'function' && !forceJs) { ++ try { ++ return !!verifySchnorrNative(Q, h, signature); ++ } catch (_) { ++ // fallback to JS implementation ++ } ++ } ++ ++ // JS fallback + return secp256k1.schnorr.verify(signature, h, Q); + } + +@@ -465,3 +557,4 @@ exports.verifySchnorr = verifySchnorr; + exports.xOnlyPointAddTweak = xOnlyPointAddTweak; + exports.xOnlyPointFromPoint = xOnlyPointFromPoint; + exports.xOnlyPointFromScalar = xOnlyPointFromScalar; ++ +diff --git a/package.json b/package.json +index 8966c2604340ecac199be87de4517dd5b2d81a0a..13aea201ba47186003710bc30ecccc1a89118e7e 100644 +--- a/package.json ++++ b/package.json +@@ -42,5 +42,35 @@ + }, + "dependencies": { + "@noble/curves": "^1.7.0" ++ }, ++ "react-native": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ }, ++ "browser": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" + } + } diff --git a/.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch b/.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch new file mode 100644 index 0000000000..a534152ba3 --- /dev/null +++ b/.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch @@ -0,0 +1,209 @@ +diff --git a/.DS_Store b/.DS_Store +new file mode 100644 +index 0000000000000000000000000000000000000000..3117a90a0d1c52a564b798d5ebe84c3506d72932 +--- /dev/null ++++ b/.DS_Store +@@ -0,0 +1 @@ ++ +\ No newline at end of file +diff --git a/abstract/weierstrass.js b/abstract/weierstrass.js +index a7a912fa46128bbc36eeefe67e6ba16855992917..ca5d7be1c7c57a9bd82159196d0397ac4331544f 100644 +--- a/abstract/weierstrass.js ++++ b/abstract/weierstrass.js +@@ -11,6 +11,29 @@ exports.ecdsa = ecdsa; + exports.weierstrassPoints = weierstrassPoints; + exports._legacyHelperEquat = _legacyHelperEquat; + exports.weierstrass = weierstrass; ++ ++const NativeAvalabsCrypto = require("react-native-nitro-avalabs-crypto"); ++const getPublicKeyFromNativeLib = NativeAvalabsCrypto.getPublicKey; ++const pointAddScalarNative = NativeAvalabsCrypto.pointAddScalar; ++const signNative = NativeAvalabsCrypto.sign; ++const verifyNative = NativeAvalabsCrypto.verify; ++const signSchnorrNative = NativeAvalabsCrypto.signSchnorr; ++const verifySchnorrNative = NativeAvalabsCrypto.verifySchnorr; ++// Native-backed helpers (forwarders) ++function _pointAddScalar(p, tweak, isCompressed) { ++ return pointAddScalarNative(p, tweak, isCompressed); ++} ++exports._pointAddScalar = _pointAddScalar; ++ ++function _signSchnorr(messageHash, secretKey) { ++ return signSchnorrNative(secretKey, messageHash); ++} ++exports._signSchnorr = _signSchnorr; ++ ++function _verifySchnorr(publicKey, messageHash, signature) { ++ return verifySchnorrNative(publicKey, messageHash, signature); ++} ++exports._verifySchnorr = _verifySchnorr; + /** + * Short Weierstrass curve methods. The formula is: y² = x³ + ax + b. + * +@@ -915,7 +938,17 @@ function ecdh(Point, ecdhOpts = {}) { + * @returns Public key, full when isCompressed=false; short when isCompressed=true + */ + function getPublicKey(secretKey, isCompressed = true) { +- return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed); ++ const con = console; ++ try { ++ const nativeRes = getPublicKeyFromNativeLib(secretKey, isCompressed); ++ return nativeRes; ++ } catch (e) { ++ con.log(`Native lib failed, falling back to JS implementation ${e}`); ++ // fall through to JS implementation below ++ } ++ ++ const res = Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed); ++ return res; + } + function keygen(seed) { + const secretKey = randomSecretKey(seed); +@@ -1232,6 +1265,14 @@ function ecdsa(Point, hash, ecdsaOpts = {}) { + * ``` + */ + function sign(message, secretKey, opts = {}) { ++ // Prefer native DER-sign, then wrap into Signature instance for compatibility ++ try { ++ const der = signNative(secretKey, message); ++ const bytes = der instanceof ArrayBuffer ? new Uint8Array(der) : der; ++ return Signature.fromBytes(bytes, 'der'); ++ } catch (_) { ++ // fall through to JS implementation below ++ } + message = (0, utils_ts_1.ensureBytes)('message', message); + const { seed, k2sig } = prepSig(message, secretKey, opts); // Steps A, D of RFC6979 3.2. + const drbg = (0, utils_ts_1.createHmacDrbg)(hash.outputLen, Fn.BYTES, hmac); +@@ -1287,6 +1328,12 @@ function ecdsa(Point, hash, ecdsaOpts = {}) { + * ``` + */ + function verify(signature, message, publicKey, opts = {}) { ++ // Fast-path: native boolean verify if available ++ try { ++ return !!verifyNative(publicKey, message, signature); ++ } catch (_) { ++ // fall through to JS implementation below ++ } + const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts); + publicKey = (0, utils_ts_1.ensureBytes)('publicKey', publicKey); + message = validateMsgAndHash((0, utils_ts_1.ensureBytes)('message', message), prehash); +diff --git a/esm/package.json b/esm/package.json +index 7f1fc33ddaa81f23f560a391b4828ecae59edd5d..8220a13ecc5a52f8db9612fdc7ef5e2a28824321 100644 +--- a/esm/package.json ++++ b/esm/package.json +@@ -1,4 +1,34 @@ + { + "type": "module", +- "sideEffects": false ++ "sideEffects": false, ++ "react-native": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ }, ++ "browser": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ } + } +diff --git a/package.json b/package.json +index 852104a522d6a005c05b5411f36c50bfb76b48bc..8d3a2182f7b60506e6301a432173bc895c7302bd 100644 +--- a/package.json ++++ b/package.json +@@ -291,5 +291,35 @@ + "schnorr", + "fft" + ], +- "funding": "https://paulmillr.com/funding/" ++ "funding": "https://paulmillr.com/funding/", ++ "react-native": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ }, ++ "browser": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ } + } +diff --git a/src/package.json b/src/package.json +index 3dbc1ca591c0557e35b6004aeba250e6a70b56e3..08d985c3d4a0bd8e6c4ffd6401312e9e4ab2246d 100644 +--- a/src/package.json ++++ b/src/package.json +@@ -1,3 +1,33 @@ + { +- "type": "module" ++ "type": "module", ++ "react-native": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ }, ++ "browser": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ } + } diff --git a/.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch b/.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch index c52f22bd88..e3cb4b681d 100644 --- a/.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch +++ b/.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch @@ -16,39 +16,19 @@ index 018d7051a57a2250c738e018ba8e1518d1b9525a..e89a135c10edc50174315c5cf0a3c987 } + diff --git a/android/src/main/java/com/gantix/JailMonkey/Rooted/RootedCheck.java b/android/src/main/java/com/gantix/JailMonkey/Rooted/RootedCheck.java -index bf75f27fd11293205bf6d58de4f8069ea0a155fe..d63ac51b87e456bd13e252c7ca691c07e28704a5 100644 +index bf75f27fd11293205bf6d58de4f8069ea0a155fe..2151858623d5be0f58aa752a1d8b16bafc133dd0 100644 --- a/android/src/main/java/com/gantix/JailMonkey/Rooted/RootedCheck.java +++ b/android/src/main/java/com/gantix/JailMonkey/Rooted/RootedCheck.java -@@ -4,25 +4,44 @@ import android.content.Context; +@@ -4,6 +4,7 @@ import android.content.Context; import com.scottyab.rootbeer.RootBeer; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; -+ -+import androidx.tracing.Trace; public class RootedCheck { private static boolean checkWithJailMonkeyMethod() { -- CheckApiVersion check; -- -- if (android.os.Build.VERSION.SDK_INT >= 23) { -- check = new GreaterThan23(); -- } else { -- check = new LessThan23(); -+ Trace.beginSection("[JailMonkey] checkWithJailMonkeyMethod"); -+ try { -+ CheckApiVersion check; -+ -+ if (android.os.Build.VERSION.SDK_INT >= 23) { -+ check = new GreaterThan23(); -+ } else { -+ check = new LessThan23(); -+ } -+ return check.checkRooted(); -+ } finally { -+ Trace.endSection(); - } -- return check.checkRooted(); +@@ -17,12 +18,23 @@ public class RootedCheck { + return check.checkRooted(); } - private final boolean jailMonkeyResult; @@ -75,8 +55,49 @@ index bf75f27fd11293205bf6d58de4f8069ea0a155fe..d63ac51b87e456bd13e252c7ca691c07 } public boolean isJailBroken() { -@@ -87,3 +106,4 @@ public class RootedCheck { +@@ -87,3 +99,4 @@ public class RootedCheck { } } } + +diff --git a/package.json b/package.json +index 31d682cd52b478e9de01b09838f19975a87352d1..0049a8b91762f6208d3b2e6256895da6c034b727 100644 +--- a/package.json ++++ b/package.json +@@ -61,5 +61,35 @@ + "ts-check": "npx tsc jailmonkey.d.ts --noEmit" + }, + "typings": "./jailmonkey.d.ts", +- "version": "2.8.0" ++ "version": "2.8.0", ++ "react-native": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ }, ++ "browser": { ++ "zlib": "browserify-zlib", ++ "http": "@tradle/react-native-http", ++ "https": "https-browserify", ++ "os": "react-native-os", ++ "path": "path-browserify", ++ "fs": "react-native-level-fs", ++ "_stream_transform": "readable-stream/transform", ++ "_stream_readable": "readable-stream/readable", ++ "_stream_writable": "readable-stream/writable", ++ "_stream_duplex": "readable-stream/duplex", ++ "_stream_passthrough": "readable-stream/passthrough", ++ "stream": "stream-browserify", ++ "vm": "vm-browserify" ++ } + } diff --git a/bitrise.yml b/bitrise.yml index 863d3ae655..6780d638b1 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -24,6 +24,13 @@ workflows: - set-java-version@1: inputs: - set_java_version: "17" + - script: + title: Install build dependencies + inputs: + - content: | + #!/usr/bin/env bash + set -ex + sudo apt-get install -y build-essential autoconf automake libtool tar - install-missing-android-tools@3: inputs: - ndk_version: 27.1.12297006 @@ -215,6 +222,9 @@ workflows: - pipeline_intermediate_files: "$BITRISE_DEPLOY_DIR/app-external-e2e-bitrise-signed.apk:BITRISE_APK_PATH\n$BITRISE_DEPLOY_DIR/app-external-e2e-androidTest-bitrise-signed.apk:BITRISE_TEST_APK_PATH\t" _build-ios: steps: + - brew-install@1: + inputs: + - packages: autoconf automake libtool - restore-cocoapods-cache@1: {} - script@1: title: Copy Gemfile to iOS folder diff --git a/package.json b/package.json index 419bc25712..2815a99eb8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "tsc": "yarn workspaces foreach -p run tsc", "test": "yarn workspaces foreach -p run test", "core": "yarn workspace @avalabs/core-mobile", + "crypto": "yarn workspace react-native-nitro-avalabs-crypto", "k2": "yarn workspace @avalabs/k2-alpine" }, "devDependencies": { @@ -52,7 +53,24 @@ "@scure/btc-signer/@noble/hashes": "1.8.0", "lodash@4.17.3": "4.17.21", "@ledgerhq/hw-transport": "6.31.0", - "jail-monkey@2.8.0": "patch:jail-monkey@npm%3A2.8.0#./.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch" + "jail-monkey@2.8.0": "patch:jail-monkey@npm%3A2.8.0#./.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch", + "@noble/curves@1.9.7": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@^1.7.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@1.3.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@^1.6.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@1.2.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@1.6.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@1.4.2": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@~1.3.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@^1.4.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@~1.4.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@~1.6.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@1.1.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@~1.2.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@~1.1.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@noble/curves@^1.8.0": "patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch", + "@bitcoinerlab/secp256k1@1.2.0": "patch:@bitcoinerlab/secp256k1@npm%3A1.2.0#./.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch", + "@bitcoinerlab/secp256k1@^1.0.5": "patch:@bitcoinerlab/secp256k1@npm%3A1.2.0#./.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch" }, "engines": { "node": ">=20.18.0", diff --git a/packages/core-mobile/ios/Podfile.lock b/packages/core-mobile/ios/Podfile.lock index c0686ad962..fbeefafc3b 100644 --- a/packages/core-mobile/ios/Podfile.lock +++ b/packages/core-mobile/ios/Podfile.lock @@ -533,6 +533,57 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - NitroAvalabsCrypto (0.0.1): + - DoubleConversion + - glog + - hermes-engine + - NitroModules + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - NitroModules (0.31.10): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - OpenSSL-Universal (1.1.1100) - OpenTelemetrySwiftApi (1.13.1) - PLCrashReporter (1.12.0) @@ -2158,6 +2209,30 @@ PODS: - React-Codegen - React-Core - ReactCommon/turbomodule/core + - react-native-performance (5.1.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-quick-base64 (2.1.2): - DoubleConversion - glog @@ -3410,6 +3485,8 @@ DEPENDENCIES: - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - jail-monkey (from `../node_modules/jail-monkey`) - lottie-react-native (from `../node_modules/lottie-react-native`) + - NitroAvalabsCrypto (from `../node_modules/react-native-nitro-avalabs-crypto`) + - NitroModules (from `../node_modules/react-native-nitro-modules`) - OpenSSL-Universal (= 1.1.1100) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -3460,6 +3537,7 @@ DEPENDENCIES: - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pager-view (from `../node_modules/react-native-pager-view`) - react-native-passkey (from `../node_modules/react-native-passkey`) + - react-native-performance (from `../node_modules/react-native-performance`) - react-native-quick-base64 (from `../node_modules/react-native-quick-base64`) - react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`) - react-native-restart (from `../node_modules/react-native-restart`) @@ -3655,6 +3733,10 @@ EXTERNAL SOURCES: :path: "../node_modules/jail-monkey" lottie-react-native: :path: "../node_modules/lottie-react-native" + NitroAvalabsCrypto: + :path: "../node_modules/react-native-nitro-avalabs-crypto" + NitroModules: + :path: "../node_modules/react-native-nitro-modules" RCT-Folly: :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: @@ -3749,6 +3831,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-pager-view" react-native-passkey: :path: "../node_modules/react-native-passkey" + react-native-performance: + :path: "../node_modules/react-native-performance" react-native-quick-base64: :path: "../node_modules/react-native-quick-base64" react-native-quick-crypto: @@ -3962,6 +4046,8 @@ SPEC CHECKSUMS: lottie-react-native: 8bc11e10576d1a3f77f4e0ae5b70503c5c890a09 MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + NitroAvalabsCrypto: 1e5e1dbdc072022443c355b70d6d598841bf8557 + NitroModules: 0af9a8516f3d8f101976d60e1f34e2a22f401600 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 @@ -4014,6 +4100,7 @@ SPEC CHECKSUMS: react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 react-native-pager-view: f238ed7fb53458bd03366944a33686f067c83e9a react-native-passkey: 3aac247c18127443ef4a002b59d8e12dc7e99f2e + react-native-performance: 4194ce7222056671d8f0ce10e96a8fead7278550 react-native-quick-base64: 651d972291fd5d9902869636a1b3c46820324490 react-native-quick-crypto: 71b622aa0a231ac068759f5eef37982ad5eae58c react-native-restart: 0bc732f4461709022a742bb29bcccf6bbc5b4863 diff --git a/packages/core-mobile/metro.config.js b/packages/core-mobile/metro.config.js index 11f292e419..362d3fed55 100644 --- a/packages/core-mobile/metro.config.js +++ b/packages/core-mobile/metro.config.js @@ -1,7 +1,16 @@ +const path = require('path') const { mergeConfig } = require('@react-native/metro-config') const { getSentryExpoConfig } = require('@sentry/react-native/metro') const merge = require('lodash.merge') +// Workspace paths (adjust if your monorepo depth is different) +const projectRoot = __dirname +const workspaceRoot = path.resolve(projectRoot, '..', '..') +const nitroCryptoPath = path.resolve( + workspaceRoot, + 'packages/react-native-nitro-avalabs-crypto' +) + const monorepoConfig = require('./metro.monorepo.config') const defaultConfig = getSentryExpoConfig(__dirname) const { assetExts, sourceExts } = defaultConfig.resolver @@ -25,13 +34,19 @@ const baseConfig = { }), babelTransformerPath: require.resolve('react-native-svg-transformer') }, + watchFolders: [workspaceRoot, nitroCryptoPath], resolver: { // mute warnings about circular dependencies requireCycleIgnorePatterns: [/^app\/.*/, /^node_modules\/.*/], extraNodeModules: { stream: require.resolve('./node_modules/stream-browserify'), - '@noble/hashes': require.resolve('./node_modules/@noble/hashes') + '@noble/hashes': require.resolve('./node_modules/@noble/hashes'), + 'react-native-nitro-avalabs-crypto': nitroCryptoPath }, + nodeModulesPaths: [ + path.resolve(workspaceRoot, 'node_modules'), + path.resolve(projectRoot, 'node_modules') + ], // TODO: should this be a temporary fix? unstable_enablePackageExports: false, // sbmodern is needed for storybook diff --git a/packages/core-mobile/package.json b/packages/core-mobile/package.json index 6fd36c3ddb..d7651fcc33 100644 --- a/packages/core-mobile/package.json +++ b/packages/core-mobile/package.json @@ -199,6 +199,8 @@ "react-native-localize": "3.2.1", "react-native-mmkv": "3.2.0", "react-native-modal-datetime-picker": "18.0.0", + "react-native-nitro-avalabs-crypto": "workspace:*", + "react-native-nitro-modules": "0.31.10", "react-native-os": "1.2.6", "react-native-pager-view": "6.7.1", "react-native-passkey": "3.1.0", @@ -369,7 +371,10 @@ "@wdio/cli>tsx>esbuild": true, "appium": true, "appium-xcuitest-driver>appium-ios-remotexpc>appium-ios-tuntap": false, - "appium>@appium/support>sharp": true + "appium>@appium/support>sharp": true, + "react-native-nitro-modules": true, + "react-native-nitro-avalabs-crypto": true, + "react-native-nitro-avalabs-crypto>react-native-nitro-modules": true } }, "react-native": { diff --git a/packages/core-mobile/react-native.config.js b/packages/core-mobile/react-native.config.js index 76214935f8..4f20438284 100644 --- a/packages/core-mobile/react-native.config.js +++ b/packages/core-mobile/react-native.config.js @@ -1,11 +1,6 @@ module.exports = { assets: ['./app/assets/fonts/'], dependencies: { - 'react-native-performance': { - platforms: { - ios: null, - android: null - } - } + } } diff --git a/packages/react-native-nitro-avalabs-crypto/.eslintrc.js b/packages/react-native-nitro-avalabs-crypto/.eslintrc.js new file mode 100644 index 0000000000..24f264610d --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/.eslintrc.js @@ -0,0 +1,17 @@ +// workaround for https://github.com/eslint/eslint/issues/3458 and https://github.com/yarnpkg/berry/issues/8 +// this allows our shared eslint config to bring along its own plugins +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname + }, + extends: ['plugin:eslint-plugin-avalabs-mobile/all'], + ignorePatterns: [ + '!.storybook/*.ts', + '!.prettierrc.js', + '.storybook/storybook.requires.js' + ] +} diff --git a/packages/react-native-nitro-avalabs-crypto/.gitignore b/packages/react-native-nitro-avalabs-crypto/.gitignore new file mode 100644 index 0000000000..a1de08b78f --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/.gitignore @@ -0,0 +1,90 @@ +# OSX +# +.DS_Store + +# XDE +.expo/ + +# VSCode +.vscode/ +jsconfig.json + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.classpath +.cxx +.gradle +.idea +.project +.settings +local.properties +android.iml + +# Cocoapods +# +example/ios/Pods + +# Ruby +example/vendor/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Expo +.expo/ + +# Turborepo +.turbo/ + +# generated by bob +lib/ + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# Nitro Modules +nitrogen/ + +ios/secp256k1-src +ios/secp-build +ios/secp-out diff --git a/packages/react-native-nitro-avalabs-crypto/.watchmanconfig b/packages/react-native-nitro-avalabs-crypto/.watchmanconfig new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/react-native-nitro-avalabs-crypto/NitroAvalabsCrypto.podspec b/packages/react-native-nitro-avalabs-crypto/NitroAvalabsCrypto.podspec new file mode 100644 index 0000000000..1f5600e4a8 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/NitroAvalabsCrypto.podspec @@ -0,0 +1,70 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +Pod::Spec.new do |s| + s.name = "NitroAvalabsCrypto" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 } + s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" } + + s.source_files = [ + # Implementation (Swift) + "ios/**/*.{swift}", + # Autolinking/Registration (Objective-C++) + "ios/**/*.{m,mm}", + # Implementation (C++ objects) + "cpp/**/*.{hpp,cpp}", + ] + + s.dependency 'React-jsi' + s.dependency 'React-callinvoker' + + load 'nitrogen/generated/ios/NitroAvalabsCrypto+autolinking.rb' + add_nitrogen_files(s) + + + # ----------------------------------------------------------------- + # Build script – produces secp256k1.xcframework + # ----------------------------------------------------------------- + s.script_phases = [{ + :name => 'Build secp256k1 (cached)', + :execution_position => :before_compile, + :shell_path => '/bin/bash', + :script => %Q{bash "${PODS_TARGET_SRCROOT}/ios/scripts/build.sh"} + }] + + # ----------------------------------------------------------------- + # Use the XCFramework – **no manual paths or -lsecp256k1** + # ----------------------------------------------------------------- + s.vendored_frameworks = '$(PODS_TARGET_SRCROOT)/ios/secp-out/secp256k1.xcframework' + + # Keep your C++/Swift flags (they are harmless) + s.pod_target_xcconfig = { + 'HEADER_SEARCH_PATHS' => '$(inherited) $(PODS_TARGET_SRCROOT)/ios/secp-out/include', + 'CLANG_CXX_LIBRARY' => 'libc++', + 'OTHER_CPLUSPLUSFLAGS' => '$(inherited) -std=gnu++20', + 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20', + 'SWIFT_OBJC_INTEROP_MODE' => 'objcxx', + 'DEFINES_MODULE' => 'YES', + 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' + } + + # Tell Xcode where the public headers live + s.xcconfig = { + 'HEADER_SEARCH_PATHS' => '$(inherited) $(PODS_TARGET_SRCROOT)/ios/secp-out/include', + } + + s.user_target_xcconfig = { + 'LIBRARY_SEARCH_PATHS[sdk=iphoneos*]' => '$(inherited) "$(PODS_ROOT)/../../node_modules/react-native-nitro-avalabs-crypto/ios/secp-build/iphoneos-arm64/lib"', + 'LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]' => '$(inherited) "$(PODS_ROOT)/../../node_modules/react-native-nitro-avalabs-crypto/ios/secp-build/iphonesimulator-arm64/lib"', + 'OTHER_LDFLAGS' => '$(inherited) -lsecp256k1' +} + + install_modules_dependencies(s) +end diff --git a/packages/react-native-nitro-avalabs-crypto/README.md b/packages/react-native-nitro-avalabs-crypto/README.md new file mode 100644 index 0000000000..ba8d1d8693 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/README.md @@ -0,0 +1,38 @@ +# react-native-nitro-template + +This is a template for Nitro Modules. + +## Usage + +Clone this repo, and change all `$$*$$` names according to your `nitro.json` file. + +## Contributing + +Contribute a change to this template to update it for newer React Native versions. + +## Structure + +- [`android/`](android): All your `android`-specific implementations. + - [`build.gradle`](android/build.gradle): The gradle build file. This contains four important pieces: + 1. Standard react-native library boilerplate code + 2. Configures Kotlin (`apply plugin: 'org.jetbrains.kotlin.android'`) + 3. Adds all Nitrogen files (`apply from: '.../NitroAvalabsCrypto+autolinking.gradle'`) + 4. Triggers the native C++ build (via CMake/`externalNativeBuild`) + - [`CMakeLists.txt`](android/CMakeLists.txt): The CMake build file to build C++ code. This contains four important pieces: + 1. Creates a library called `NitroAvalabsCrypto` (same as in `nitro.json`) + 2. Adds all Nitrogen files (`include(.../NitroAvalabsCrypto+autolinking.cmake)`) + 3. Adds all custom C++ files (only `HybridTestObjectCpp.cpp`) + 4. Adds a `cpp-adapter.cpp` file, which autolinks all C++ HybridObjects (only `HybridTestObjectCpp`) + - [`src/main/java/com/margelo/nitro/nitroavalabscrypto/`](android/src/main/java/com/margelo/nitro/nitroavalabscrypto/): All Kotlin implementations. + - [`NitroAvalabsCryptoPackage.kt`](android/src/main/java/com/margelo/nitro/nitroavalabscrypto/NitroAvalabsCryptoPackage.kt): The react-native package. You need this because the react-native CLI only adds libraries if they have a `*Package.kt` file. In here, you can autolink all Kotlin HybridObjects. +- [`cpp/`](cpp): All your cross-platform implementations. (only `HybridTestObjectCpp.cpp`) +- [`ios/`](ios): All your iOS-specific implementations. +- [`nitrogen/`](nitrogen): All files generated by nitrogen. You should commit this folder to git. +- [`src/`](src): The TypeScript codebase. This defines all HybridObjects and loads them at runtime. + - [`specs/`](src/specs): All HybridObject types. Nitrogen will run on all `*.nitro.ts` files. +- [`nitro.json`](nitro.json): The configuration file for nitrogen. This will define all native namespaces, as well as the library name. +- [`NitroAvalabsCrypto.podspec`](NitroAvalabsCrypto.podspec): The iOS podspec build file to build the iOS code. This contains three important pieces: + 1. Specifies the Pod's name. This must be identical to the name specified in `nitro.json`. + 2. Adds all of your `.swift` or `.cpp` files (implementations). + 3. Adds all Nitrogen files (`add_nitrogen_files(s)`) +- [`package.json`](package.json): The npm package.json file. `react-native-nitro-modules` should be a `peerDependency`. diff --git a/packages/react-native-nitro-avalabs-crypto/android/CMakeLists.txt b/packages/react-native-nitro-avalabs-crypto/android/CMakeLists.txt new file mode 100644 index 0000000000..ad2111a8f0 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/CMakeLists.txt @@ -0,0 +1,57 @@ +project(NitroAvalabsCrypto) +cmake_minimum_required(VERSION 3.9.0) + +set(PACKAGE_NAME NitroAvalabsCrypto) +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_CXX_STANDARD 20) + +# Define C++ library and add all sources +file(GLOB CPP_SOURCES "../cpp/*.cpp" "../cpp/*.hpp") +add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp ${CPP_SOURCES}) + +# Add Nitrogen specs +include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroAvalabsCrypto+autolinking.cmake) + +# Set up local includes +include_directories("src/main/cpp" "../cpp") + +# Debug variables +message("SECP256K1_ANDROID_ROOT: ${SECP256K1_ANDROID_ROOT}") +message("ANDROID_ABI: ${ANDROID_ABI}") +message("SECP256K1_LIB_DIR: ${SECP256K1_ANDROID_ROOT}/${ANDROID_ABI}/lib") +file(GLOB LIB_FILES "${SECP256K1_ANDROID_ROOT}/${ANDROID_ABI}/lib/*") +message("Files in SECP256K1_LIB_DIR: ${LIB_FILES}") + +# Check for secp256k1 variables +if(NOT DEFINED SECP256K1_INCLUDE_DIR OR NOT DEFINED SECP256K1_ANDROID_ROOT) + message(FATAL_ERROR "SECP256K1 vars not provided. Ensure Gradle passes -DSECP256K1_INCLUDE_DIR and -DSECP256K1_ANDROID_ROOT.") +endif() + +# Set the library path directly +set(SECP256K1_LIB_DIR "${SECP256K1_ANDROID_ROOT}/${ANDROID_ABI}/lib") +file(REAL_PATH "${SECP256K1_LIB_DIR}" RESOLVED_LIB_DIR) +message("Resolved SECP256K1_LIB_DIR: ${RESOLVED_LIB_DIR}") +set(SECP256K1_LIB "${RESOLVED_LIB_DIR}/libsecp256k1.so") +if(NOT EXISTS "${SECP256K1_LIB}") + message(FATAL_ERROR "libsecp256k1.so does not exist at ${SECP256K1_LIB}") +else() + file(SIZE "${SECP256K1_LIB}" LIB_SIZE) + message("libsecp256k1.so size: ${LIB_SIZE} bytes") + if(LIB_SIZE EQUAL 0) + message(FATAL_ERROR "libsecp256k1.so is empty at ${SECP256K1_LIB}") + endif() + message("Manually set libsecp256k1.so at: ${SECP256K1_LIB}") +endif() + +# Add headers for secp256k1 +include_directories("${SECP256K1_INCLUDE_DIR}") + +find_library(LOG_LIB log) + +# Link all libraries together +target_link_libraries( + ${PACKAGE_NAME} + ${LOG_LIB} + ${SECP256K1_LIB} + android +) \ No newline at end of file diff --git a/packages/react-native-nitro-avalabs-crypto/android/build.gradle b/packages/react-native-nitro-avalabs-crypto/android/build.gradle new file mode 100644 index 0000000000..32e3836c47 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/build.gradle @@ -0,0 +1,601 @@ +buildscript { + + // Buildscript is evaluated before everything else so we can't use getExtOrDefault + def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["NitroAvalabsCrypto_kotlinVersion"] + + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.7.2" + // noinspection DifferentKotlinGradleVersion + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" + } +} + +def reactNativeArchitectures() { + def value = rootProject.getProperties().get("reactNativeArchitectures") + def res = value ? value.split(",") : ["armeabi-v7a", "arm64-v8a"] + println "[reactNativeArchitectures] Using architectures: ${res}" + return res +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroAvalabsCrypto_" + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroAvalabsCrypto_" + name]).toInteger() +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +apply from: '../nitrogen/generated/android/NitroAvalabsCrypto+autolinking.gradle' + +// === secp256k1 Android build wiring (headers + .so per ABI) === +ext { + SECP_OUTPUTS_INCLUDE_DIR = "$buildDir/outputs/include" + SECP_OUTPUTS_ROOT = "$buildDir/outputs/android" // /lib/libsecp256k1.so +} + +ext { + secpRepoUrl = 'https://github.com/bitcoin-core/secp256k1.git' + secpTag = 'v0.7.0' + secpCommit = '' + workDirBase = file("$buildDir/secp256k1") + installRoot = file("$buildDir/secp-android") + outputsRoot = file(SECP_OUTPUTS_ROOT) + outputsIncludeDir = file(SECP_OUTPUTS_INCLUDE_DIR) + enableModules = (project.findProperty('secpModules') ?: '') // e.g., ecdh,recovery,schnorrsig,extrakeys + // Use the same API level for all ABIs; default to max(minSdkVersion, 21). Override with -PsecpApi. + secpApi = (project.findProperty('secpApi') ?: Math.max(getExtOrIntegerDefault("minSdkVersion"), 21)).toString() // Base directory +} + +import org.apache.tools.ant.taskdefs.condition.Os + +def isMac() { Os.isFamily(Os.FAMILY_MAC) } +def isLinux() { Os.isFamily(Os.FAMILY_UNIX) && !Os.isFamily(Os.FAMILY_MAC) } +def isWindows() { Os.isFamily(Os.FAMILY_WINDOWS) } +def standardOutput = new ByteArrayOutputStream() +def errorOutput = new ByteArrayOutputStream() + +def findNdkRoot() { + // First, check environment variables (works for Bitrise and most CI) + def fromEnv = ['ANDROID_NDK_HOME','ANDROID_NDK_ROOT','ANDROID_NDK'].collect { System.getenv(it) } + .find { it && file(it).exists() } + + if (fromEnv) { + println "[NDK] Found NDK via environment: ${fromEnv}" + return fromEnv + } + + // Fallback: try to find in SDK directory + def home = System.getenv('HOME') ?: System.getProperty('user.home') + def sdkRoot = System.getenv('ANDROID_SDK_ROOT') ?: System.getenv('ANDROID_HOME') + + // Common SDK locations by platform + def sdkPaths = [] + if (sdkRoot) { + sdkPaths << file(sdkRoot) + } + if (isMac()) { + sdkPaths << file("${home}/Library/Android/sdk") + } else if (isLinux()) { + sdkPaths << file("/opt/android-sdk-linux") // Common Bitrise location + sdkPaths << file("/opt/android-sdk") + sdkPaths << file("${home}/Android/Sdk") + sdkPaths << file("${home}/android-sdk") + } else if (isWindows()) { + sdkPaths << file("${home}/AppData/Local/Android/Sdk") + } + + // Try ndk-bundle first (legacy) + for (sdkPath in sdkPaths) { + if (sdkPath.exists()) { + def ndkBundle = new File(sdkPath, "ndk-bundle") + if (ndkBundle.exists()) { + println "[NDK] Found NDK bundle: ${ndkBundle}" + return ndkBundle.absolutePath + } + } + } + + // Try versioned NDK directory (newer approach) + for (sdkPath in sdkPaths) { + if (sdkPath.exists()) { + def ndkDir = new File(sdkPath, "ndk") + if (ndkDir.exists()) { + def versions = ndkDir.listFiles()?.findAll { it.isDirectory() }?.sort { a, b -> + // Sort by version number (descending) to get latest + b.name.replaceAll(/[^\d.]/, '') <=> a.name.replaceAll(/[^\d.]/, '') + } + if (versions && !versions.isEmpty()) { + println "[NDK] Found versioned NDK: ${versions.first()}" + return versions.first().absolutePath + } + } + } + } + + throw new GradleException(""" + Android NDK not found! + For local development: + - Install NDK via Android Studio SDK Manager + - Or set ANDROID_NDK_HOME environment variable + + Current search paths tried: ${sdkPaths.collect { it.absolutePath }.join(', ')} + """) +} + +def ndkRoot = file(findNdkRoot()) +println "[NDK] Using NDK root: ${ndkRoot.absolutePath}" + +// Detect host tag (platform-specific prebuilt toolchain directory) +def detectHostTag() { + def ndkRoot = file(findNdkRoot()) + def prebuiltDir = file("${ndkRoot}/toolchains/llvm/prebuilt") + + if (!prebuiltDir.exists()) { + throw new GradleException("NDK toolchain not found at ${prebuiltDir}. Your NDK installation may be corrupted.") + } + + def candidates = [] + if (isMac()) { + candidates = ['darwin-arm64', 'darwin-x86_64'] + } else if (isLinux()) { + candidates = ['linux-x86_64', 'linux-x86'] + } else if (isWindows()) { + candidates = ['windows-x86_64', 'windows'] + } + + // Try expected candidates first + def found = candidates.find { new File(prebuiltDir, it).exists() } + if (found) { + println "[NDK] Detected host tag: ${found}" + return found + } + + // Fallback: find any directory + def dirs = prebuiltDir.listFiles()?.findAll { it.isDirectory() } + if (dirs && !dirs.isEmpty()) { + def fallback = dirs.first().name + println "[NDK] Using fallback host tag: ${fallback}" + return fallback + } + + throw new GradleException("Cannot locate NDK llvm/prebuilt host tag under ${prebuiltDir}. Available: ${prebuiltDir.list()?.join(', ')}") +} + +def hostTag = detectHostTag() +def toolBin = file("${ndkRoot}/toolchains/llvm/prebuilt/${hostTag}/bin") + +if (!toolBin.exists()) { + throw new GradleException("NDK toolchain bin directory not found: ${toolBin}") +} + +println "[NDK] Toolchain bin: ${toolBin.absolutePath}" + +def abiMatrix = { + [ + 'armeabi-v7a': [ host:'arm-linux-androideabi', target:'armv7a-linux-androideabi', api: secpApi, march: 'armv7-a', mfpu: 'neon' ], + 'arm64-v8a' : [ host:'aarch64-linux-android', target:'aarch64-linux-android', api: secpApi, march: null, mfpu: null ], + 'x86' : [ host:'i686-linux-android', target:'i686-linux-android', api: secpApi, march: null, mfpu: null ], + 'x86_64' : [ host:'x86_64-linux-android', target:'x86_64-linux-android', api: secpApi, march: null, mfpu: null ], + ] +}.call() + +def moduleFlagsString = { + def map = [ + ecdh : '--enable-module-ecdh', + recovery : '--enable-module-recovery', + extrakeys : '--enable-module-extrakeys', + schnorrsig: '--enable-module-schnorrsig', + ] + (enableModules ?: '').split(',').collect { it.trim() }.findAll { it }.collect { map[it] ?: '' }.findAll { it }.join(' ') +}.call() + +// Fetch repo at pinned tag or commit (incremental) +tasks.register('fetchSecp256k1') { + inputs.property('repoUrl', secpRepoUrl) + inputs.property('tag', secpTag) + inputs.property('commit', secpCommit) + def workDir = workDirBase + outputs.dir(workDir) + + doLast { + if (!workDir.exists()) { + workDir.mkdirs() + } + + def ref = (secpTag ?: secpCommit ?: '').trim() + if (ref) { + def ok = true + try { + exec { workingDir workDir; commandLine 'git', 'init'; ignoreExitValue true } + exec { workingDir workDir; commandLine 'git', 'remote', 'remove', 'origin'; ignoreExitValue true } + exec { workingDir workDir; commandLine 'git', 'remote', 'add', 'origin', secpRepoUrl; ignoreExitValue true } + exec { workingDir workDir; commandLine 'git', 'fetch', '--tags', '--prune', 'origin' } + def co1 = exec { workingDir workDir; commandLine 'git', 'checkout', "refs/tags/${ref}"; ignoreExitValue true } + if (co1.exitValue != 0) { + def co2 = exec { workingDir workDir; commandLine 'git', 'checkout', ref; ignoreExitValue true } + if (co2.exitValue != 0) ok = false + } + } catch (Exception e) { + ok = false + } + + if (!ok) { + // --- Fallback: download release tarball --- + def url = "https://github.com/bitcoin-core/secp256k1/archive/refs/tags/${ref}.tar.gz" + def tarFile = file("${buildDir}/secp256k1-${ref}.tar.gz") + def refNoV = ref.startsWith('v') ? ref.substring(1) : ref + def extractDir = file("${buildDir}/secp256k1-${refNoV}") + + println "[secp] Git checkout failed for '${ref}', falling back to tarball: ${url}" + def curlRes = exec { commandLine 'curl', '-fL', '-o', tarFile.absolutePath, url; ignoreExitValue true } + if (curlRes.exitValue != 0) { + throw new GradleException("Failed to download ${url}. Try setting -PsecpTag to a valid release (e.g., v0.6.0).") + } + // Ensure clean target + if (extractDir.exists()) extractDir.deleteDir() + exec { commandLine 'tar', '-xzf', tarFile.absolutePath, '-C', buildDir.absolutePath } + + if (!extractDir.exists()) { + throw new GradleException("Expected extracted folder ${extractDir} not found after untar. Tag '${ref}' may be invalid.") + } + // Move to workDir + if (workDir.exists() && workDir.listFiles()?.length) workDir.deleteDir() + extractDir.renameTo(workDir) + println "[secp] Extracted to ${workDir}" + } else { + println "[secp] Checked out ${ref} via git" + } + } else { + // No explicit ref: clone/update default branch best-effort + if (!new File(workDir, '.git').exists()) { + exec { commandLine 'git', 'clone', secpRepoUrl, workDir.absolutePath } + } else { + exec { workingDir workDir; commandLine 'git', 'pull', '--ff-only', 'origin'; ignoreExitValue true } + } + } + } +} + +// Per-ABI configure/build (incremental) + +def abis = ['armeabi-v7a','arm64-v8a'] + +abis.each { abi -> + def meta = abiMatrix[abi] + def ccTrip = "${meta.target}${meta.api}-clang" + // Use LLVM tools for consistency (NDK r25+) + def arTool = 'llvm-ar' + def ranlibTool = 'llvm-ranlib' + def stripTool = 'llvm-strip' + + def workDir = file("${workDirBase}/${abi}") + + def installDir = file("${installRoot}/${abi}") + def stagedLib = file("${outputsRoot}/${abi}/lib/libsecp256k1.so") + def libDirOut = stagedLib.parentFile + def stampFile = file("${buildDir}/stamps/secp_${abi}.stamp") + + tasks.register("configureSecp_${abi}") { + dependsOn 'fetchSecp256k1' + + inputs.property('host', meta.host) + inputs.property('target', meta.target) + inputs.property('api', meta.api) + inputs.property('commit', secpCommit) + inputs.property('modules', enableModules) + inputs.property('ndkRoot', ndkRoot.absolutePath) + inputs.property('hostTag', hostTag) + inputs.files(fileTree(dir: workDirBase, includes: ['configure.ac','Makefile.am','autogen.sh'])).withPathSensitivity(PathSensitivity.RELATIVE) + outputs.file(stampFile) + + onlyIf { !(stampFile.exists() && stagedLib.exists()) } + + doLast { + + // Copy source to ABI-specific directory + if (!workDir.exists() || !new File(workDir, 'configure.ac').exists()) { + workDir.mkdirs() + copy { + from workDirBase + into workDir + } + } + + outputsIncludeDir.mkdirs() + installDir.mkdirs() + libDirOut.mkdirs() + file("${buildDir}/stamps").mkdirs() + + // Run autogen.sh if configure script doesn't exist + def configureScript = new File(workDir, 'configure') + if (!configureScript.exists()) { + println "[secp:${abi}] Running autogen.sh to generate configure script..." + exec { + workingDir workDir + commandLine 'bash', '-c', './autogen.sh' + // Ensure PATH includes standard locations for autotools + environment 'PATH', System.getenv('PATH') + } + } else { + println "[secp:${abi}] Configure script already exists, skipping autogen.sh" + } + + println "[secp:${abi}] Using API level ${meta.api} (unified)" + + // Platform-specific libtool names + def libtoolize = isMac() ? 'glibtoolize' : 'libtoolize' + def libtool = isMac() ? 'glibtool' : 'libtool' + + def sysroot = new File(ndkRoot, "toolchains/llvm/prebuilt/${hostTag}/sysroot").absolutePath + + // Build CFLAGS with proper architecture flags + def cflags = ["-fPIC", "-O2", "--sysroot=${sysroot}"] + def ldflags = [] + + // ARM-specific flags + if (abi == 'armeabi-v7a') { + cflags += ["-march=${meta.march}", "-mthumb", "-mfpu=${meta.mfpu}", "-mfloat-abi=softfp"] + ldflags += ["-march=${meta.march}"] + } + + def cflagsStr = cflags.join(' ') + def ldflagsStr = ldflags.join(' ') + + println "[secp:${abi}] CFLAGS: ${cflagsStr}" + println "[secp:${abi}] LDFLAGS: ${ldflagsStr}" + + exec { + + workingDir workDir + environment 'CC', new File(toolBin, ccTrip).absolutePath + // Use LLVM binutils with libtool-friendly flags (NDK r25+) + environment 'AR', new File(toolBin, arTool).absolutePath + environment 'ARFLAGS', 'crs' + environment 'RANLIB', new File(toolBin, ranlibTool).absolutePath + environment 'NM', new File(toolBin, 'llvm-nm').absolutePath + environment 'STRIP', new File(toolBin, stripTool).absolutePath + // Architecture-appropriate CFLAGS + environment 'CFLAGS', cflagsStr + environment 'LDFLAGS', ldflagsStr + // Link against Android log for __android_log_print and enable optional configure logging + environment 'LIBS', '-llog' + // On macOS, libtool is glibtool/gnulibtool; autogen.sh already glibtoolize's, but keep these for safety + environment 'LIBTOOLIZE', libtoolize + environment 'LIBTOOL', libtool + + // Disable PIE for older Android (if needed) + environment 'lt_cv_prog_compiler_pic_works', 'yes' + environment 'lt_cv_prog_compiler_static_works', 'yes' + + def configureArgs = [ + './configure', + "--host=${meta.host}", + '--enable-shared', + '--disable-static', + '--with-pic', + '--disable-benchmark', + '--disable-tests', + "--prefix=${installDir.absolutePath}", + moduleFlagsString + ].findAll { it }.join(' ') + + // Don't use login shell + commandLine 'bash', '-c', configureArgs + } + + // Capture output for debugging + standardOutput = new ByteArrayOutputStream() + errorOutput = new ByteArrayOutputStream() + + println "[secp:${abi}] configure output: ${standardOutput.toString()}" + if (errorOutput.toString()) { + println "[secp:${abi}] configure errors: ${errorOutput.toString()}" + } + + stampFile.text = "configured:${abi}:${secpCommit}:${enableModules}:${meta.api}:${hostTag}:${ndkRoot}" + } + } + + tasks.register("buildSecp_${abi}") { + dependsOn "configureSecp_${abi}" + + inputs.file(file("${workDir}/Makefile")) + inputs.property('commit', secpCommit) + inputs.property('modules', enableModules) + outputs.file(stagedLib) + + onlyIf { !stagedLib.exists() } + + doLast { + def jobs = project.findProperty('jobs') ?: Runtime.runtime.availableProcessors() + def buildOutput = new ByteArrayOutputStream() + def buildError = new ByteArrayOutputStream() + + // Platform-specific libtool + def libtoolize = isMac() ? 'glibtoolize' : 'libtoolize' + def libtool = isMac() ? 'glibtool' : 'libtool' + + exec { + workingDir workDir + environment 'LIBS', '-llog' + environment 'LIBTOOLIZE', libtoolize + environment 'LIBTOOL', libtool + // Don't use login shell + commandLine 'bash', '-c', "make -j${jobs} && make install" + standardOutput = buildOutput + errorOutput = buildError + } + + // Print output for debugging + def outputLines = buildOutput.toString().readLines() + if (outputLines.size() > 50) { + println "[secp:${abi}] build output (last 50 lines): ${outputLines.takeRight(50).join('\n')}" + } else { + println "[secp:${abi}] build output: ${buildOutput.toString()}" + } + if (buildError.toString()) { + println "[secp:${abi}] build errors: ${buildError.toString()}" + } + + copy { + from file("${installDir}/lib") + include 'libsecp256k1.so*' + into libDirOut + } + + if (!outputsIncludeDir.exists() || outputsIncludeDir.listFiles() == null || outputsIncludeDir.listFiles().length == 0) { + copy { + from file("${installDir}/include") + into outputsIncludeDir + } + } + println "[${abi}] staged -> ${stagedLib}" + } + } +} + +// Aggregate task + tasks.register('buildSecpAllAndroid') { + group = 'build' + description = 'Build secp256k1 Android .so for all ABIs and stage headers (incremental)' + dependsOn abis.collect { "buildSecp_${it}" } + } + +// Clean helper + tasks.register('cleanSecpAndroid') { + doLast { delete workDirBase, installRoot, outputsRoot, outputsIncludeDir, file("${buildDir}/stamps") } + } + +android { + namespace "com.margelo.nitro.nitroavalabscrypto" + + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + + externalNativeBuild { + cmake { + cppFlags "-frtti -fexceptions -Wall -fstack-protector-all" + arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", + "-DSECP256K1_INCLUDE_DIR=${SECP_OUTPUTS_INCLUDE_DIR}", "-DSECP256K1_ANDROID_ROOT=${SECP_OUTPUTS_ROOT}" + abiFilters (*reactNativeArchitectures()) + + buildTypes { + debug { + cppFlags "-O1 -g" + } + release { + cppFlags "-O2" + } + } + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + packagingOptions { + excludes = [ + "META-INF", + "META-INF/**", + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libfolly_json.so", + "**/libfolly_runtime.so", + "**/libglog.so", + "**/libhermes.so", + "**/libhermes-executor-debug.so", + "**/libhermes_executor.so", + "**/libreactnative.so", + "**/libreactnativejni.so", + "**/libturbomodulejsijni.so", + "**/libreact_nativemodule_core.so", + "**/libjscexecutor.so" + ] + pickFirst "**/libsecp256k1.so" + } + + buildFeatures { + buildConfig true + prefab true + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + java.srcDirs += [ + "generated/java", + "generated/jni" + ] + } + } +} + +// Ensure native build tasks run after secp outputs are prepared (AGP 8.x safe) +afterEvaluate { + def secpTask = tasks.named('buildSecpAllAndroid') + + tasks.configureEach { t -> + def n = t.name + if (n.startsWith('externalNativeBuild') || + n.startsWith('configureCMake') || + n.startsWith('buildCMake') || + n.startsWith('generateJsonModel') || + n.startsWith('prefabGenerate')) { + t.dependsOn(secpTask) + t.doFirst { + println "[secp] '${n}' dependsOn buildSecpAllAndroid" + } + } + } +} + +repositories { + mavenCentral() + google() +} + +def kotlin_version = getExtOrDefault("kotlinVersion") + +dependencies { + implementation "com.facebook.react:react-android" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation project(":react-native-nitro-modules") +} diff --git a/packages/react-native-nitro-avalabs-crypto/android/fix-prefab.gradle b/packages/react-native-nitro-avalabs-crypto/android/fix-prefab.gradle new file mode 100644 index 0000000000..d6c010ef78 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/fix-prefab.gradle @@ -0,0 +1,51 @@ +tasks.configureEach { task -> + // Make sure that we generate our prefab publication file only after having built the native library + // so that not a header publication file, but a full configuration publication will be generated, which + // will include the .so file + + def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/ + def matcher = task.name =~ prefabConfigurePattern + if (matcher.matches()) { + def variantName = matcher[0][1] + task.outputs.upToDateWhen { false } + task.dependsOn("externalNativeBuild${variantName}") + } +} + +afterEvaluate { + def abis = reactNativeArchitectures() + rootProject.allprojects.each { proj -> + if (proj === rootProject) return + + def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config -> + config.dependencies.any { dep -> + dep.group == project.group && dep.name == project.name + } + } + if (!dependsOnThisLib && proj != project) return + + if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) { + return + } + + def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants + // Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to + // generate a libnameConfig.cmake file that will contain our native library (.so). + // See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue + variants.all { variant -> + def variantName = variant.name + abis.each { abi -> + def searchDir = new File(proj.projectDir, ".cxx/${variantName}") + if (!searchDir.exists()) return + def matches = [] + searchDir.eachDir { randomDir -> + def prefabFile = new File(randomDir, "${abi}/prefab_config.json") + if (prefabFile.exists()) matches << prefabFile + } + matches.each { prefabConfig -> + prefabConfig.setLastModified(System.currentTimeMillis()) + } + } + } + } +} diff --git a/packages/react-native-nitro-avalabs-crypto/android/gradle.properties b/packages/react-native-nitro-avalabs-crypto/android/gradle.properties new file mode 100644 index 0000000000..270c644849 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/gradle.properties @@ -0,0 +1,5 @@ +NitroAvalabsCrypto_kotlinVersion=2.1.20 +NitroAvalabsCrypto_minSdkVersion=23 +NitroAvalabsCrypto_targetSdkVersion=36 +NitroAvalabsCrypto_compileSdkVersion=36 +NitroAvalabsCrypto_ndkVersion=27.1.12297006 diff --git a/packages/react-native-nitro-avalabs-crypto/android/src/main/AndroidManifest.xml b/packages/react-native-nitro-avalabs-crypto/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a2f47b6057 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/packages/react-native-nitro-avalabs-crypto/android/src/main/cpp/cpp-adapter.cpp b/packages/react-native-nitro-avalabs-crypto/android/src/main/cpp/cpp-adapter.cpp new file mode 100644 index 0000000000..4de74b7369 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/src/main/cpp/cpp-adapter.cpp @@ -0,0 +1,6 @@ +#include +#include "NitroAvalabsCryptoOnLoad.hpp" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + return margelo::nitro::nitroavalabscrypto::initialize(vm); +} diff --git a/packages/react-native-nitro-avalabs-crypto/android/src/main/java/com/margelo/nitro/nitroavalabscrypto/NitroAvalabsCryptoPackage.kt b/packages/react-native-nitro-avalabs-crypto/android/src/main/java/com/margelo/nitro/nitroavalabscrypto/NitroAvalabsCryptoPackage.kt new file mode 100644 index 0000000000..9895a37372 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/android/src/main/java/com/margelo/nitro/nitroavalabscrypto/NitroAvalabsCryptoPackage.kt @@ -0,0 +1,18 @@ +package com.margelo.nitro.nitroavalabscrypto + +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.BaseReactPackage + +class NitroAvalabsCryptoPackage : BaseReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() } + + companion object { + init { + NitroAvalabsCryptoOnLoad.initializeNative() + } + } +} diff --git a/packages/react-native-nitro-avalabs-crypto/babel.config.js b/packages/react-native-nitro-avalabs-crypto/babel.config.js new file mode 100644 index 0000000000..3e0218e68f --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:@react-native/babel-preset'], +} diff --git a/packages/react-native-nitro-avalabs-crypto/cpp/CryptoHybrid.cpp b/packages/react-native-nitro-avalabs-crypto/cpp/CryptoHybrid.cpp new file mode 100644 index 0000000000..860dbf184f --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/cpp/CryptoHybrid.cpp @@ -0,0 +1,316 @@ +#include "CryptoHybrid.hpp" +#include +#include +#include +#include +#ifdef __ANDROID__ +#include +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "CryptoHybrid", __VA_ARGS__) +#else +#define LOGI(...) +#endif + +static std::string toHex(const uint8_t* p, size_t n) { + static const char* k = "0123456789abcdef"; + std::string s; s.resize(n*2); + for (size_t i=0;i>4]; s[2*i+1]=k[p[i]&0xF]; } + return s; +} + +namespace margelo::nitro::nitroavalabscrypto { + +namespace { + std::once_flag g_once; + secp256k1_context* g_ctx = nullptr; + + inline uint8_t hexNibble(char c) { + if (c >= '0' && c <= '9') return static_cast(c - '0'); + c = static_cast(std::tolower(static_cast(c))); + if (c >= 'a' && c <= 'f') return static_cast(10 + (c - 'a')); + throw std::invalid_argument("Invalid hex character"); + } +} + +secp256k1_context* CryptoHybrid::ctx() { + std::call_once(g_once, [] { + g_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + // optional randomization (not strictly required) + unsigned char seed[32] = {0}; + (void)secp256k1_context_randomize(g_ctx, seed); + }); + return g_ctx; +} + +std::vector CryptoHybrid::hexToBytes(const std::string& h) { + std::string hex = h; + if (hex.rfind("0x", 0) == 0 || hex.rfind("0X", 0) == 0) hex.erase(0, 2); + if (hex.size() % 2 != 0) throw std::invalid_argument("Hex string must have even length"); + std::vector out(hex.size()/2); + for (size_t i=0;i((hexNibble(hex[2*i]) << 4) | hexNibble(hex[2*i+1])); + } + return out; +} + +std::vector CryptoHybrid::bytesFromVariant(const BufferOrString& v) { + if (std::holds_alternative(v)) { + return hexToBytes(std::get(v)); + } else { + const auto& ab = std::get>(v); + if (!ab) throw std::invalid_argument("ArrayBuffer is null"); + auto* p = reinterpret_cast(ab->data()); + return std::vector(p, p + ab->size()); + } +} + +std::array CryptoHybrid::require32(const BufferOrString& v, const char* what) { + auto bytes = bytesFromVariant(v); + if (bytes.size() != 32) { + throw std::invalid_argument(std::string(what) + " must be 32 bytes"); + } + std::array out{}; + std::copy(bytes.begin(), bytes.end(), out.begin()); + return out; +} + +secp256k1_pubkey CryptoHybrid::parsePubkey(const std::vector& in) { + secp256k1_pubkey pk{}; + // Accept 33 (compressed) or 65 (uncompressed) + if (in.size() != 33 && in.size() != 65) { + throw std::invalid_argument("Public key must be 33 or 65 bytes"); + } + if (secp256k1_ec_pubkey_parse(ctx(), &pk, in.data(), in.size()) != 1) { + throw std::invalid_argument("Invalid public key bytes"); + } + return pk; +} + +std::vector CryptoHybrid::serializePubkey(const secp256k1_pubkey& pk, bool compressed) { + size_t len = compressed ? 33 : 65; + std::vector out(len); + unsigned int flags = compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED; + if (secp256k1_ec_pubkey_serialize(ctx(), out.data(), &len, &pk, flags) != 1) { + throw std::runtime_error("Failed to serialize public key"); + } + out.resize(len); + return out; +} + +std::shared_ptr CryptoHybrid::toAB(const std::vector& v) { + auto ab = ArrayBuffer::allocate(v.size()); + std::memcpy(ab->data(), v.data(), v.size()); + return ab; +} + +/* ---------- getPublicKey* (ECDSA-style pubkey from 32-byte seckey) ---------- */ + +std::shared_ptr CryptoHybrid::getPublicKeyFromString(const std::string& secretKey, std::optional isCompressed) { + auto sk = hexToBytes(secretKey); + if (sk.size() != 32) throw std::invalid_argument("secretKey must be 32 bytes hex"); + bool comp = isCompressed.value_or(true); + + secp256k1_pubkey pk{}; + if (secp256k1_ec_seckey_verify(ctx(), sk.data()) != 1) + throw std::invalid_argument("Invalid secret key"); + if (secp256k1_ec_pubkey_create(ctx(), &pk, sk.data()) != 1) + throw std::runtime_error("secp256k1_ec_pubkey_create failed"); + + return toAB(serializePubkey(pk, comp)); +} + +std::shared_ptr CryptoHybrid::getPublicKeyFromArrayBuffer(const std::shared_ptr& secretKey, std::optional isCompressed) { + if (!secretKey) throw std::invalid_argument("secretKey ArrayBuffer is null"); + if (secretKey->size() != 32) throw std::invalid_argument("secretKey must be 32 bytes"); + bool comp = isCompressed.value_or(true); + + const auto* sk = reinterpret_cast(secretKey->data()); + if (secp256k1_ec_seckey_verify(ctx(), sk) != 1) + throw std::invalid_argument("Invalid secret key"); + + secp256k1_pubkey pk{}; + if (secp256k1_ec_pubkey_create(ctx(), &pk, sk) != 1) + throw std::runtime_error("secp256k1_ec_pubkey_create failed"); + + return toAB(serializePubkey(pk, comp)); +} + +/* ------------------------ pointAddScalar: P + t·G ------------------------- */ + +std::shared_ptr CryptoHybrid::pointAddScalar( + const BufferOrString& publicKey, + const BufferOrString& tweak, + std::optional isCompressed +) { + bool comp = isCompressed.value_or(true); + auto pkBytes = bytesFromVariant(publicKey); + auto t32 = require32(tweak, "tweak"); + + secp256k1_pubkey pk = parsePubkey(pkBytes); + + // P = P + t*G + if (secp256k1_ec_pubkey_tweak_add(ctx(), &pk, t32.data()) != 1) { + throw std::runtime_error("Tweak add failed (invalid tweak or infinity)"); + } + + return toAB(serializePubkey(pk, comp)); +} + +/* ----------------------------- ECDSA sign/verify -------------------------- */ +/* Assumption: message is a 32-byte digest (e.g., SHA-256). */ + +std::shared_ptr CryptoHybrid::sign( + const BufferOrString& secretKey, + const BufferOrString& message +) { + auto sk = require32(secretKey, "secretKey"); + auto msg32 = require32(message, "message"); + + if (secp256k1_ec_seckey_verify(ctx(), sk.data()) != 1) + throw std::invalid_argument("Invalid secret key"); + + secp256k1_ecdsa_signature sig{}; + if (secp256k1_ecdsa_sign(ctx(), &sig, msg32.data(), sk.data(), nullptr, nullptr) != 1) { + throw std::runtime_error("ECDSA sign failed"); + } + + // DER serialize + std::vector der(72); // max DER size ~72 bytes + size_t derlen = der.size(); + if (secp256k1_ecdsa_signature_serialize_der(ctx(), der.data(), &derlen, &sig) != 1) { + throw std::runtime_error("ECDSA DER serialization failed"); + } + der.resize(derlen); + return toAB(der); +} + +bool CryptoHybrid::verify( + const BufferOrString& publicKey, + const BufferOrString& message, + const BufferOrString& signature +) { + auto pkBytes = bytesFromVariant(publicKey); + auto msg32 = require32(message, "message"); + + secp256k1_pubkey pk = parsePubkey(pkBytes); + + auto sigBytes = bytesFromVariant(signature); + secp256k1_ecdsa_signature sig{}; + + int parsed = 0; + // Try DER first + if (!sigBytes.empty()) { + parsed = secp256k1_ecdsa_signature_parse_der(ctx(), &sig, sigBytes.data(), sigBytes.size()); + if (!parsed && sigBytes.size() == 64) { + // Try compact if 64 bytes + parsed = secp256k1_ecdsa_signature_parse_compact(ctx(), &sig, sigBytes.data()); + } + } + if (!parsed) return false; + + // Normalize (makes high-S equal low-S for verification) + secp256k1_ecdsa_signature sigNorm = sig; + (void)secp256k1_ecdsa_signature_normalize(ctx(), &sigNorm, &sig); + + return secp256k1_ecdsa_verify(ctx(), &sigNorm, msg32.data(), &pk) == 1; +} + +/* -------------------------- Schnorr sign/verify --------------------------- */ +/* Assumption: messageHash is a 32-byte BIP340 digest; pubkey may be 32-byte xonly + or 33/65-byte normal EC key (we’ll convert to xonly). */ + +std::shared_ptr CryptoHybrid::signSchnorr( + const BufferOrString& secretKey, + const BufferOrString& messageHash, + const BufferOrString& auxRand +) { + auto sk = require32(secretKey, "secretKey"); + auto msg32 = require32(messageHash, "messageHash"); + auto aux32 = require32(auxRand, "auxRand"); + + LOGI("signSchnorr sk=%s", toHex(sk.data(), 32).c_str()); + LOGI("signSchnorr msg=%s", toHex(msg32.data(), 32).c_str()); + LOGI("signSchnorr aux=%s", toHex(aux32.data(), 32).c_str()); + + if (secp256k1_ec_seckey_verify(ctx(), sk.data()) != 1) + throw std::invalid_argument("Invalid secret key"); + + secp256k1_keypair keypair; + if (secp256k1_keypair_create(ctx(), &keypair, sk.data()) != 1) + throw std::runtime_error("keypair_create failed"); + + // --- DEBUG: log x-only and compressed pubkeys --- + { + // X-only pubkey (BIP340) + secp256k1_xonly_pubkey xpk{}; + int parity = 0; + if (secp256k1_keypair_xonly_pub(ctx(), &xpk, &parity, &keypair) == 1) { + unsigned char x32[32]; + secp256k1_xonly_pubkey_serialize(ctx(), x32, &xpk); + LOGI("signSchnorr xonly.pk.x=%s parity=%d", toHex(x32, 32).c_str(), parity); + } else { + LOGI("signSchnorr xonly.pk FAILED"); + } + + // Full compressed pubkey (33 bytes) + secp256k1_pubkey full{}; + if (secp256k1_ec_pubkey_create(ctx(), &full, sk.data()) == 1) { + unsigned char comp33[33]; + size_t compLen = 33; + if (secp256k1_ec_pubkey_serialize(ctx(), comp33, &compLen, &full, SECP256K1_EC_COMPRESSED) == 1) { + LOGI("signSchnorr pubkey(compressed,33)=%s", toHex(comp33, compLen).c_str()); + } else { + LOGI("signSchnorr pubkey serialize FAILED"); + } + } else { + LOGI("signSchnorr ec_pubkey_create FAILED"); + } + } + + std::array sig64{}; + if (secp256k1_schnorrsig_sign32(ctx(), sig64.data(), msg32.data(), &keypair, aux32.data()) != 1) + throw std::runtime_error("schnorrsig_sign32 failed"); + + // --- Self-verify before returning --- + secp256k1_xonly_pubkey xpk{}; + int parity = 0; + if (secp256k1_keypair_xonly_pub(ctx(), &xpk, &parity, &keypair) != 1) + throw std::runtime_error("keypair_xonly_pub failed"); + + if (secp256k1_schnorrsig_verify(ctx(), sig64.data(), msg32.data(), 32, &xpk) != 1) { + LOGI("signSchnorr self-verify FAILED"); + throw std::runtime_error("Schnorr self-verify failed"); + } + + LOGI("signSchnorr sig=%s", toHex(sig64.data(), 64).c_str()); + return toAB(std::vector(sig64.begin(), sig64.end())); +} + +bool CryptoHybrid::verifySchnorr( + const BufferOrString& publicKey, + const BufferOrString& messageHash, + const BufferOrString& signature +) { + auto msg32 = require32(messageHash, "messageHash"); + auto sig = bytesFromVariant(signature); + if (sig.size() != 64) return false; + + auto pkBytes = bytesFromVariant(publicKey); + LOGI("verifySchnorr pkLen=%zu msg=%s sig=%s", pkBytes.size(), + toHex(reinterpret_cast(msg32.data()), 32).c_str(), + toHex(sig.data(), 64).c_str()); + + secp256k1_xonly_pubkey xpk{}; + if (pkBytes.size() == 32) { + if (secp256k1_xonly_pubkey_parse(ctx(), &xpk, pkBytes.data()) != 1) return false; + } else { + try { + secp256k1_pubkey full = parsePubkey(pkBytes); + int parity = 0; + if (secp256k1_xonly_pubkey_from_pubkey(ctx(), &xpk, &parity, &full) != 1) return false; + } catch (...) { return false; } + } + + return secp256k1_schnorrsig_verify(ctx(), sig.data(), msg32.data(), 32, &xpk) == 1; +} + +} // namespace margelo::nitro::nitroavalabscrypto diff --git a/packages/react-native-nitro-avalabs-crypto/cpp/CryptoHybrid.hpp b/packages/react-native-nitro-avalabs-crypto/cpp/CryptoHybrid.hpp new file mode 100644 index 0000000000..5099fe133e --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/cpp/CryptoHybrid.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "HybridCryptoSpec.hpp" // your generated spec +#include +// Schnorr / xonly +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace margelo::nitro::nitroavalabscrypto { + +class CryptoHybrid final : public HybridCryptoSpec { +public: + // Per your spec’s note: explicitly call HybridObject(TAG) in ctor + CryptoHybrid() : HybridObject(TAG) {} + ~CryptoHybrid() override = default; + + typedef std::variant, std::string> BufferOrString; + + // ---- Spec methods ---- + std::shared_ptr getPublicKey( + const std::variant>& secretKey, + std::optional isCompressed); + + std::shared_ptr getPublicKeyFromString(const std::string& secretKey, std::optional isCompressed) override; + std::shared_ptr getPublicKeyFromArrayBuffer(const std::shared_ptr& secretKey, std::optional isCompressed) override; + std::shared_ptr pointAddScalar( + const BufferOrString& publicKey, + const BufferOrString& tweak, + std::optional isCompressed) override; + + std::shared_ptr sign( + const BufferOrString& secretKey, + const BufferOrString& message) override; + + bool verify( + const BufferOrString& publicKey, + const BufferOrString& message, + const BufferOrString& signature) override; + + std::shared_ptr signSchnorr( + const BufferOrString& secretKey, + const BufferOrString& messageHash, + const BufferOrString& auxRand + ) override; + + bool verifySchnorr( + const BufferOrString& publicKey, + const BufferOrString& messageHash, + const BufferOrString& signature) override; + + +protected: + // If your nitrogen requires it, you can override loadHybridMethods(), + // but the base already wires methods based on the spec. + +private: + // context singleton + static secp256k1_context* ctx(); + + // helpers + static std::vector hexToBytes(const std::string& hex); + static std::vector bytesFromVariant(const BufferOrString& v); + static std::array require32(const BufferOrString& v, const char* what); + static std::vector serializePubkey(const secp256k1_pubkey& pk, bool compressed); + static secp256k1_pubkey parsePubkey(const std::vector& in); + static std::shared_ptr toAB(const std::vector& v); +}; + +} // namespace margelo::nitro::nitroavalabscrypto diff --git a/packages/react-native-nitro-avalabs-crypto/ios/scripts/build.sh b/packages/react-native-nitro-avalabs-crypto/ios/scripts/build.sh new file mode 100644 index 0000000000..ac7b3cd345 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/ios/scripts/build.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ------------------- Configuration ------------------- +SECP_REPO_URL=${SECP_REPO_URL:-"https://github.com/bitcoin-core/secp256k1.git"} +SECP_TAG=${SECP_TAG:-"v0.7.0"} +MODULE_FLAGS=${SECP_MODULES:-"--enable-module-extrakeys --enable-module-schnorrsig"} + +ROOT_DIR="${PODS_TARGET_SRCROOT:-${SRCROOT:-$(pwd)}}" +IOS_DIR="${ROOT_DIR}/ios" +WORK_DIR="${IOS_DIR}/secp256k1-src" +BUILD_DIR="${IOS_DIR}/secp-build" +OUT_DIR="${IOS_DIR}/secp-out" +XC_OUT="${OUT_DIR}/secp256k1.xcframework" + +# Detect SDK (Xcode env) +if [[ "${SDK_NAME:-}" == iphonesimulator* || "${EFFECTIVE_PLATFORM_NAME:-}" == "-iphonesimulator" ]]; then + BUILD_TARGET="iphonesimulator" +else + BUILD_TARGET="iphoneos" +fi +STAMP_FILE="${OUT_DIR}/.last_target" + +# ------------------- Homebrew tools ------------------- +BREW_PREFIX="$((/usr/bin/env brew --prefix) 2>/dev/null || true)" +if [[ -n "$BREW_PREFIX" && -d "$BREW_PREFIX" ]]; then + export PATH="$BREW_PREFIX/bin:$BREW_PREFIX/opt/automake/bin:$BREW_PREFIX/opt/autoconf/bin:$BREW_PREFIX/opt/libtool/bin:$PATH" +fi +for p in /opt/homebrew/bin /usr/local/bin; do + [[ -d "$p" ]] && case ":$PATH:" in *":$p:"*) ;; *) export PATH="$p:$PATH";; esac +done + +if ! command -v autoreconf >/dev/null 2>&1; then + echo "[secp] ERROR: 'autoreconf' not found. Install: brew install autoconf automake libtool" + exit 1 +fi + +# ------------------- Fast-path cache ------------------- +if [[ -d "${XC_OUT}" ]] && [[ -f "${STAMP_FILE}" ]] && grep -q "^${BUILD_TARGET}$" "${STAMP_FILE}"; then + echo "[secp] Using cached ${XC_OUT} for ${BUILD_TARGET}" + exit 0 +fi +if [[ -d "${XC_OUT}" ]] && [[ -f "${STAMP_FILE}" ]] && ! grep -q "^${BUILD_TARGET}$" "${STAMP_FILE}"; then + echo "[secp] Target changed – rebuilding" + rm -rf "${XC_OUT}" +fi + +mkdir -p "${WORK_DIR}" "${BUILD_DIR}" "${OUT_DIR}" + +# ------------------- Clone / update ------------------- +if [[ ! -d "${WORK_DIR}/.git" ]]; then + echo "[secp] Cloning ${SECP_REPO_URL}@${SECP_TAG}..." + git clone --depth 1 --branch "${SECP_TAG}" "${SECP_REPO_URL}" "${WORK_DIR}" +else + echo "[secp] Updating repo..." + git -C "${WORK_DIR}" fetch --tags + git -C "${WORK_DIR}" checkout "${SECP_TAG}" + git -C "${WORK_DIR}" reset --hard "${SECP_TAG}" +fi + +export LIBTOOLIZE="${LIBTOOLIZE:-glibtoolize}" +export LIBTOOL="${LIBTOOL:-glibtool}" + +pushd "${WORK_DIR}" >/dev/null +echo "[secp] Running autogen.sh..." +bash ./autogen.sh +popd >/dev/null + +# ------------------- Build helper ------------------- +build_one() { + local sdk="$1" # iphoneos / iphonesimulator + local arch="$2" # arm64 / x86_64 + local host="$3" # aarch64-apple-darwin / x86_64-apple-darwin + + # ---- Clean previous build ---- + echo "[secp] Cleaning old objects for ${sdk}/${arch}..." + (cd "${WORK_DIR}" && make distclean || true) + find "${WORK_DIR}" -name "*.o" -o -name "*.lo" -o -name "*.la" -o -name "*.a" -delete + rm -rf "${WORK_DIR}/.libs" + + # ---- SDK / compiler ---- + local SDKROOT=$(xcrun --sdk "${sdk}" --show-sdk-path) + local CC=$(xcrun --sdk "${sdk}" -f clang) + local CXX=$(xcrun --sdk "${sdk}" -f clang++) + + # ---- Flags (identical for all slices) ---- + local ARCH_FLAG="-arch ${arch}" + local MIN_VERSION + if [[ "${sdk}" == "iphonesimulator" ]]; then + MIN_VERSION="-mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-12.0}" + local TARGET_FLAG="-target ${arch}-apple-ios${IPHONEOS_DEPLOYMENT_TARGET:-12.0}-simulator" + else + MIN_VERSION="-miphoneos-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-12.0}" + local TARGET_FLAG="" + fi + + local CFLAGS="-O2 -fPIC -fembed-bitcode ${ARCH_FLAG} ${TARGET_FLAG} -isysroot ${SDKROOT} ${MIN_VERSION}" + local LDFLAGS="${ARCH_FLAG} ${TARGET_FLAG} -isysroot ${SDKROOT} ${MIN_VERSION}" + + local PREFIX="${BUILD_DIR}/${sdk}-${arch}" + mkdir -p "${PREFIX}" + + echo "[secp] Configuring ${sdk}/${arch}..." + ( + cd "${WORK_DIR}" + env \ + CC="${CC}" CXX="${CXX}" \ + CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" \ + LIBTOOLIZE="${LIBTOOLIZE}" LIBTOOL="${LIBTOOL}" \ + ./configure \ + --host="${host}" \ + --enable-shared=no --enable-static \ + --with-pic \ + --disable-benchmark --disable-tests \ + --prefix="${PREFIX}" \ + ${MODULE_FLAGS} + make -j$(sysctl -n hw.ncpu) + make install + ) + + # ---- Verify slice ---- + local OUT_LIB="${PREFIX}/lib/libsecp256k1.a" + if [[ -f "${OUT_LIB}" ]]; then + local INFO=$(lipo -info "${OUT_LIB}") + echo "[secp] Built ${sdk}/${arch}: ${INFO}" + fi +} + +# ------------------- Build slices ------------------- +build_one iphoneos arm64 aarch64-apple-darwin + +HOST_ARCH="$(uname -m)" +if [[ "$HOST_ARCH" == "arm64" ]]; then + build_one iphonesimulator arm64 aarch64-apple-darwin +elif [[ "$HOST_ARCH" == "x86_64" ]]; then + build_one iphonesimulator x86_64 x86_64-apple-darwin +else + echo "[secp] WARNING: unknown host arch $HOST_ARCH – no simulator slice" +fi + +# ------------------- Copy headers (once) ------------------- +rm -rf "${OUT_DIR}/include" +for inc in \ + "${BUILD_DIR}/iphoneos-arm64/include" \ + "${BUILD_DIR}/iphonesimulator-arm64/include" \ + "${BUILD_DIR}/iphonesimulator-x86_64/include"; do + [[ -d "$inc" ]] && cp -R "$inc" "${OUT_DIR}/include" && break +done + +# ------------------- Create XCFramework ------------------- +echo "[secp] Creating XCFramework..." +rm -rf "${XC_OUT}" + +DEV_LIB="${BUILD_DIR}/iphoneos-arm64/lib/libsecp256k1.a" +SIM_ARM64_LIB="${BUILD_DIR}/iphonesimulator-arm64/lib/libsecp256k1.a" +SIM_X64_LIB="${BUILD_DIR}/iphonesimulator-x86_64/lib/libsecp256k1.a" + +XC_ARGS=() +[[ -f "${DEV_LIB}" ]] && XC_ARGS+=( -library "${DEV_LIB}" -headers "${OUT_DIR}/include" ) +[[ -f "${SIM_ARM64_LIB}" ]] && XC_ARGS+=( -library "${SIM_ARM64_LIB}" -headers "${OUT_DIR}/include" ) +[[ -f "${SIM_X64_LIB}" ]] && XC_ARGS+=( -library "${SIM_X64_LIB}" -headers "${OUT_DIR}/include" ) + +if [[ ${#XC_ARGS[@]} -eq 0 ]]; then + echo "[secp] No libraries to package!" + exit 1 +fi + +xcodebuild -create-xcframework \ + "${XC_ARGS[@]}" \ + -output "${XC_OUT}" || { + echo "[secp] Failed to create XCFramework" + exit 1 + } + +echo "[secp] XCFramework created: ${XC_OUT}" +echo "${BUILD_TARGET}" > "${STAMP_FILE}" \ No newline at end of file diff --git a/packages/react-native-nitro-avalabs-crypto/nitro.json b/packages/react-native-nitro-avalabs-crypto/nitro.json new file mode 100644 index 0000000000..8b578db909 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/nitro.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://nitro.margelo.com/nitro.schema.json", + "cxxNamespace": [ + "nitroavalabscrypto" + ], + "ios": { + "iosModuleName": "NitroAvalabsCrypto" + }, + "android": { + "androidNamespace": [ + "nitroavalabscrypto" + ], + "androidCxxLibName": "NitroAvalabsCrypto" + }, + "autolinking": { + "Crypto": { + "cpp": "CryptoHybrid" + } + }, + "ignorePaths": [ + "**/node_modules" + ] +} diff --git a/packages/react-native-nitro-avalabs-crypto/package.json b/packages/react-native-nitro-avalabs-crypto/package.json new file mode 100644 index 0000000000..10ebfd7cd5 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/package.json @@ -0,0 +1,74 @@ +{ + "name": "react-native-nitro-avalabs-crypto", + "version": "0.0.1", + "description": "react-native-nitro-avalabs-crypto", + "main": "lib/index", + "module": "lib/index", + "types": "lib/index.d.ts", + "react-native": "src/index", + "source": "src/index", + "files": [ + "src", + "react-native.config.js", + "lib", + "nitrogen", + "android/build.gradle", + "android/gradle.properties", + "android/fix-prefab.gradle", + "android/CMakeLists.txt", + "android/src", + "ios/**/*.h", + "ios/**/*.m", + "ios/**/*.mm", + "ios/**/*.cpp", + "ios/**/*.swift", + "app.plugin.js", + "nitro.json", + "*.podspec", + "README.md" + ], + "scripts": { + "postinstall": "tsc || exit 0;", + "typecheck": "tsc --noEmit", + "clean": "rm -rf android/build node_modules/**/android/build lib", + "lint": "eslint .", + "tsc": "tsc -p .", + "specs": "tsc --noEmit false && nitrogen --logLevel=\"debug\"", + "setup": "npx nitrogen" + }, + "keywords": [ + "react-native", + "nitro" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/mrousavy/nitro.git" + }, + "author": "Marc Rousavy (https://github.com/mrousavy)", + "license": "MIT", + "bugs": { + "url": "https://github.com/mrousavy/nitro/issues" + }, + "homepage": "https://github.com/mrousavy/nitro#readme", + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "devDependencies": { + "@react-native/babel-preset": "0.79.5", + "@react-native/eslint-config": "0.82.0", + "@rushstack/eslint-patch": "1.10.4", + "@types/react": "19.0.10", + "eslint": "8.50.0", + "eslint-plugin-avalabs-mobile": "workspace:*", + "nitrogen": "*", + "react": "19.0.0", + "react-native": "0.79.5", + "react-native-nitro-modules": "0.31.10", + "typescript": "^5.8.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-nitro-modules": "*" + } +} diff --git a/packages/react-native-nitro-avalabs-crypto/react-native.config.js b/packages/react-native-nitro-avalabs-crypto/react-native.config.js new file mode 100644 index 0000000000..3fdf8eaadc --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/react-native.config.js @@ -0,0 +1,16 @@ +// https://github.com/react-native-community/cli/blob/main/docs/dependencies.md + +module.exports = { + dependency: { + platforms: { + /** + * @type {import('@react-native-community/cli-types').IOSDependencyParams} + */ + ios: {}, + /** + * @type {import('@react-native-community/cli-types').AndroidDependencyParams} + */ + android: {}, + }, + }, +} diff --git a/packages/react-native-nitro-avalabs-crypto/src/Crypto.ts b/packages/react-native-nitro-avalabs-crypto/src/Crypto.ts new file mode 100644 index 0000000000..0c6bdb0580 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/src/Crypto.ts @@ -0,0 +1,367 @@ +/* eslint-disable no-bitwise */ +import { NitroModules } from 'react-native-nitro-modules' +import type { Crypto } from './specs/Crypto.nitro' + +const con = console + +// Native hybrid object +const NativeCrypto = NitroModules.createHybridObject('Crypto') + +/** Strip 0x, validate, and normalize hex to even length (left-pad with one '0' if needed). */ +function normalizeHex(hex: string): string { + let h = hex.startsWith('0x') || hex.startsWith('0X') ? hex.slice(2) : hex + if (!/^[0-9a-fA-F]*$/.test(h)) throw new TypeError('Invalid hex string') + if (h.length % 2) h = '0' + h + return h.toLowerCase() +} + +/** Convert hex string to a fresh ArrayBuffer. */ +function hexToArrayBuffer(hex: string): ArrayBuffer { + const h = normalizeHex(hex) + const len = h.length / 2 + const out = new Uint8Array(len) + for (let i = 0; i < len; i++) { + out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16) + } + return out.buffer +} + +/** Convert a Uint8Array or ArrayBuffer to a tight ArrayBuffer view. */ +function toArrayBuffer(input: Uint8Array | ArrayBuffer): ArrayBuffer { + if (input instanceof ArrayBuffer) return input + // Make a tight copy that respects byteOffset/byteLength + return input.buffer.slice( + input.byteOffset, + input.byteOffset + input.byteLength + ) as ArrayBuffer +} + +/** Accepts string (hex), ArrayBuffer, or Uint8Array and returns ArrayBuffer */ +function hexLikeToArrayBuffer( + input: string | ArrayBuffer | Uint8Array +): ArrayBuffer { + if (typeof input === 'string') return hexToArrayBuffer(input) + if (input instanceof ArrayBuffer) return input + if (input instanceof Uint8Array) return toArrayBuffer(input) + throw new TypeError('Expected hex string, ArrayBuffer, or Uint8Array') +} + +/** Ensure a 32-byte buffer */ +function ensure32(name: string, ab: ArrayBuffer): ArrayBuffer { + if (ab.byteLength !== 32) throw new TypeError(`${name} must be 32 bytes`) + return ab +} + +/** bigint → 32-byte ArrayBuffer (left-padded). Throws if it doesn't fit. */ +function bigintToArrayBuffer32(n: bigint): ArrayBuffer { + if (n < 0n) throw new TypeError('Secret key must be non-negative') + const out = new Uint8Array(32) + let i = 31 + let v = n + while (v > 0n) { + if (i < 0) throw new RangeError('bigint does not fit into 32 bytes') + out[i] = Number(v & 0xffn) + v >>= 8n + i-- + } + return out.buffer +} + +/** bigint → 64-char hex string (left-padded). Throws if it doesn't fit. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function bigintToHex64(n: bigint): string { + if (n < 0n) throw new TypeError('Secret key must be non-negative') + const hex = n.toString(16) + if (hex.length > 64) throw new RangeError('bigint does not fit into 32 bytes') + return hex.padStart(64, '0').toLowerCase() +} + +/** + * Public JS API — accepts Uint8Array | ArrayBuffer | string | bigint. + * Returns Uint8Array for ergonomic use in JS. + */ +// eslint-disable-next-line sonarjs/cognitive-complexity +export function getPublicKey( + secretKey: Uint8Array | ArrayBuffer | string | bigint, + isCompressed = true +): Uint8Array { + con.log('[Crypto] getPublicKey called with', typeof secretKey, isCompressed) + let ab: ArrayBuffer | undefined + let hex: string | undefined + + if (typeof secretKey === 'bigint') { + con.log('[Crypto] Secret is bigint') + // Prefer zero-copy native path via ArrayBuffer + ab = bigintToArrayBuffer32(secretKey) + // hex fallback available if needed: hex = bigintToHex64(secretKey); + } else if (typeof secretKey === 'string') { + con.log('[Crypto] Secret is string', secretKey.slice(0, 10) + '...') + hex = normalizeHex(secretKey) + } else { + con.log( + '[Crypto] Secret is buffer-like', + secretKey instanceof Uint8Array, + secretKey instanceof ArrayBuffer + ) + ab = toArrayBuffer(secretKey) + } + + let outBuf: ArrayBuffer + if (ab && typeof NativeCrypto.getPublicKeyFromArrayBuffer === 'function') { + con.log('[Crypto] Calling NativeCrypto.getPublicKeyFromArrayBuffer') + outBuf = NativeCrypto.getPublicKeyFromArrayBuffer(ab, isCompressed) + } else if (hex && typeof NativeCrypto.getPublicKeyFromString === 'function') { + con.log('[Crypto] Calling NativeCrypto.getPublicKeyFromString') + // Native returns ArrayBuffer already — no conversion needed + outBuf = NativeCrypto.getPublicKeyFromString(hex, isCompressed) + } else if ( + hex && + typeof NativeCrypto.getPublicKeyFromArrayBuffer === 'function' + ) { + con.log('[Crypto] Calling NativeCrypto.getPublicKeyFromArrayBuffer') + outBuf = NativeCrypto.getPublicKeyFromArrayBuffer( + hexToArrayBuffer(hex), + isCompressed + ) + } else if (ab && typeof NativeCrypto.getPublicKeyFromString === 'function') { + con.log('[Crypto] Calling NativeCrypto.getPublicKeyFromString') + // fallback: convert buffer to hex and use string method; native returns ArrayBuffer + const bytes = new Uint8Array(ab) + let h = '' + for (let i = 0; i < bytes.length; i++) { + const byte = bytes[i] + if (byte !== undefined) { + h += byte.toString(16).padStart(2, '0') + } + } + outBuf = NativeCrypto.getPublicKeyFromString(h, isCompressed) + } else { + throw new Error( + 'Native Crypto hybrid does not expose the expected methods.' + ) + } + + con.log( + '[Crypto] Native call succeeded, result byteLength:', + outBuf.byteLength + ) + con.log('[Crypto] Returning Uint8Array with length', outBuf.byteLength) + try { + const res = new Uint8Array(outBuf) + con.log('[Crypto] getPublicKey completed') + return res + } catch (error) { + con.error('[Crypto] getPublicKey failed', error) + throw error + } +} + +// Optionally re-export the native methods (typed) if you want direct access: +export const getPublicKeyFromArrayBuffer = + NativeCrypto.getPublicKeyFromArrayBuffer.bind(NativeCrypto) +export const getPublicKeyFromString = + NativeCrypto.getPublicKeyFromString.bind(NativeCrypto) + +/** Add scalar*G to existing public key (P + t*G). Returns Uint8Array. */ +export function pointAddScalar( + publicKey: string | ArrayBuffer | Uint8Array, + tweak: string | ArrayBuffer | Uint8Array, + isCompressed = true +): Uint8Array { + con.log('[Crypto] pointAddScalar called') + const pkAB = hexLikeToArrayBuffer(publicKey as unknown as string) + const twAB = hexLikeToArrayBuffer(tweak as unknown as string) + const out = NativeCrypto.pointAddScalar(pkAB, twAB, isCompressed) + return new Uint8Array(out) +} + +/** ECDSA sign. Message must be a 32-byte digest. Returns DER signature bytes. */ +function isDerSignature(u8: Uint8Array): boolean { + return u8.length >= 8 && u8[0] === 0x30 +} + +/** Convert 32-byte big-endian integer to minimal DER INTEGER (with zero prefix if high bit set). */ +function be32ToDerInt(src: Uint8Array): Uint8Array { + // Strip leading zeros + let i = 0 + while (i < src.length - 1 && src[i] === 0) i++ + const v = src.subarray(i) + // If high bit set, prepend 0x00 to force positive + if ((v[0] ?? 0) & 0x80) { + const out = new Uint8Array(v.length + 1) + out[0] = 0x00 + out.set(v, 1) + return out + } + return v +} + +/** Convert a 64-byte compact ECDSA signature (r||s) to DER sequence. */ +function compact64ToDer(sig64: Uint8Array): Uint8Array { + if (sig64.length !== 64) + throw new TypeError('compact ECDSA signature must be 64 bytes') + const r = sig64.subarray(0, 32) + const s = sig64.subarray(32, 64) + const rDer = be32ToDerInt(r) + const sDer = be32ToDerInt(s) + const len = 2 + rDer.length + 2 + sDer.length // 0x02 rLen r 0x02 sLen s + const out = new Uint8Array(2 + 2 + rDer.length + 2 + sDer.length) + let p = 0 + out[p++] = 0x30 // SEQUENCE + out[p++] = len + out[p++] = 0x02 // INTEGER + out[p++] = rDer.length + out.set(rDer, p) + p += rDer.length + out[p++] = 0x02 // INTEGER + out[p++] = sDer.length + out.set(sDer, p) + return out +} + +export function sign( + secretKey: string | ArrayBuffer | Uint8Array, + message: string | ArrayBuffer | Uint8Array +): Uint8Array { + con.log('[Crypto] sign called') + const skAB = hexLikeToArrayBuffer(secretKey) + const msgAB = hexLikeToArrayBuffer(message) + if (msgAB.byteLength !== 32) + throw new TypeError('ECDSA message must be 32 bytes') + const out = NativeCrypto.sign(skAB, msgAB) + let sig = new Uint8Array(out) + // Normalize to compact(64) – many JS libs expect r||s + if (isDerSignature(sig)) { + con.log('[Crypto] sign: converting DER signature to compact(64)') + sig = derToCompact64(sig) + } else if (sig.length !== 64) { + throw new TypeError(`ECDSA signature has unexpected length: ${sig.length}`) + } + return sig +} + +/** Parse minimal ASN.1 DER ECDSA signature and return {r,s} raw big-endian bytes (unpadded). */ +// eslint-disable-next-line sonarjs/cognitive-complexity +function parseDerEcdsa(sig: Uint8Array): { r: Uint8Array; s: Uint8Array } { + if (sig.length < 8 || sig[0] !== 0x30) + throw new TypeError('Invalid DER: no SEQ') + let p = 1 + let len = sig[p++] ?? 0 + if (len & 0x80) { + const n = len & 0x7f + if (n === 0 || n > 2) throw new TypeError('Invalid DER: long len too big') + if (p + n > sig.length) throw new TypeError('Invalid DER: length overflow') + len = 0 + for (let i = 0; i < n; i++) len = (len << 8) | (sig[p++] ?? 0) + } + if (p + len !== sig.length) throw new TypeError('Invalid DER: len mismatch') + if (sig[p++] !== 0x02) throw new TypeError('Invalid DER: missing r INTEGER') + const rLen = sig[p++] ?? 0 + if (p + rLen > sig.length) throw new TypeError('Invalid DER: r overflow') + let r = sig.subarray(p, p + rLen) + p += rLen + if (sig[p++] !== 0x02) throw new TypeError('Invalid DER: missing s INTEGER') + const sLen = sig[p++] ?? 0 + if (p + sLen > sig.length) throw new TypeError('Invalid DER: s overflow') + let s = sig.subarray(p, p + sLen) + // Strip an optional leading 0x00 that enforces positive INTEGER + if (r.length > 0 && r[0] === 0x00) r = r.subarray(1) + if (s.length > 0 && s[0] === 0x00) s = s.subarray(1) + return { r, s } +} + +/** Convert DER ECDSA signature to compact 64-byte (r||s). */ +function derToCompact64(sigDer: Uint8Array): Uint8Array { + const { r, s } = parseDerEcdsa(sigDer) + if (r.length > 32 || s.length > 32) + throw new TypeError('Invalid DER: r/s too long') + const out = new Uint8Array(64) + out.set(r, 32 - r.length) + out.set(s, 64 - s.length) + return out +} + +/** ECDSA verify. Message must be a 32-byte digest. Accepts DER or compact(64B) signature. */ +/** Determine if signature is ASN.1 DER (starts with 0x30). */ + +export function verify( + publicKey: string | ArrayBuffer | Uint8Array, + message: string | ArrayBuffer | Uint8Array, + signature: string | ArrayBuffer | Uint8Array +): boolean { + con.log('[Crypto] verify called') + const pkAB = hexLikeToArrayBuffer(publicKey) + const msgAB = hexLikeToArrayBuffer(message) + const sigAB0 = hexLikeToArrayBuffer(signature) + if (msgAB.byteLength !== 32) + throw new TypeError('ECDSA message must be 32 bytes') + + // Normalize signature to DER if it is compact-64 + let sigU8 = new Uint8Array(sigAB0) + if (!isDerSignature(sigU8)) { + if (sigU8.length === 64) { + con.log('[Crypto] verify: converting compact(64) signature to DER') + sigU8 = compact64ToDer(sigU8) as Uint8Array + } else { + con.log( + '[Crypto] verify: non-DER signature with unexpected length', + sigU8.length + ) + } + } + return NativeCrypto.verify(pkAB, msgAB, sigU8.buffer) +} + +/** Schnorr sign (BIP-340). messageHash must be 32 bytes. Returns 64-byte signature. */ +/** Schnorr sign (BIP-340). messageHash must be 32 bytes. Returns 64-byte signature. */ +export function signSchnorr( + messageHash: string | ArrayBuffer | Uint8Array, + secretKey: string | ArrayBuffer | Uint8Array, + auxRand?: string | ArrayBuffer | Uint8Array +): Uint8Array { + con.log('[Crypto] signSchnorr called (msg, sk, aux). args=', { + msgType: typeof messageHash, + skType: typeof secretKey, + hasAux: auxRand !== undefined, + }) + + const msgAB = ensure32( + 'Schnorr messageHash', + hexLikeToArrayBuffer(messageHash) + ) + const skAB = ensure32('Schnorr secretKey', hexLikeToArrayBuffer(secretKey)) + + let auxAB: ArrayBuffer + if (auxRand === undefined) { + con.log('[Crypto] signSchnorr: using zero-filled auxRand') + auxAB = new Uint8Array(32).buffer + } else { + auxAB = ensure32('Schnorr auxRand', hexLikeToArrayBuffer(auxRand as any)) + con.log( + '[Crypto] signSchnorr: auxRand provided, byteLength=', + (auxAB as ArrayBuffer).byteLength + ) + } + + // Native expects (secretKey, messageHash, auxRand) + const out = NativeCrypto.signSchnorr(skAB, msgAB, auxAB) + const sig = new Uint8Array(out) + con.log('[Crypto] signSchnorr: native returned', sig.length, 'bytes') + return sig +} +/** Schnorr verify (BIP-340). messageHash must be 32 bytes. Signature must be 64 bytes. */ +export function verifySchnorr( + publicKey: string | ArrayBuffer | Uint8Array, + messageHash: string | ArrayBuffer | Uint8Array, + signature: string | ArrayBuffer | Uint8Array +): boolean { + con.log('[Crypto] verifySchnorr called') + const pkAB = hexLikeToArrayBuffer(publicKey) + const msgAB = hexLikeToArrayBuffer(messageHash) + const sigAB = hexLikeToArrayBuffer(signature) + if (msgAB.byteLength !== 32) + throw new TypeError('Schnorr messageHash must be 32 bytes') + if (sigAB.byteLength !== 64) + throw new TypeError('Schnorr signature must be 64 bytes') + return NativeCrypto.verifySchnorr(pkAB, msgAB, sigAB) +} diff --git a/packages/react-native-nitro-avalabs-crypto/src/index.ts b/packages/react-native-nitro-avalabs-crypto/src/index.ts new file mode 100644 index 0000000000..c79e5821d3 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/src/index.ts @@ -0,0 +1 @@ +export * from './Crypto' diff --git a/packages/react-native-nitro-avalabs-crypto/src/specs/Crypto.nitro.ts b/packages/react-native-nitro-avalabs-crypto/src/specs/Crypto.nitro.ts new file mode 100644 index 0000000000..eb4465b75c --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/src/specs/Crypto.nitro.ts @@ -0,0 +1,69 @@ +import type { HybridObject } from 'react-native-nitro-modules' + +// Use ArrayBuffer in specs (Nitro’s zero-copy binary type) +export type HexLike = string | ArrayBuffer + +export interface Crypto extends HybridObject<{ ios: 'c++'; android: 'c++' }> { + // existing methods + getPublicKeyFromString(secretKey: string, isCompressed?: boolean): ArrayBuffer + getPublicKeyFromArrayBuffer( + secretKey: ArrayBuffer, + isCompressed?: boolean + ): ArrayBuffer + + // NEW additions: + /** + * Adds scalar*G to an existing public key P. + * @param publicKey Hex string or ArrayBuffer representing P + * @param tweak Hex string or ArrayBuffer representing scalar + * @param isCompressed optional boolean + * @returns ArrayBuffer for resulting public key + */ + pointAddScalar( + publicKey: HexLike, + tweak: HexLike, + isCompressed?: boolean + ): ArrayBuffer + + /** + * Generic sign (e.g., ECDSA) using secret key. + * @param secretKey Hex string or ArrayBuffer + * @param message Hex string or ArrayBuffer + * @returns ArrayBuffer representing signature + */ + sign(secretKey: HexLike, message: HexLike): ArrayBuffer + + /** + * Generic verify (e.g., ECDSA) using public key. + * @param publicKey Hex string or ArrayBuffer + * @param message Hex string or ArrayBuffer + * @param signature Hex string or ArrayBuffer + * @returns boolean + */ + verify(publicKey: HexLike, message: HexLike, signature: HexLike): boolean + + /** + * Schnorr sign using secret key. + * @param secretKey Hex string or ArrayBuffer + * @param message Hash Hex string or ArrayBuffer (32 bytes) + * @returns ArrayBuffer for Schnorr signature + */ + signSchnorr( + secretKey: HexLike, + messageHash: HexLike, + auxRand: HexLike + ): ArrayBuffer + + /** + * Schnorr verify using public key. + * @param publicKey Hex string or ArrayBuffer + * @param messageHash Hex string or ArrayBuffer (32 bytes) + * @param signature Hex string or ArrayBuffer + * @returns boolean + */ + verifySchnorr( + publicKey: HexLike, + messageHash: HexLike, + signature: HexLike + ): boolean +} diff --git a/packages/react-native-nitro-avalabs-crypto/tsconfig.json b/packages/react-native-nitro-avalabs-crypto/tsconfig.json new file mode 100644 index 0000000000..081ed2e1d0 --- /dev/null +++ b/packages/react-native-nitro-avalabs-crypto/tsconfig.json @@ -0,0 +1,44 @@ +{ + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "*.d.ts", + ".eslintrc.js", + ], + "exclude": [ + "example", + "node_modules", + "dist" + ], + "compilerOptions": { + "composite": true, + "outDir": "lib", + "rootDir": ".", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-native", + "lib": [ + "esnext", + "DOM" + ], + "module": "commonjs", + "moduleResolution": "node", + "noEmit": false, + "noFallthroughCasesInSwitch": true, + "ignoreDeprecations": "6.0", + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + "declaration": true + } +} diff --git a/yarn.lock b/yarn.lock index d3b6c96a80..996f797ff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -594,6 +594,8 @@ __metadata: react-native-localize: 3.2.1 react-native-mmkv: 3.2.0 react-native-modal-datetime-picker: 18.0.0 + react-native-nitro-avalabs-crypto: "workspace:*" + react-native-nitro-modules: 0.31.10 react-native-os: 1.2.6 react-native-pager-view: 6.7.1 react-native-passkey: 3.1.0 @@ -4479,13 +4481,12 @@ __metadata: languageName: node linkType: hard -"@bitcoinerlab/secp256k1@npm:^1.0.5": - version: 1.0.5 - resolution: "@bitcoinerlab/secp256k1@npm:1.0.5" +"@bitcoinerlab/secp256k1@patch:@bitcoinerlab/secp256k1@npm%3A1.2.0#./.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch::locator=mobile-monorepo%40workspace%3A.": + version: 1.2.0 + resolution: "@bitcoinerlab/secp256k1@patch:@bitcoinerlab/secp256k1@npm%3A1.2.0#./.yarn/patches/@bitcoinerlab-secp256k1-npm-1.2.0-1098d4b329.patch::version=1.2.0&hash=058e44&locator=mobile-monorepo%40workspace%3A." dependencies: - "@noble/hashes": ^1.1.5 - "@noble/secp256k1": ^1.7.1 - checksum: 410a60394f2877941815a8b2ac6b70215ab2278ee106dea67673224c55cee87c8fc7136fc7a0d8ce73181b7953a811738d4c9480bdc2216c9cd75e2e07407723 + "@noble/curves": ^1.7.0 + checksum: 20781cf9e1039bda8685ae0c0b7486141b070afec9844428236a006db84889525ebb406b5aa3ffe8ff4463e065ed0091f9abf2525f3fa5f609bcc7bb6b52e0d5 languageName: node linkType: hard @@ -5040,6 +5041,17 @@ __metadata: languageName: node linkType: hard +"@eslint-community/eslint-utils@npm:^4.7.0": + version: 4.9.0 + resolution: "@eslint-community/eslint-utils@npm:4.9.0" + dependencies: + eslint-visitor-keys: ^3.4.3 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: ae9b98eea006d1354368804b0116b8b45017a4e47b486d1b9cfa048a8ed3dc69b9b074eb2b2acb14034e6897c24048fd42b6a6816d9dc8bb9daad79db7d478d2 + languageName: node + linkType: hard + "@eslint-community/regexpp@npm:^4.10.0": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" @@ -9331,52 +9343,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": - version: 1.1.0 - resolution: "@noble/curves@npm:1.1.0" - dependencies: - "@noble/hashes": 1.3.1 - checksum: 2658cdd3f84f71079b4e3516c47559d22cf4b55c23ac8ee9d2b1f8e5b72916d9689e59820e0f9d9cb4a46a8423af5b56dc6bb7782405c88be06a015180508db5 - languageName: node - linkType: hard - -"@noble/curves@npm:1.2.0, @noble/curves@npm:~1.2.0": - version: 1.2.0 - resolution: "@noble/curves@npm:1.2.0" - dependencies: - "@noble/hashes": 1.3.2 - checksum: bb798d7a66d8e43789e93bc3c2ddff91a1e19fdb79a99b86cd98f1e5eff0ee2024a2672902c2576ef3577b6f282f3b5c778bebd55761ddbb30e36bf275e83dd0 - languageName: node - linkType: hard - -"@noble/curves@npm:1.3.0, @noble/curves@npm:~1.3.0": - version: 1.3.0 - resolution: "@noble/curves@npm:1.3.0" - dependencies: - "@noble/hashes": 1.3.3 - checksum: b65342ee66c4a440eee2978524412eabba9a9efdd16d6370e15218c6a7d80bddf35e66bb57ed52c0dfd32cb9a717b439ab3a72db618f1a0066dfebe3fd12a421 - languageName: node - linkType: hard - -"@noble/curves@npm:1.4.2, @noble/curves@npm:~1.4.0": - version: 1.4.2 - resolution: "@noble/curves@npm:1.4.2" - dependencies: - "@noble/hashes": 1.4.0 - checksum: c475a83c4263e2c970eaba728895b9b5d67e0ca880651e9c6e3efdc5f6a4f07ceb5b043bf71c399fc80fada0b8706e69d0772bffdd7b9de2483b988973a34cba - languageName: node - linkType: hard - -"@noble/curves@npm:1.6.0, @noble/curves@npm:~1.6.0": - version: 1.6.0 - resolution: "@noble/curves@npm:1.6.0" - dependencies: - "@noble/hashes": 1.5.0 - checksum: 258f3feb2a6098cf35521562ecb7d452fd728e8a008ff9f1ef435184f9d0c782ceb8f7b7fa8df3317c3be7a19f53995ee124cd05c8080b130bd42e3cb072f24d - languageName: node - linkType: hard - -"@noble/curves@npm:1.9.7, @noble/curves@npm:^1.7.0": +"@noble/curves@npm:1.9.7": version: 1.9.7 resolution: "@noble/curves@npm:1.9.7" dependencies: @@ -9385,30 +9352,12 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:^1.4.0": - version: 1.5.0 - resolution: "@noble/curves@npm:1.5.0" - dependencies: - "@noble/hashes": 1.4.0 - checksum: a43464c5db67a931b1c93d6634c98e30d791dd567408ebeffd582be1a7f31169f6f26b191e24a9552d89d935408bd8c3dfb90ad8b47286ecf53cbdd2d79d02af - languageName: node - linkType: hard - -"@noble/curves@npm:^1.6.0": - version: 1.8.0 - resolution: "@noble/curves@npm:1.8.0" - dependencies: - "@noble/hashes": 1.7.0 - checksum: 88198bc5b8049358dfcc6c5e121125744fb81c703299127800f38f868a41697bc26bef8f88dc38f1939f4e0133b8db5f24337164eca7421a6a9480ee711f5e1b - languageName: node - linkType: hard - -"@noble/curves@npm:^1.8.0": - version: 1.8.1 - resolution: "@noble/curves@npm:1.8.1" +"@noble/curves@patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch::locator=mobile-monorepo%40workspace%3A.": + version: 1.9.7 + resolution: "@noble/curves@patch:@noble/curves@npm%3A1.9.7#./.yarn/patches/@noble-curves-npm-1.9.7-2b9efc8ab4.patch::version=1.9.7&hash=9b53f1&locator=mobile-monorepo%40workspace%3A." dependencies: - "@noble/hashes": 1.7.1 - checksum: 4143f1248ed57c1ae46dfef5c692a91383e5830420b9c72d3ff1061aa9ebbf8999297da6d2aed8a9716fef8e6b1f5a45737feeab02abf55ca2a4f514bf9339ec + "@noble/hashes": 1.8.0 + checksum: f2934f924a64903713b98c4abb3efc0f97e71d4a2f69b50d30412bcee4be30cd2d2b7b0004d78321e43297de4e88ad7e73e6e574c8e40abcb5d7a5875fd6f25c languageName: node linkType: hard @@ -9426,7 +9375,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.1.5, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:^1.7.0": +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:^1.7.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: c94e98b941963676feaba62475b1ccfa8341e3f572adbb3b684ee38b658df44100187fa0ef4220da580b13f8d27e87d5492623c8a02ecc61f23fb9960c7918f5 @@ -11562,6 +11511,29 @@ __metadata: languageName: node linkType: hard +"@react-native/eslint-config@npm:0.82.0": + version: 0.82.0 + resolution: "@react-native/eslint-config@npm:0.82.0" + dependencies: + "@babel/core": ^7.25.2 + "@babel/eslint-parser": ^7.25.1 + "@react-native/eslint-plugin": 0.82.0 + "@typescript-eslint/eslint-plugin": ^8.36.0 + "@typescript-eslint/parser": ^8.36.0 + eslint-config-prettier: ^8.5.0 + eslint-plugin-eslint-comments: ^3.2.0 + eslint-plugin-ft-flow: ^2.0.1 + eslint-plugin-jest: ^29.0.1 + eslint-plugin-react: ^7.30.1 + eslint-plugin-react-hooks: ^5.2.0 + eslint-plugin-react-native: ^4.0.0 + peerDependencies: + eslint: ">=8" + prettier: ">=2" + checksum: 2728a97fa1f784e3ee154e3a00b3b08821cd8d2fef9354a58508076be98be43bd8f8084e95c6a8e23654e583990cb5bcedc70fb10608b63220168ea9176c0c90 + languageName: node + linkType: hard + "@react-native/eslint-plugin@npm:0.79.5": version: 0.79.5 resolution: "@react-native/eslint-plugin@npm:0.79.5" @@ -11569,6 +11541,13 @@ __metadata: languageName: node linkType: hard +"@react-native/eslint-plugin@npm:0.82.0": + version: 0.82.0 + resolution: "@react-native/eslint-plugin@npm:0.82.0" + checksum: b456cde2911a90745ab811a577535a4c8afe602ff4f706dce6812fd5c2d5de7d2e426e2fc240aac405675065fc622983db83c657f54ca33eeda6d6c6440d8cdc + languageName: node + linkType: hard + "@react-native/gradle-plugin@npm:0.79.5": version: 0.79.5 resolution: "@react-native/gradle-plugin@npm:0.79.5" @@ -13782,6 +13761,17 @@ __metadata: languageName: node linkType: hard +"@ts-morph/common@npm:~0.28.1": + version: 0.28.1 + resolution: "@ts-morph/common@npm:0.28.1" + dependencies: + minimatch: ^10.0.1 + path-browserify: ^1.0.1 + tinyglobby: ^0.2.14 + checksum: bc3e879ff55fe8fe460d49124d10f74aba4ec92c261b7f65d48153a107e1b733676bb89e1c55fa4e5c045fe055c6c5247f7d340aaf1db1a44ffaf32ca2a00ec5 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.9 resolution: "@tsconfig/node10@npm:1.0.9" @@ -14853,6 +14843,27 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/eslint-plugin@npm:^8.36.0": + version: 8.47.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.47.0" + dependencies: + "@eslint-community/regexpp": ^4.10.0 + "@typescript-eslint/scope-manager": 8.47.0 + "@typescript-eslint/type-utils": 8.47.0 + "@typescript-eslint/utils": 8.47.0 + "@typescript-eslint/visitor-keys": 8.47.0 + graphemer: ^1.4.0 + ignore: ^7.0.0 + natural-compare: ^1.4.0 + ts-api-utils: ^2.1.0 + peerDependencies: + "@typescript-eslint/parser": ^8.47.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 25dbfcce58db2cbebf427fdb46e57ff044f7f619688ad290c3910229779dfab26d663212e5b2afab313e8e5e4e2c747685b6707be6bfdef3a8bf954f8fab0eec + languageName: node + linkType: hard + "@typescript-eslint/experimental-utils@npm:^5.0.0": version: 5.62.0 resolution: "@typescript-eslint/experimental-utils@npm:5.62.0" @@ -14899,6 +14910,35 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/parser@npm:^8.36.0": + version: 8.47.0 + resolution: "@typescript-eslint/parser@npm:8.47.0" + dependencies: + "@typescript-eslint/scope-manager": 8.47.0 + "@typescript-eslint/types": 8.47.0 + "@typescript-eslint/typescript-estree": 8.47.0 + "@typescript-eslint/visitor-keys": 8.47.0 + debug: ^4.3.4 + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 4fa175b24364f2e894494074fbcdd525e02e39ab1f4d89a95407a822177a2a4e9e75108f747cbda158245c10d61a30bae11583ab4b515df9abbee2551ee468a4 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/project-service@npm:8.47.0" + dependencies: + "@typescript-eslint/tsconfig-utils": ^8.47.0 + "@typescript-eslint/types": ^8.47.0 + debug: ^4.3.4 + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 3e001f98da9df74b7720a75d8cd32d6cf9e15ef57541256f9ec83ae583777994bf267a36e332e05f1c00d7a8d794337e7264d8072e609327364891c10ef94437 + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/scope-manager@npm:5.62.0" @@ -14929,6 +14969,25 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/scope-manager@npm:8.47.0" + dependencies: + "@typescript-eslint/types": 8.47.0 + "@typescript-eslint/visitor-keys": 8.47.0 + checksum: 4df9033db772ee211ecf967dad3cf970167cf71e69061690412321d9677043bfb4fb58cbf9e83ffdb25ffbfa714accff7c92f1cdac87cfab376e1f5204b48492 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.47.0, @typescript-eslint/tsconfig-utils@npm:^8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.47.0" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: dd1e4868d3ea1f4d0f583310e388ab191c0845f43eca4529e7cc9088b6dc58529eee2be3fb1b0a11d4b50f63fa6d212ff921fa58c7aa5ac44129e2caed1998ab + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/type-utils@npm:5.62.0" @@ -14963,6 +15022,22 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/type-utils@npm:8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/type-utils@npm:8.47.0" + dependencies: + "@typescript-eslint/types": 8.47.0 + "@typescript-eslint/typescript-estree": 8.47.0 + "@typescript-eslint/utils": 8.47.0 + debug: ^4.3.4 + ts-api-utils: ^2.1.0 + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 975655a484750be4c6dabac9fc420916dfa9697a9de921e8f76dc17f3b73e270df6b3d9a9550847b7f94a80842756e4b058bf57ad4420f78fdff022d0e10f217 + languageName: node + linkType: hard + "@typescript-eslint/types@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/types@npm:5.62.0" @@ -14984,6 +15059,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.47.0, @typescript-eslint/types@npm:^8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/types@npm:8.47.0" + checksum: 2818d17d2cf383c94f6870a8d4db2be9ec8b41988d58c91fcc9b13af11ec08600a40223c09920cd2e82d1ef5596eb4d9565da1d1b66e0e5967e602b9fb06a335 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -15040,6 +15122,26 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.47.0" + dependencies: + "@typescript-eslint/project-service": 8.47.0 + "@typescript-eslint/tsconfig-utils": 8.47.0 + "@typescript-eslint/types": 8.47.0 + "@typescript-eslint/visitor-keys": 8.47.0 + debug: ^4.3.4 + fast-glob: ^3.3.2 + is-glob: ^4.0.3 + minimatch: ^9.0.4 + semver: ^7.6.0 + ts-api-utils: ^2.1.0 + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: c8d06cc086d4848325513930c759eacb958bc58b3a10124dfe9883e798e8057217e2329975a02c585cb34f91b157171e3d47aa32b82011b5981cf90a9992c7b3 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.10.0": version: 5.62.0 resolution: "@typescript-eslint/utils@npm:5.62.0" @@ -15072,6 +15174,21 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:8.47.0, @typescript-eslint/utils@npm:^8.0.0": + version: 8.47.0 + resolution: "@typescript-eslint/utils@npm:8.47.0" + dependencies: + "@eslint-community/eslint-utils": ^4.7.0 + "@typescript-eslint/scope-manager": 8.47.0 + "@typescript-eslint/types": 8.47.0 + "@typescript-eslint/typescript-estree": 8.47.0 + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 43939de12e12781563c2c679be242786690ec78f2562fcbda96e9cb51f8fc4d6cd9fbb80f871b616d059945c6ef7a54bd942652b56eb23207dc1e7ac55910e83 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.3.0": version: 8.3.0 resolution: "@typescript-eslint/utils@npm:8.3.0" @@ -15116,6 +15233,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.47.0": + version: 8.47.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.47.0" + dependencies: + "@typescript-eslint/types": 8.47.0 + eslint-visitor-keys: ^4.2.1 + checksum: 6aa9234bbe6b44fc9d11304f4f78cf77768d2d8fb2bce5f67132c257b8b1aff569295045eb3cefef6943ede71b305ba109fcdb7170d7276861b5cbbcd8309dae + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.3.0": version: 1.3.0 resolution: "@ungap/structured-clone@npm:1.3.0" @@ -19193,6 +19320,13 @@ __metadata: languageName: node linkType: hard +"code-block-writer@npm:^13.0.3": + version: 13.0.3 + resolution: "code-block-writer@npm:13.0.3" + checksum: 8e234f0ec2db9625d5efb9f05bdae79da6559bb4d9df94a6aa79a89a7b5ae25093b70d309fc5122840c9c07995cb14b4dd3f98a30f8878e3a3372e177df79454 + languageName: node + linkType: hard + "coinselect@npm:3.1.13": version: 3.1.13 resolution: "coinselect@npm:3.1.13" @@ -22258,6 +22392,24 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-jest@npm:^29.0.1": + version: 29.1.0 + resolution: "eslint-plugin-jest@npm:29.1.0" + dependencies: + "@typescript-eslint/utils": ^8.0.0 + peerDependencies: + "@typescript-eslint/eslint-plugin": ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + jest: "*" + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + jest: + optional: true + checksum: e66e7e52a35c50307de366166e1e2979cb12356e6033e426008b1bd9b37a466c0856552a6b6c188b3cc6a20790fcd8ea16071f0147fbaed0f68ee3737290c7e0 + languageName: node + linkType: hard + "eslint-plugin-prettier@npm:4.2.1, eslint-plugin-prettier@npm:^4.0.0": version: 4.2.1 resolution: "eslint-plugin-prettier@npm:4.2.1" @@ -22300,6 +22452,15 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-react-hooks@npm:^5.2.0": + version: 5.2.0 + resolution: "eslint-plugin-react-hooks@npm:5.2.0" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + checksum: 5920736a78c0075488e7e30e04fbe5dba5b6b5a6c8c4b5742fdae6f9b8adf4ee387bc45dc6e03b4012865e6fd39d134da7b83a40f57c90cc9eecf80692824e3a + languageName: node + linkType: hard + "eslint-plugin-react-native-globals@npm:^0.1.1": version: 0.1.2 resolution: "eslint-plugin-react-native-globals@npm:0.1.2" @@ -22427,6 +22588,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 3a77e3f99a49109f6fb2c5b7784bc78f9743b834d238cdba4d66c602c6b52f19ed7bcd0a5c5dbbeae3a8689fd785e76c001799f53d2228b278282cf9f699fff5 + languageName: node + linkType: hard + "eslint@npm:8.50.0": version: 8.50.0 resolution: "eslint@npm:8.50.0" @@ -23513,6 +23681,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: bd537daa9d3cd53887eed35efa0eab2dbb1ca408790e10e024120e7a36c6e9ae2b33710cb8381e35def01bc9c1d7eaba746f886338413e68ff6ebaee07b9a6e8 + languageName: node + linkType: hard + "fecha@npm:^4.2.0": version: 4.2.3 resolution: "fecha@npm:4.2.3" @@ -25368,6 +25548,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^7.0.0": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: d0862bf64d3d58bf34d5fb0a9f725bec9ca5ce8cd1aecc8f28034269e8f69b8009ffd79ca3eda96962a6a444687781cd5efdb8c7c8ddc0a6996e36d31c217f14 + languageName: node + linkType: hard + "image-q@npm:^4.0.0": version: 4.0.0 resolution: "image-q@npm:4.0.0" @@ -26640,8 +26827,8 @@ __metadata: "jail-monkey@patch:jail-monkey@npm%3A2.8.0#./.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch::locator=mobile-monorepo%40workspace%3A.": version: 2.8.0 - resolution: "jail-monkey@patch:jail-monkey@npm%3A2.8.0#./.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch::version=2.8.0&hash=225a6a&locator=mobile-monorepo%40workspace%3A." - checksum: c5caacdc4a38dcdd45209f55c3376d39cd2ea7ae788f90e4bdb270838cb8f13c1b5001a79cd7c6f1ed7f0d462c9ef2d0fbc959e1e48a26dff4dd9aa7ab9ab4f4 + resolution: "jail-monkey@patch:jail-monkey@npm%3A2.8.0#./.yarn/patches/jail-monkey-npm-2.8.0-77e4d06b40.patch::version=2.8.0&hash=5a71d1&locator=mobile-monorepo%40workspace%3A." + checksum: e46a3eb6b071371220d916a96f758e0d5cf6f5d06269802b0943f5ebbc4ee30ac86ef244832632a97a9eac3c41a5121c25d3821d9501ca6f1a4b0e399ef2f00f languageName: node linkType: hard @@ -29316,6 +29503,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": ^5.0.0 + checksum: 8820c0be92994f57281f0a7a2cc4268dcc4b610f9a1ab666685716b4efe4b5898b43c835a8f22298875b31c7a278a5e3b7e253eee7c886546bb0b61fb94bca6b + languageName: node + linkType: hard + "minimatch@npm:^10.0.3": version: 10.0.3 resolution: "minimatch@npm:10.0.3" @@ -29916,6 +30112,21 @@ __metadata: languageName: node linkType: hard +"nitrogen@npm:*": + version: 0.31.8 + resolution: "nitrogen@npm:0.31.8" + dependencies: + chalk: ^5.3.0 + react-native-nitro-modules: ^0.31.8 + ts-morph: ^27.0.0 + yargs: ^18.0.0 + zod: ^4.0.5 + bin: + nitrogen: lib/index.js + checksum: 00c53f2d7ba5a82f2dd78cc8cb797fa7224b25ec45ac53bb0464abb2a96eea4e8b847a632f3bd11d95b80d3fd54cfdc726121318b0ddd8238d42d23a566efad9 + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -31165,7 +31376,7 @@ __metadata: languageName: node linkType: hard -"path-browserify@npm:1.0.1": +"path-browserify@npm:1.0.1, path-browserify@npm:^1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" checksum: c6d7fa376423fe35b95b2d67990060c3ee304fc815ff0a2dc1c6c3cfaff2bd0d572ee67e18f19d0ea3bbe32e8add2a05021132ac40509416459fffee35200699 @@ -31402,6 +31613,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 6817fb74eb745a71445debe1029768de55fd59a42b75606f478ee1d0dc1aa6e78b711d041a7c9d5550e042642029b7f373dc1a43b224c4b7f12d23436735dba0 + languageName: node + linkType: hard + "pidtree@npm:^0.6.0, pidtree@npm:~0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" @@ -32911,6 +33129,48 @@ __metadata: languageName: node linkType: hard +"react-native-nitro-avalabs-crypto@workspace:*, react-native-nitro-avalabs-crypto@workspace:packages/react-native-nitro-avalabs-crypto": + version: 0.0.0-use.local + resolution: "react-native-nitro-avalabs-crypto@workspace:packages/react-native-nitro-avalabs-crypto" + dependencies: + "@react-native/babel-preset": 0.79.5 + "@react-native/eslint-config": 0.82.0 + "@rushstack/eslint-patch": 1.10.4 + "@types/react": 19.0.10 + eslint: 8.50.0 + eslint-plugin-avalabs-mobile: "workspace:*" + nitrogen: "*" + react: 19.0.0 + react-native: 0.79.5 + react-native-nitro-modules: 0.31.10 + typescript: ^5.8.3 + peerDependencies: + react: "*" + react-native: "*" + react-native-nitro-modules: "*" + languageName: unknown + linkType: soft + +"react-native-nitro-modules@npm:0.31.10": + version: 0.31.10 + resolution: "react-native-nitro-modules@npm:0.31.10" + peerDependencies: + react: "*" + react-native: "*" + checksum: 1c8d2f12ff29b6733c784c5fd0dedcbec4ceec9f82eb49f49e7734f31b9367346938010dcacf69bf695a352e36fec5aa23fd57b8091f6acd78c3d9a61e14ea7f + languageName: node + linkType: hard + +"react-native-nitro-modules@npm:^0.31.8": + version: 0.31.8 + resolution: "react-native-nitro-modules@npm:0.31.8" + peerDependencies: + react: "*" + react-native: "*" + checksum: 3976b93015011585112c939ba762240b4cd913435de1a5321d18bbb719624fbfcf8ceca68575b5a03528211d02e36982043363fee8d3c874ac358c2f055f7fdf + languageName: node + linkType: hard + "react-native-os@npm:1.2.6": version: 1.2.6 resolution: "react-native-os@npm:1.2.6" @@ -33284,7 +33544,7 @@ react-native-webview@ava-labs/react-native-webview: peerDependencies: react: "*" react-native: "*" - checksum: e08d1254d04f3074970b63cdf0a363d6b189009270d457e868a26ef534addd69b320b85bef2a22e4eddb79006751bded431b56aaf2baf41976a6d0a5a2a2b91e + checksum: 92b1d7e77f26519681b361c1fccb0c1ddd885f0e0e930046fe9a430eaf62358e86e84065eba18313d8c50d41597c4caae7ea6042cde0293556e8fc1184b9ca8e languageName: node linkType: hard @@ -36734,6 +36994,16 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard +"tinyglobby@npm:^0.2.14": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: ^6.5.0 + picomatch: ^4.0.3 + checksum: 0e33b8babff966c6ab86e9b825a350a6a98a63700fa0bb7ae6cf36a7770a508892383adc272f7f9d17aaf46a9d622b455e775b9949a3f951eaaf5dfb26331d44 + languageName: node + linkType: hard + "tinyrainbow@npm:^1.2.0": version: 1.2.0 resolution: "tinyrainbow@npm:1.2.0" @@ -36882,6 +37152,15 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard +"ts-api-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "ts-api-utils@npm:2.1.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 5b1ef89105654d93d67582308bd8dfe4bbf6874fccbcaa729b08fbb00a940fd4c691ca6d0d2b18c3c70878d9a7e503421b7cc473dbc3d0d54258b86401d4b15d + languageName: node + linkType: hard + "ts-command-line-args@npm:^2.2.0": version: 2.5.1 resolution: "ts-command-line-args@npm:2.5.1" @@ -37015,6 +37294,16 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard +"ts-morph@npm:^27.0.0": + version: 27.0.2 + resolution: "ts-morph@npm:27.0.2" + dependencies: + "@ts-morph/common": ~0.28.1 + code-block-writer: ^13.0.3 + checksum: 1ed2e89257d6f48fdce49bf51e1767787579220197efaa31ac25971c656c9a8a5a6bdd123042d16f83674eec119e4462a06f716187aec0b5e4740888ab5b73b7 + languageName: node + linkType: hard + "ts-node@npm:10.9.2": version: 10.9.2 resolution: "ts-node@npm:10.9.2" @@ -39577,7 +39866,7 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard -"yargs@npm:18.0.0": +"yargs@npm:18.0.0, yargs@npm:^18.0.0": version: 18.0.0 resolution: "yargs@npm:18.0.0" dependencies: @@ -39733,6 +40022,13 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard +"zod@npm:^4.0.5": + version: 4.1.12 + resolution: "zod@npm:4.1.12" + checksum: 91174acc7d2ca5572ad522643474ddd60640cf6877b5d76e5d583eb25e3c4072c6f5eb92ab94f231ec5ce61c6acdfc3e0166de45fb1005b1ea54986b026b765f + languageName: node + linkType: hard + "zustand@npm:5.0.6": version: 5.0.6 resolution: "zustand@npm:5.0.6"