What changed, and why it matters
This commit is a test-only refactor that makes random number generation injectable so unit tests can use a fixed seed and produce deterministic results. It does not change the production code path: the firmware still uses the hardware random number generator (OsRng) when signing real Monero transactions. The expected test output was updated because the deterministic test RNG now produces different, predictable values.
No security action required. Treat as normal test/maintenance refactor. If reviewing for supply-chain risk, verify that rand_core 0.6.4 is the expected workspace version and that OsRng remains available in the no_std firmware build.
Security signals we found
No security fix or vulnerability patch is present in the diff.
Production randomness source remains OsRng; no downgrade to weak RNG.
Deterministic RNG is used only in #[cfg(test)] blocks.
No bounds checks, memory safety, cryptographic algorithm, or key-handling changes.
Evidence from the diff
The change threads an explicit RngCore+CryptoRng through encrypt_data_with_pvk, generate_export_ur_data, and ExportedTransferDetail::key_image, replacing direct OsRng calls inside those functions. Tests now pass a seeded ChaCha20Rng, while the C FFI entry point monero_generate_keyimage and the transaction signing path sign_tx continue to pass OsRng. Cargo.toml changes unify rand_core to the workspace version with default-features = false. The updated test assertion reflects the new deterministic ciphertext/signature produced by the seeded RNG.
Changed components
rust/apps/monero/src/utils/mod.rsrust/apps/monero/src/key_images.rsrust/apps/monero/src/transfer.rsrust/rust_c/src/monero/mod.rsrust/Cargo.tomlrust/Cargo.lockrust/apps/monero/Cargo.tomlrust/rust_c/Cargo.tomlInspect captured patch +35 / −13
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 4a0c852..f8fb87e 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -3840,6 +3840,7 @@ dependencies = [
"itertools 0.13.0",
"keystore",
"minicbor",
+ "rand_core 0.6.4",
"rsa",
"rust_tools",
"serde_json",
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index ad88a7f..05be213 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -95,7 +95,7 @@ serde_derive = { version = "1.0.159" }
serde_bytes = { version = "0.11.5", default-features = false, features = [
"alloc",
] }
-rand_core = { version = "0.6" }
+rand_core = { version = "0.6", default-features = false }
rand_chacha = { version = "0.3.1", default-features = false }
sha2 = { version = "0.10.6", default-features = false, features = ["oid"] }
aes = { version = "0.8.4", default-features = false }
diff --git a/rust/apps/monero/Cargo.toml b/rust/apps/monero/Cargo.toml
index 1fa0af1..43bf78b 100644
--- a/rust/apps/monero/Cargo.toml
+++ b/rust/apps/monero/Cargo.toml
@@ -11,7 +11,7 @@ cryptoxide = { workspace = true }
bitcoin = { workspace = true }
curve25519-dalek = { version = "4.1.3", default-features = false }
base58-monero = { version = "2", default-features = false }
-rand_core = { version = "0.6", default-features = false }
+rand_core = { workspace = true }
zeroize = { version = "^1.5", default-features = false }
app_utils = { path = "../utils" }
rust_tools = { path = "../../tools" }
diff --git a/rust/apps/monero/src/key_images.rs b/rust/apps/monero/src/key_images.rs
index 446030a..1a7f60e 100644
--- a/rust/apps/monero/src/key_images.rs
+++ b/rust/apps/monero/src/key_images.rs
@@ -13,7 +13,6 @@ use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::EdwardsPoint;
use hex;
use monero_serai::generators::hash_to_point;
-use rand_core::OsRng;
use rand_core::{CryptoRng, RngCore};
#[derive(Debug, Clone, Copy)]
@@ -240,7 +239,7 @@ impl ExportedTransferDetail {
pub fn key_image<R: RngCore + CryptoRng>(
&self,
keypair: &KeyPair,
- rng: R,
+ mut rng: R,
) -> KeyImageAndSignature {
generate_key_image(
keypair,
@@ -286,7 +285,11 @@ impl ExportedTransferDetail {
}
}
-pub fn generate_export_ur_data(keypair: KeyPair, request_data: Vec<u8>) -> Result<Vec<u8>> {
+pub fn generate_export_ur_data<R: RngCore + CryptoRng>(
+ keypair: KeyPair,
+ request_data: Vec<u8>,
+ mut rng: R,
+) -> Result<Vec<u8>> {
let decrypted_data = decrypt_data_with_pvk(
keypair.view.to_bytes().try_into().unwrap(),
request_data.clone(),
@@ -304,18 +307,23 @@ pub fn generate_export_ur_data(keypair: KeyPair, request_data: Vec<u8>) -> Resul
let mut key_images: KeyImages = KeyImages(vec![]);
for output in outputs.details.iter() {
- key_images.0.push(output.key_image(&keypair.clone(), OsRng));
+ key_images
+ .0
+ .push(output.key_image(&keypair.clone(), &mut rng));
}
Ok(encrypt_data_with_pvk(
keypair,
key_images.to_bytes(),
KEY_IMAGE_EXPORT_MAGIC,
+ &mut rng,
))
}
#[cfg(test)]
mod tests {
+ use rand_core::SeedableRng;
+
use super::*;
#[test]
@@ -380,6 +388,8 @@ mod tests {
#[test]
fn test_build_key_images_response() {
+ let rng_seed = [0; 32];
+ let rng = rand_chacha::ChaCha20Rng::from_seed(rng_seed.try_into().unwrap());
let data = hex::decode("4d6f6e65726f206f7574707574206578706f727404a66c8ac44d24aefcdc62411394362a9ca0a5622a0f9be2ea6af704e9ffa43d53a139338aa1ae8b86f8f2c4cef08bed7f059f8ea7adc6760e894acd4f7d67c5b4e60e4fbd16a9f5ba34196b42899a8a1bed460e12d37f6a9e9e57305ab2d227a0ee2142d18444e396e60ad70f8cc8d22f6195391ed8e770755f64dacf9768a34946e1094692ec12dc2dc4430f").unwrap();
let sec_s_key = PrivateKey::from_bytes(
@@ -393,9 +403,9 @@ mod tests {
let keypair = crate::key::KeyPair::new(sec_v_key.clone(), sec_s_key.clone());
let key_images_export_data =
- generate_export_ur_data(keypair.clone(), data.clone()).unwrap();
+ generate_export_ur_data(keypair.clone(), data.clone(), rng).unwrap();
- assert_eq!(hex::encode(key_images_export_data), "4d6f6e65726f206b657920696d616765206578706f727403a7f77b9eb360d066d49f2eaa597fe16862b5c1c90eba00af226a1e6c43b774b2b468994d6ff7ee2a7d829812c2d6adedcb9131133f043ff98223531f2b721ff7c1468885baea1a7acd4d6c929ea8ce07161c7f443e9e6ed19677c6c6f53185a50a0418f14ce26d7988c2190e09a04809346d6d7aabdfe929ce88bed228531a44d4c9f1ee2826dcd2f4d78900");
+ assert_eq!(hex::encode(key_images_export_data), "4d6f6e65726f206b657920696d616765206578706f727403903df1a0ade0b87669089dd7b782428c6cc412dbd2ea7da1aa91a84de1213f8e3fdfd09875d6a119b90a158db50787b36f383f87e29ca264a635e90e3d6dce3d2334cab3ee8e2461fc7614fe05c5d83b23f8f7e9906bffc471cd99f31ec56e6870a9ce5ade2f32b43d5966014a4f57bc8837fb004ebe67e284e64c970a0201ec32b57aae7ce0d614274bd60e");
}
#[test]
diff --git a/rust/apps/monero/src/transfer.rs b/rust/apps/monero/src/transfer.rs
index 06d4c55..4d73c31 100644
--- a/rust/apps/monero/src/transfer.rs
+++ b/rust/apps/monero/src/transfer.rs
@@ -806,6 +806,7 @@ pub fn sign_tx(keypair: KeyPair, request_data: Vec<u8>) -> Result<Vec<u8>> {
keypair,
signed_txes.serialize(),
SIGNED_TX_PREFIX,
+ OsRng,
))
}
diff --git a/rust/apps/monero/src/utils/mod.rs b/rust/apps/monero/src/utils/mod.rs
index 80b6e38..e705da5 100644
--- a/rust/apps/monero/src/utils/mod.rs
+++ b/rust/apps/monero/src/utils/mod.rs
@@ -60,10 +60,15 @@ pub fn decrypt_data_with_pincode(data: Vec<u8>, pin: [u8; 6]) -> String {
String::from_utf8(buffer).unwrap()
}
-pub fn encrypt_data_with_pvk(keypair: KeyPair, data: Vec<u8>, magic: &str) -> Vec<u8> {
+pub fn encrypt_data_with_pvk<R: RngCore + CryptoRng>(
+ keypair: KeyPair,
+ data: Vec<u8>,
+ magic: &str,
+ mut rng: R,
+) -> Vec<u8> {
let pvk_hash = cryptonight_hash_v0(&keypair.view.to_bytes());
let magic_bytes = magic.as_bytes();
- let nonce_num = OsRng.next_u64().to_be_bytes();
+ let nonce_num = rng.next_u64().to_be_bytes();
let key = GenericArray::from_slice(&pvk_hash);
let nonce = GenericArray::from_slice(&nonce_num);
@@ -92,7 +97,7 @@ pub fn encrypt_data_with_pvk(keypair: KeyPair, data: Vec<u8>, magic: &str) -> Ve
&keccak256(&unsigned_buffer),
&keypair.view.get_public_key(),
&PrivateKey::from_bytes(&keypair.view.to_bytes()),
- &mut OsRng,
+ &mut rng,
)
.unwrap();
buffer.extend_from_slice(&signature.0);
@@ -212,6 +217,7 @@ pub fn fmt_monero_amount(value: u64) -> String {
mod tests {
use super::*;
use hex;
+ use rand_core::SeedableRng;
#[test]
fn test_verify() {
@@ -346,6 +352,8 @@ mod tests {
#[test]
fn test_encrypt_data_with_pvk() {
+ let rng_seed = [0; 32];
+ let mut rng = rand_chacha::ChaCha20Rng::from_seed(rng_seed.try_into().unwrap());
let sec_s_key = PrivateKey::from_bytes(
&hex::decode("6ae3c3f834b39aa102158b3a54a6e9557f0ff71e196e7b08b89a11be5093ad03")
.unwrap(),
@@ -358,7 +366,7 @@ mod tests {
let data = hex::decode("03000707013e8c52245d21b22cbcb90f95270a7937d4974d726209f0a41fdefc7f9df01fde01c8b486383e45d72b841a8b76094dbaa26f9800aac4eaced3bc06122a3380bcf6c666d2281480a0b787e905000000012d58a6378c07f230148c11979cc6e3bec2719f0ec92de21f7fae02029ab025e000f385873857dc102abc6d35c878db7be629646658ae1a418afb27a943f8a2591be4f450e9148094ebdc03000001014ef323a52d2e048594ad73acbe5fb7e588b1859ec9aa02b2670f487660b2700901f485873857dc102abc6d35c878db7be629646658ae1a418afb27a943f8a2591be4f450e914c0b5809ce50500000001cb8ab3c1b4dd10404a4a3c9275a7e2e1e9bf2e4edf1c84f61952bb97965573a300d0c78a38bdd50fdc0367b3141fdc055dec3af5e3ac920dd55816823dfe02f70c3d1816431480c2d72f00000301dd8c2a791056760d903bf06e7930585201e0bd20bcba1e720b85ad0e4d628e4801d1c78a38bdd50fdc0367b3141fdc055dec3af5e3ac920dd55816823dfe02f70c3d18164314a0eec19e03000000019b65ada69049d73e4b049ebd50393410cdc05dad5314690d2b4a36628c4e257600a4909d385d43421399107bd34350b8938f9ff69da18e8f083e6522adf6aa270b3f370ed41480e8eda1ba01000100016311ba60a0a8c636806e232db3e1ad7f79e26df3d24258e264e4351e47f4374d01a5909d385d43421399107bd34350b8938f9ff69da18e8f083e6522adf6aa270b3f370ed414c0c2b383ae0400000063c57cc457a1485fc5f8e6dfc8b70430f41946a7d0cd51e84ef5ac819ff2b2c4bcec6f1e6dd57e7e791d8cca2091169bba53496d72375331f8d56cd33f5e0ca4").unwrap();
let magic = OUTPUT_EXPORT_MAGIC;
- let bin_data = encrypt_data_with_pvk(keypair, data.clone(), magic);
+ let bin_data = encrypt_data_with_pvk(keypair, data.clone(), magic, rng);
let keypair = crate::key::KeyPair::new(sec_v_key, sec_s_key);
diff --git a/rust/rust_c/Cargo.toml b/rust/rust_c/Cargo.toml
index 54876e3..649eff4 100644
--- a/rust/rust_c/Cargo.toml
+++ b/rust/rust_c/Cargo.toml
@@ -35,6 +35,7 @@ zcash_vendor = { workspace = true, optional = true }
ed25519-bip32-core = { workspace = true }
app_utils = { workspace = true }
rust_tools = { workspace = true }
+rand_core = { workspace = true }
sui-types = { git = "https://github.com/KeystoneHQ/sui.git", tag = "0.1.2", package = "sui-types" }
#apps
diff --git a/rust/rust_c/src/monero/mod.rs b/rust/rust_c/src/monero/mod.rs
index 9eba270..9f5f422 100644
--- a/rust/rust_c/src/monero/mod.rs
+++ b/rust/rust_c/src/monero/mod.rs
@@ -17,6 +17,7 @@ use crate::common::utils::{convert_c_char, recover_c_char};
use crate::extract_array;
use crate::extract_ptr_with_type;
use cty::c_char;
+use rand_core::OsRng;
use structs::{DisplayMoneroOutput, DisplayMoneroUnsignedTx};
use ur_registry::monero::xmr_keyimage::XmrKeyImage;
use ur_registry::monero::xmr_output::XmrOutput;
@@ -196,7 +197,7 @@ pub unsafe extern "C" fn monero_generate_keyimage(
let request = extract_ptr_with_type!(ptr, XmrOutput);
let seed = extract_array!(seed, u8, seed_len as usize);
let keypair = app_monero::key::generate_keypair(seed, major).unwrap();
- match app_monero::key_images::generate_export_ur_data(keypair, request.get_payload()) {
+ match app_monero::key_images::generate_export_ur_data(keypair, request.get_payload(), OsRng) {
Ok(data) => {
let data = XmrKeyImage::new(data);
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.