Uh oh!
There was an error while loading. Please reload this page.
fix(rs-sdk-ffi): zeroize private key arrays after use in crypto/signer FFI - #3433
Conversation
…r FFI Private key byte arrays in non-shielded FFI functions were not being zeroized after use, leaving key material on the stack. The shielded crypto module already handles this correctly via zeroize::Zeroize. Wrap all `[u8; 32]` key arrays with `Zeroizing<>` so they are automatically zeroed on drop, covering all return paths including early returns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review GateCommit:
|
📝 WalkthroughWalkthroughRemoved Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@## v3.1-dev #3433 +/- ##
============================================
+ Coverage 80.22% 80.49% +0.26%
============================================
Files 2855 2855 Lines 281685 284101 +2416 ============================================
+ Hits 225983 228678 +2695 + Misses 55702 55423 -279
🚀 New features to boost your workflow:
|
cargo-machete flagged serde_json as unused in rs-scripts, causing CI failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/rs-sdk-ffi/src/crypto/mod.rs (1)
53-71:⚠️ Potential issue | 🟠 MajorZeroizing the stack buffer is good, but key bytes still persist in a non-zeroized
Vec<u8>.At lines 53, 113, and 188, private keys are decoded into
private_key_bytes(Vec<u8>) and then copied intoZeroizing<[u8; 32]>. TheVec<u8>copy is not zeroized on drop, so sensitive material can still linger in heap memory.Use
hex::decode_to_slicedirectly into theZeroizingbuffer to avoid creating the extra secret copy.Suggested pattern (apply in all 3 functions)
- let private_key_bytes = match hex::decode(private_key_str) {- Ok(bytes) if bytes.len() == 32 => bytes,- Ok(_) => {- return DashSDKResult::error(DashSDKError::new(- DashSDKErrorCode::InvalidParameter,- "Private key must be exactly 32 bytes".to_string(),- ))- }- Err(e) => {- return DashSDKResult::error(DashSDKError::new(- DashSDKErrorCode::InvalidParameter,- format!("Invalid private key hex: {}", e),- ))- }- };-- let mut key_array = Zeroizing::new([0u8; 32]);- key_array.copy_from_slice(&private_key_bytes);+ let mut key_array = Zeroizing::new([0u8; 32]);+ if let Err(e) = hex::decode_to_slice(private_key_str, &mut *key_array) {+ return DashSDKResult::error(DashSDKError::new(+ DashSDKErrorCode::InvalidParameter,+ format!("Invalid private key hex: {}", e),+ ));+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rs-sdk-ffi/src/crypto/mod.rs` around lines 53 - 71, The code decodes hex into a temporary Vec<u8> named private_key_bytes and then copies into a Zeroizing<[u8; 32]> (key_array), leaving the heap copy around; change each occurrence (where private_key_str is decoded at lines referenced) to allocate key_array as Zeroizing::new([0u8; 32]) first and use hex::decode_to_slice(private_key_str, &mut *key_array) (or equivalent) to decode directly into the zeroized stack buffer, validate the returned length/error and avoid calling copy_from_slice on a Vec so no intermediate Vec<u8> containing the secret is ever created (apply same change for all three functions/locations that use private_key_str / private_key_bytes / key_array).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/rs-sdk-ffi/src/crypto/mod.rs`:
- Around line 53-71: The code decodes hex into a temporary Vec<u8> named
private_key_bytes and then copies into a Zeroizing<[u8; 32]> (key_array),
leaving the heap copy around; change each occurrence (where private_key_str is
decoded at lines referenced) to allocate key_array as Zeroizing::new([0u8; 32])
first and use hex::decode_to_slice(private_key_str, &mut *key_array) (or
equivalent) to decode directly into the zeroized stack buffer, validate the
returned length/error and avoid calling copy_from_slice on a Vec so no
intermediate Vec<u8> containing the secret is ever created (apply same change
for all three functions/locations that use private_key_str / private_key_bytes /
key_array).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 58226d2a-5e04-43de-b13e-df78a5f28a68
📒 Files selected for processing (3)
packages/rs-scripts/Cargo.tomlpackages/rs-sdk-ffi/src/crypto/mod.rspackages/rs-sdk-ffi/src/signer_simple.rs
💤 Files with no reviewable changes (1)
- packages/rs-scripts/Cargo.toml
QuantumExplorer
commented
Apr 3, 2026
Self reviewed |
Uh oh!
There was an error while loading. Please reload this page.
✅ DashSDKFFI.xcframework built for this PR.
SwiftPM (host the zip at a stable URL, then use): .binaryTarget(
name:"DashSDKFFI",
url:"https://your.cdn.example/DashSDKFFI.xcframework.zip",
checksum:"6af15c010dd65b2dab699725e8ab77c6ded24a7145096f4571e12d1380bb898f")Xcode manual integration:
|
Issue Being Fixed
Security audit finding M5: Private key byte arrays in non-shielded FFI functions (
crypto/mod.rsandsigner_simple.rs) were not being zeroized after use, leaving key material lingering on the stack.The shielded crypto module (
shielded/crypto/address.rs,bundle_build.rs,decrypt.rs) already handles this correctly viazeroize::Zeroize. This PR brings the non-shielded FFI functions to the same standard.What was changed
Wrapped all
[u8; 32]private key arrays withzeroize::Zeroizing<>so they are automatically zeroed on drop. This covers all return paths including early returns, with zero runtime overhead beyond the memset on drop.Files changed:
packages/rs-sdk-ffi/src/crypto/mod.rs— 3 functions:dash_sdk_validate_private_key_for_public_key,dash_sdk_private_key_to_wif,dash_sdk_public_key_data_from_private_key_datapackages/rs-sdk-ffi/src/signer_simple.rs— 1 function:dash_sdk_signer_create_from_private_keyBreaking Changes
None.
Zeroizing<[u8; 32]>implementsDeref<Target=[u8; 32]>, so all existing call sites (.as_slice(),&key_array, indexing) work transparently.Tests
Existing compilation and runtime behavior unchanged —
Zeroizingis a zero-cost wrapper that only adds a zeroize call on drop.🤖 Generated with Claude Code
Summary by CodeRabbit
Security
Chores