Add React Native Nitro bindings for CDK#1982
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1982 +/- ##
==========================================
+ Coverage 71.55% 71.68% +0.13%
==========================================
Files 356 357 +1
Lines 73999 74394 +395
==========================================
+ Hits 52950 53332 +382
- Misses 21049 21062 +13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
1dc1d1c to
c7c2964
Compare
08e9361 to
f5d4910
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6963f16291
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| # Link against pre-built cdk_ffi native library | ||
| # The .so must be placed at: android/src/main/jniLibs/<abi>/libcdk_ffi.so | ||
| find_library(CDK_FFI cdk_ffi PATHS |
There was a problem hiding this comment.
Link Android against the generated Nitro library
For Android package consumers, this CMake file searches for libcdk_ffi.so, but this binding crate is named cdk_nitro and the publish workflow copies libcdk_nitro.so into android/src/main/jniLibs/<abi>. As a result the packaged React Native module will not link/load its Rust symbols on Android; the CMake target should find and link cdk_nitro instead of cdk_ffi.
Useful? React with 👍 / 👎.
|
|
||
| # Link against pre-built cdk_ffi static library | ||
| # Place libcdk_ffi.a in ios/Frameworks/ for each architecture | ||
| s.vendored_libraries = 'ios/Frameworks/libcdk_ffi.a' |
There was a problem hiding this comment.
Point the podspec at the shipped Nitro XCFramework
For iOS releases, the workflow installs ios/Frameworks/CdkNitro.xcframework, but the podspec still declares a non-existent ios/Frameworks/libcdk_ffi.a and -lcdk_ffi. Users running pod install/building the package from the published artifacts will therefore fail to find or link the native library; the podspec needs to vend/link the CdkNitro.xcframework/cdk_nitro artifact that this PR actually builds.
Useful? React with 👍 / 👎.
| pubkeys: add_pks, | ||
| refund_keys: refund_pks, | ||
| num_sigs: if num_sigs > 1 { Some(num_sigs) } else { None }, | ||
| sig_flag: Default::default(), |
There was a problem hiding this comment.
Honor the requested P2PK signature flag
When callers pass sigFlag: 'SigAll' through the Nitro API, C++ forwards it here but Rust ignores _sig_flag and always serializes Default::default() (SIG_INPUTS). That changes the NUT-10 spending condition the wallet creates, so outputs that the app intended to require signatures on both inputs and outputs are minted with input-only enforcement; parse/map the provided flag and reject unknown values instead of defaulting unconditionally.
Useful? React with 👍 / 👎.
| let add_pks = parse_pubkey_array(additional_pubkeys, additional_pubkeys_len); | ||
| let refund_pks = parse_pubkey_array(refund_pubkeys, refund_pubkeys_len); |
There was a problem hiding this comment.
Reject invalid optional P2PK keys instead of dropping them
When an additionalPubkeys or refundPubkeys array is present but one entry is malformed, parse_pubkey_array returns None, which these assignments treat the same as if the caller omitted the array. For example, a typo in a multisig additional key will create a valid output locked only to the primary key rather than throwing, weakening the requested spending condition; return a Result<Option<Vec<_>>> and fail the call on parse errors.
Useful? React with 👍 / 👎.
b1d57ab to
9e59899
Compare
|
@crodas Are the codex reviews addressed they are not marked resolved? |
Some of it is revisited; lemme double-check and close the addressed feedback |
cdk-bot
left a comment
There was a problem hiding this comment.
Verified findings approved for disclosure:
- Validate custom split sums against requested amount (medium) - Callers can accidentally create blinded output requests whose total value differs from the amount they requested, leading to over/under-requested minting or change outputs in the new React Native binding.
- Check seed pointer before from_raw_parts (medium) - A malformed FFI call can trigger undefined behavior in the new React Native Nitro Rust binding instead of returning a null result for invalid seed input.
- Check FFI string pointers before CStr::from_ptr (medium) - Malformed FFI calls with null string pointers can trigger undefined behavior in the new React Native Nitro Rust binding instead of returning a null result.
| const std::vector<KeyEntry>& keys, | ||
| const std::optional<std::vector<double>>& customSplit) { | ||
|
|
||
| if (customSplit.has_value()) { |
There was a problem hiding this comment.
Should this validate the custom split before returning it? With the current path, amount is ignored whenever customSplit is present, so a call like createRandomData(10, ..., [100]) will create outputs totaling 100 while the API call requested 10. Since the non-custom path enforces that the requested amount can be split exactly, the custom path should probably sum the converted denominations and throw if they do not equal amount (and ideally reject invalid/non-integer values too).
| }; | ||
|
|
||
| let seed_slice = unsafe { slice::from_raw_parts(seed, seed_len as usize) }; | ||
|
|
There was a problem hiding this comment.
Should this validate seed before creating the slice? slice::from_raw_parts requires a non-null, properly aligned pointer even for a zero-length slice, so a bad FFI call such as seed = NULL, seed_len = 0 hits undefined behavior before the length check can return null_mut(). Please check seed.is_null() (and preferably seed_len == 64) before calling from_raw_parts.
| sig_flag_ptr: *const c_char, | ||
| ) -> *mut CdkBlindResult { | ||
| let pubkey_str = match unsafe { CStr::from_ptr(pubkey_hex) }.to_str() { | ||
| Ok(s) => s, |
There was a problem hiding this comment.
Should these FFI string pointers be checked for null before calling CStr::from_ptr? from_ptr requires a non-null pointer, so malformed calls can invoke undefined behavior instead of returning null_mut(). This applies at least to pubkey_hex, keyset_id, and the entries in parse_pubkey_array; adding explicit is_null() checks would make these invalid-input paths safe like the existing sig_flag_ptr handling.
Add a new `cdk-nitro` crate and React Native package that exposes CDK functionality via Nitro Modules. The bindings provide a C FFI layer with a C++ HybridObject bridge that lets React Native apps create and manipulate Cashu tokens (OutputData, proofs, keysets, etc.). Includes: - Rust FFI crate (`bindings/react-native/rust`) with OutputDataCreator - C++ HybridObject bridge wrapping the FFI calls - TypeScript Nitro spec and package entry point - Android CMakeLists and iOS podspec for native builds - Node.js FFI test suite using native addon loading - CI job (nitro-binding-tests) and justfile recipes (test-nitro, test-nitro-node) - Added nodejs to the bindings Nix devshell
Add CI workflow (.github/workflows/nitro-publish.yml) that syncs React Native bindings to a separate cdk-nitro repo, cross-compiles native libraries for iOS and Android, creates an XCFramework, and publishes to npm with prebuilt binaries. Add ffi-release-nitro justfile recipe and hook it into the main release flow alongside Swift, Kotlin, and Go.
Add README.md for the React Native Nitro bindings with installation from GitHub, usage examples, API reference, and supported platforms. Make npm publish step conditional on NPM_TOKEN secret availability, emitting a warning with the git install URL when skipped.
- Rename kotlin-build → cross-build in flake.nix (with backwards-compat alias) - Add aarch64-apple-ios-sim to cross-build targets - Use cdk#cross-build instead of cdk#bindings in nitro workflow - Stop copying rust-toolchain.toml to cdk-nitro (conflicts with Nix toolchain) - Remove any rust-toolchain.toml before building as safety measure
- Add bindings/react-native/nitrogen/generated/ and lib/ to .gitignore - Add npm install + codegen step to nitro-publish.yml so generated files sync to cdk-nitro repo - Add codegen verification step to ci.yml nitro-binding-tests job
CMakeLists.txt searched for libcdk_ffi.so but the publish workflow ships libcdk_nitro.so. Similarly the podspec referenced a nonexistent libcdk_ffi.a instead of the CdkNitro.xcframework that gets built.
Parse the sig_flag C string into SigFlag::SigInputs or SigFlag::SigAll, reject unknown values, and default to SigInputs only when null.
parse_pubkey_array now returns Result<Option<Vec<_>>, ()> so a bad entry in additional_pubkeys or refund_pubkeys aborts the call rather than being treated as if the caller omitted the array entirely.
Five tests covering the two previous fixes: - SigAll flag appears in serialized NUT-10 secret - null sig_flag defaults to SIG_INPUTS - unknown sig_flag rejected with null - malformed additional/refund pubkeys rejected with null
…tion - Validate seed pointer and length before slice::from_raw_parts in deterministic blinding - Add null checks for pubkey_hex, keyset_id, and parse_pubkey_array entries - Validate custom split sums to requested amount and contains valid denominations
cdk-bot
left a comment
There was a problem hiding this comment.
Verified findings approved for disclosure:
- React Native P2PK bindings omit refund multisig threshold (medium) - React Native callers cannot create multisig refund P2PK conditions; multiple refund keys are downgraded to the default one required refund signature.
Additional locations included in summary:- bindings/react-native/src/specs/OutputDataCreator.nitro.ts:35
- bindings/react-native/cpp/HybridOutputDataCreator.cpp:106
Unanchored locations included in summary: - bindings/react-native/cpp/cdk_nitro.h:79
| refund_keys: refund_pks, | ||
| num_sigs: if num_sigs > 1 { Some(num_sigs) } else { None }, | ||
| sig_flag, | ||
| num_sigs_refund: None, |
There was a problem hiding this comment.
The new React Native P2PK binding exposes refundPubkeys, but it never exposes or forwards the corresponding num_sigs_refund setting. At this PR head, the TS P2PKOptions has no numSigsRefund, the C++/C FFI call has no refund-signature-count argument, and Rust always serializes Conditions { num_sigs_refund: None }.
That means callers can create a refund path with multiple refund pubkeys but cannot require (for example) 2-of-2 refund signatures; CDK will fall back to the default single refund signature requirement (unwrap_or(1) in the core spending checks). Please plumb a numSigsRefund option through the Nitro spec, C++ bridge, C header, and Rust FFI into Conditions.num_sigs_refund, just like numSigs is handled for the primary path.
Description
Fixes #1902
Add a new
cdk-nitrocrate and React Native package that exposes CDK functionality via Nitro Modules. The bindings provide a C FFI layer with a C++ HybridObject bridge that lets React Native apps create and manipulate Cashu tokens (OutputData, proofs, keysets, etc.).Includes:
bindings/react-native/rust) with OutputDataCreatorNotes to the reviewers
Suggested CHANGELOG Updates
CHANGED
ADDED
REMOVED
FIXED
Checklist
just quick-checkbefore committingcrates/cdk-ffi)