What changed, and why it matters
This commit is a code-review hardening pass for the Cardano wallet module in the Keystone 3 firmware. It replaces many Rust `.unwrap()` calls with proper error handling, adds bounds checks for derivation paths and public keys, zeroizes sensitive seed/entropy buffers after use, and fixes a seed-length bug for BIP39 vs SLIP39 accounts. Most changes reduce the chance that malformed transaction data or missing key material causes a crash or leaks secrets, but the commit is broad and includes many test additions, so it is better characterized as defensive cleanup than a single fixed vulnerability.
Treat this as a defensive hardening commit. Review the remaining `.unwrap()`/`.expect()` sites in the Cardano module and other chains, ensure all FFI-extracted secret buffers are zeroized consistently, and verify that `GetCurrentAccountSeedLen()` is used everywhere seed length is computed. Run the new unit tests and perform fuzzing on Cardano transaction parsing/signing inputs. No emergency patch is indicated, but the seed-length fix should be validated against BIP39 and SLIP39 account creation tests.
Security signals we found
Removal of numerous `.unwrap()` calls in Cardano parsing/signing paths
Addition of `zeroize()` on entropy/seed buffers after FFI extraction
Fix of seed length selection for BIP39 vs SLIP39 in public-info generation
Clearing of sensitive C strings before `free_simple_response_c_char`
Bounds and error handling added for derivation-path components and key hashes
Global mutable pointer `xpub` renamed to `g_xpub` to reduce shadowing/confusion
Evidence from the diff
The diff hardens Cardano signing and parsing across Rust (rust/apps/cardano) and C layers. Key changes: (1) Result<T> replaces the R<T> alias and .unwrap() is removed from address.rs, governance.rs, slip23.rs, structs.rs, and transaction.rs in favor of map_err/? propagation; (2) derivation-path index access is guarded (get_index().ok_or(...)); (3) calc_icarus_master_key now returns Result<XPrv> instead of panicking; (4) seed buffers extracted in FFI are switched to mutable slices and zeroize()d after master-key generation in cardano_sign_catalyst, cardano_sign_sign_data, cardano_sign_tx, cardano_sign_tx_unlimited, cardano_get_pubkey_by_slip23, and get_icarus_master_key; (5) C code introduces GetCurrentAccountSeedLen() to use the correct 64-byte BIP39 seed length instead of the entropy length, fixing a likely bug in AccountPublicSavePublicInfo and TempAccountPublicInfo; (6) sensitive C strings (cip3_response, ledger_bitbox02_response) are cleared before free; (7) a global xpub pointer is renamed g_xpub and an index lookup is simplified. No CVE, advisory, or vendor security statement is present in the supplied materials.
Changed components
rust/apps/cardano/src/address.rsrust/apps/cardano/src/errors.rsrust/apps/cardano/src/governance.rsrust/apps/cardano/src/slip23.rsrust/apps/cardano/src/structs.rsrust/apps/cardano/src/transaction.rsrust/rust_c/src/cardano/mod.rsrust/rust_c/src/common/mod.rssrc/crypto/account_public_info.csrc/crypto/utils/hash_and_salt.csrc/managers/account_manager.csrc/managers/account_manager.hsrc/ui/gui_chain/multi/web3/gui_ada.cInspect captured patch +1240 / −257
diff --git a/rust/apps/cardano/src/address.rs b/rust/apps/cardano/src/address.rs
index 73e331a..037d332 100644
--- a/rust/apps/cardano/src/address.rs
+++ b/rust/apps/cardano/src/address.rs
@@ -1,4 +1,4 @@
-use crate::errors::{CardanoError, R};
+use crate::errors::{CardanoError, Result};
use alloc::string::{String, ToString};
use cardano_serialization_lib::protocol_types::credential::*;
@@ -16,7 +16,7 @@ pub enum AddressType {
Enterprise,
}
-pub fn calc_stake_address_from_xpub(stake_key: [u8; 32]) -> R<String> {
+pub fn calc_stake_address_from_xpub(stake_key: [u8; 32]) -> Result<String> {
let stake_key_hash = blake2b_224(&stake_key);
let address = RewardAddress::new(
1,
@@ -28,13 +28,19 @@ pub fn calc_stake_address_from_xpub(stake_key: [u8; 32]) -> R<String> {
.map_err(|e| CardanoError::AddressEncodingError(e.to_string()))
}
-pub fn derive_xpub_from_xpub(xpub: String, path: CryptoKeyPath) -> R<String> {
+pub fn derive_xpub_from_xpub(xpub: String, path: CryptoKeyPath) -> Result<String> {
let xpub_bytes = hex::decode(xpub).map_err(|e| CardanoError::DerivationError(e.to_string()))?;
let xpub =
XPub::from_slice(&xpub_bytes).map_err(|e| CardanoError::DerivationError(e.to_string()))?;
let mut xpub = xpub;
for component in path.get_components() {
- xpub = xpub.derive(DerivationScheme::V2, component.get_index().unwrap())?;
+ xpub = xpub.derive(
+ DerivationScheme::V2,
+ component
+ .get_index()
+ .ok_or(CardanoError::DerivationError("Index is None".to_string()))
+ .map_err(|e| CardanoError::DerivationError(e.to_string()))?,
+ )?;
}
Ok(hex::encode(xpub.public_key()))
}
@@ -46,7 +52,7 @@ pub fn derive_address(
stake_key_index: u32,
address_type: AddressType,
network: u8,
-) -> R<String> {
+) -> Result<String> {
let xpub_bytes = hex::decode(xpub).map_err(|e| CardanoError::DerivationError(e.to_string()))?;
let xpub =
XPub::from_slice(&xpub_bytes).map_err(|e| CardanoError::DerivationError(e.to_string()))?;
@@ -105,7 +111,7 @@ pub fn derive_address(
}
}
-pub fn derive_pubkey_hash(xpub: String, change: u32, index: u32) -> R<[u8; 28]> {
+pub fn derive_pubkey_hash(xpub: String, change: u32, index: u32) -> Result<[u8; 28]> {
let xpub_bytes = hex::decode(xpub).map_err(|e| CardanoError::DerivationError(e.to_string()))?;
let xpub =
XPub::from_slice(&xpub_bytes).map_err(|e| CardanoError::DerivationError(e.to_string()))?;
@@ -165,14 +171,14 @@ mod tests {
buf.extend(stake);
let spend_address =
bech32::encode::<Bech32>(bech32::Hrp::parse_unchecked(prefix), &buf).unwrap();
- println!("{}", spend_address);
+ println!("{spend_address}");
let mut buf2 = vec![];
buf2.push(0b1110_0001);
buf2.extend(stake);
let reward_address =
bech32::encode::<Bech32>(bech32::Hrp::parse_unchecked("stake"), &buf2).unwrap();
- println!("{}", reward_address);
+ println!("{reward_address}");
}
#[test]
@@ -187,6 +193,7 @@ mod tests {
)
.unwrap();
{
+ println!("xpub = {xpub}");
let spend_address =
derive_address(xpub.to_string(), 0, 0, 0, AddressType::Base, 1).unwrap();
assert_eq!("addr1qy8ac7qqy0vtulyl7wntmsxc6wex80gvcyjy33qffrhm7sh927ysx5sftuw0dlft05dz3c7revpf7jx0xnlcjz3g69mq4afdhv", spend_address)
@@ -221,6 +228,17 @@ mod tests {
);
}
+ #[test]
+ fn test_calc_stake_address_from_xpub_different_keys() {
+ let key1 = [0u8; 32];
+ let key2 = [1u8; 32];
+ let addr1 = calc_stake_address_from_xpub(key1).unwrap();
+ let addr2 = calc_stake_address_from_xpub(key2).unwrap();
+ assert_ne!(addr1, addr2);
+ assert!(addr1.starts_with("stake"));
+ assert!(addr2.starts_with("stake"));
+ }
+
#[test]
fn test_derive_pubkey_hash() {
let path = "m/1852'/1815'/0'";
@@ -263,11 +281,119 @@ mod tests {
let seed = hex::decode("c080e9d40873204bb1bb5837dc88886b").unwrap();
let xpub = crate::slip23::from_seed_slip23_path(&seed, path)
.unwrap()
- .xprv
.public()
.to_string();
let spend_address =
derive_address(xpub.to_string(), 0, 0, 0, AddressType::Base, 1).unwrap();
assert_eq!("addr1q9jlm0nq3csn7e6hs9ndt8yhwy4pzxtaq5vvs7zqdzyqv0e9wqpqu38y55a5xjx36lvu49apd4ke34q3ajus2ayneqcqqqnxcc", spend_address)
}
+
+ #[test]
+ fn test_base_address_change_chain_internal_vs_external() {
+ let account_path = "m/1852'/1815'/0'";
+ let entropy = hex::decode("00000000000000000000000000000000").unwrap();
+ let xpub =
+ keystore::algorithms::ed25519::bip32_ed25519::get_extended_public_key_by_entropy(
+ entropy.as_slice(),
+ b"",
+ &account_path.to_string(),
+ )
+ .unwrap();
+
+ let external = derive_address(xpub.to_string(), 0, 0, 0, AddressType::Base, 1).unwrap();
+ let internal = derive_address(xpub.to_string(), 1, 0, 0, AddressType::Base, 1).unwrap();
+
+ assert_ne!(external, internal);
+ assert!(external.starts_with("addr"));
+ assert!(internal.starts_with("addr"));
+ }
+
+ #[test]
+ fn test_base_address_changes_with_stake_index() {
+ let account_path = "m/1852'/1815'/0'";
+ let entropy = hex::decode("00000000000000000000000000000000").unwrap();
+ let xpub =
+ keystore::algorithms::ed25519::bip32_ed25519::get_extended_public_key_by_entropy(
+ entropy.as_slice(),
+ b"",
+ &account_path.to_string(),
+ )
+ .unwrap();
+
+ let base_idx0 = derive_address(xpub.to_string(), 0, 0, 0, AddressType::Base, 1).unwrap();
+ let base_idx1 = derive_address(xpub.to_string(), 0, 0, 1, AddressType::Base, 1).unwrap();
+ assert_ne!(base_idx0, base_idx1);
+ }
+
+ #[test]
+ fn test_derive_address_testnet() {
+ let account_path = "m/1852'/1815'/0'";
+ let entropy = hex::decode("00000000000000000000000000000000").unwrap();
+ let xpub =
+ keystore::algorithms::ed25519::bip32_ed25519::get_extended_public_key_by_entropy(
+ entropy.as_slice(),
+ b"",
+ &account_path.to_string(),
+ )
+ .unwrap();
+
+ let testnet_address =
+ derive_address(xpub.to_string(), 0, 0, 0, AddressType::Base, 0).unwrap();
+ assert!(testnet_address.starts_with("addr_test"));
+ }
+
+ #[test]
+ fn test_derive_address_different_indexes() {
+ let account_path = "m/1852'/1815'/0'";
+ let entropy = hex::decode("00000000000000000000000000000000").unwrap();
+ let xpub =
+ keystore::algorithms::ed25519::bip32_ed25519::get_extended_public_key_by_entropy(
+ entropy.as_slice(),
+ b"",
+ &account_path.to_string(),
+ )
+ .unwrap();
+
+ let addr0 = derive_address(xpub.to_string(), 0, 0, 0, AddressType::Base, 1).unwrap();
+ let addr1 = derive_address(xpub.to_string(), 0, 1, 0, AddressType::Base, 1).unwrap();
+ assert_ne!(addr0, addr1);
+ }
+
+ #[test]
+ fn test_derive_pubkey_hash_different_paths() {
+ let path = "m/1852'/1815'/0'";
+ let entropy = hex::decode("00000000000000000000000000000000").unwrap();
+ let xpub =
+ keystore::algorithms::ed25519::bip32_ed25519::get_extended_public_key_by_entropy(
+ entropy.as_slice(),
+ b"",
+ &path.to_string(),
+ )
+ .unwrap();
+
+ let hash0 = derive_pubkey_hash(xpub.to_string(), 0, 0).unwrap();
+ let hash1 = derive_pubkey_hash(xpub.to_string(), 0, 1).unwrap();
+ assert_ne!(hash0, hash1);
+ }
+
+ #[test]
+ fn test_derive_xpub_from_xpub_invalid_hex() {
+ let invalid_xpub = "invalid_hex".to_string();
+ let path1 = PathComponent::new(Some(2), false).unwrap();
+ let source_fingerprint: [u8; 4] = [18, 52, 86, 120];
+ let components = vec![path1];
+ let crypto_key_path = CryptoKeyPath::new(components, Some(source_fingerprint), None);
+ let result = derive_xpub_from_xpub(invalid_xpub, crypto_key_path);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_derive_xpub_from_xpub_empty_path() {
+ let xpub = "cc077f786b2f9d5e8fcdef0c7aad56efc4a70abb7bf5947148d5921d23bfe22abe95c9196a0ece66f56065665aeb8d081ba1e19bbf4fe5d27f07d4c362bb39a5".to_string();
+ let source_fingerprint: [u8; 4] = [18, 52, 86, 120];
+ let crypto_key_path = CryptoKeyPath::new(vec![], Some(source_fingerprint), None);
+ let derived_xpub = derive_xpub_from_xpub(xpub, crypto_key_path).unwrap();
+ // Empty path should return the same xpub (just the public key part)
+ assert_eq!(derived_xpub.len(), 64); // 32 bytes hex encoded
+ }
}
diff --git a/rust/apps/cardano/src/errors.rs b/rust/apps/cardano/src/errors.rs
index e7e16f9..05e100d 100644
--- a/rust/apps/cardano/src/errors.rs
+++ b/rust/apps/cardano/src/errors.rs
@@ -20,7 +20,7 @@ pub enum CardanoError {
InvalidSeed(String),
}
-pub type R<T> = Result<T, CardanoError>;
+pub type Result<T> = core::result::Result<T, CardanoError>;
impl From<DeserializeError> for CardanoError {
fn from(value: DeserializeError) -> Self {
diff --git a/rust/apps/cardano/src/governance.rs b/rust/apps/cardano/src/governance.rs
index 07fa735..198b4d4 100644
--- a/rust/apps/cardano/src/governance.rs
+++ b/rust/apps/cardano/src/governance.rs
@@ -1,5 +1,5 @@
use crate::address;
-use crate::errors::{CardanoError, R};
+use crate::errors::{CardanoError, Result};
use crate::structs::SignVotingRegistrationResult;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
@@ -16,7 +16,7 @@ pub fn build_metadata_cbor(
payment_address: &[u8],
nonce: u64,
voting_purpose: u8,
-) -> R<Vec<u8>> {
+) -> Result<Vec<u8>> {
match build_delegations(delegations) {
Ok(delegations_vec) => {
let voting_registration = CardanoVotingRegistration::new(
@@ -36,7 +36,7 @@ pub fn build_metadata_cbor(
}
}
-pub fn build_delegations(delegations: Vec<CardanoDelegation>) -> R<Vec<(String, u8)>> {
+pub fn build_delegations(delegations: Vec<CardanoDelegation>) -> Result<Vec<(String, u8)>> {
let mut delegations_vec = Vec::new();
for delegation in delegations {
let vote_key = hex::encode(delegation.get_pub_key());
@@ -53,7 +53,7 @@ pub fn sign(
nonce: u64,
voting_purpose: u8,
icarus_master_key: XPrv,
-) -> R<SignVotingRegistrationResult> {
+) -> Result<SignVotingRegistrationResult> {
let cbor = build_metadata_cbor(
delegations,
stake_pub,
@@ -68,28 +68,34 @@ pub fn sign_voting_registration(
path: &String,
unsigned: &[u8],
icarus_master_key: XPrv,
-) -> R<SignVotingRegistrationResult> {
+) -> Result<SignVotingRegistrationResult> {
let bip32_signing_key =
keystore::algorithms::ed25519::bip32_ed25519::derive_extended_privkey_by_xprv(
&icarus_master_key,
path,
)
- .unwrap();
+ .map_err(|e| CardanoError::DerivationError(e.to_string()))?;
let signed_data = bip32_signing_key.sign::<Vec<u8>>(&blake2b_256(unsigned));
Ok(SignVotingRegistrationResult::new(
signed_data.to_bytes().to_vec(),
))
}
-pub fn parse_stake_address(stake_pub: Vec<u8>) -> R<String> {
+pub fn parse_stake_address(stake_pub: Vec<u8>) -> Result<String> {
let stake_address =
- address::calc_stake_address_from_xpub(stake_pub.try_into().unwrap()).unwrap();
+ address::calc_stake_address_from_xpub(stake_pub.try_into().map_err(|_| {
+ CardanoError::InvalidTransaction("Invalid stake public key length".to_string())
+ })?)
+ .map_err(|e| CardanoError::AddressEncodingError(e.to_string()))?;
Ok(stake_address)
}
-pub fn parse_payment_address(payment_address: Vec<u8>) -> R<String> {
- let payment_address = Address::from_bytes(payment_address).unwrap();
- let addr = payment_address.to_bech32(None).unwrap();
+pub fn parse_payment_address(payment_address: Vec<u8>) -> Result<String> {
+ let payment_address = Address::from_bytes(payment_address)
+ .map_err(|e| CardanoError::InvalidTransaction(e.to_string()))?;
+ let addr = payment_address
+ .to_bech32(None)
+ .map_err(|e| CardanoError::AddressEncodingError(e.to_string()))?;
Ok(addr)
}
@@ -246,4 +252,102 @@ mod tests {
let payment_address = parse_payment_address(payment_address_bytes).unwrap();
assert_eq!(payment_address, "addr_test1qp5l5x7exwzhgupzs0v0ku0censcx8p75jz52cl4uszr463n5nclg6z9gazt9lekgje2k7w53em2xxrljqh73gdul2ksx5mjej".to_string());
}
+
+ #[test]
+ fn test_build_delegations_empty() {
+ let delegations = vec![];
+ let result = build_delegations(delegations).unwrap();
+ assert_eq!(result.len(), 0);
+ }
+
+ #[test]
+ fn test_build_delegations_single() {
+ let vote_pub_key =
+ hex::decode("a6a3c0447aeb9cc54cf6422ba32b294e5e1c3ef6d782f2acff4a70694c4d1663")
+ .unwrap();
+ let delegations = vec![CardanoDelegation::new(vote_pub_key, 5)];
+ let delegations_vec = build_delegations(delegations).unwrap();
+ assert_eq!(delegations_vec.len(), 1);
+ assert_eq!(delegations_vec[0].1, 5);
+ }
+
+ #[test]
+ fn test_build_metadata_cbor_with_zero_nonce() {
+ let vote_pub_key =
+ hex::decode("a6a3c0447aeb9cc54cf6422ba32b294e5e1c3ef6d782f2acff4a70694c4d1663")
+ .unwrap();
+ let delegations = vec![CardanoDelegation::new(vote_pub_key, 1)];
+ let stake_pub =
+ hex::decode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c")
+ .unwrap();
+ let payment_address = hex::decode("0069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad").unwrap();
+ let cbor = build_metadata_cbor(
+ delegations,
+ &stake_pub,
+ &payment_address,
+ 0, // zero nonce
+ 0,
+ )
+ .unwrap();
+ assert!(!cbor.is_empty());
+ }
+
+ #[test]
+ fn test_build_metadata_cbor_different_voting_purpose() {
+ let vote_pub_key =
+ hex::decode("a6a3c0447aeb9cc54cf6422ba32b294e5e1c3ef6d782f2acff4a70694c4d1663")
+ .unwrap();
+ let delegations = vec![CardanoDelegation::new(vote_pub_key, 1)];
+ let stake_pub =
+ hex::decode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c")
+ .unwrap();
+ let payment_address = hex::decode("0069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad").unwrap();
+ let cbor1 =
+ build_metadata_cbor(delegations.clone(), &stake_pub, &payment_address, 100, 0).unwrap();
+ let cbor2 = build_metadata_cbor(
+ delegations,
+ &stake_pub,
+ &payment_address,
+ 100,
+ 1, // different voting purpose
+ )
+ .unwrap();
+ assert_ne!(cbor1, cbor2);
+ }
+
+ #[test]
+ fn test_parse_stake_address_invalid_length() {
+ let invalid_stake_pub = vec![0u8; 31]; // wrong length
+ let result = parse_stake_address(invalid_stake_pub);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_parse_payment_address_invalid() {
+ let invalid_address = vec![0xff; 10]; // invalid address bytes
+ let result = parse_payment_address(invalid_address);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_sign_voting_registration_different_paths() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key =
+ keystore::algorithms::ed25519::bip32_ed25519::get_icarus_master_key_by_entropy(
+ &entropy, passphrase,
+ )
+ .map_err(|e| CardanoError::SigningFailed(e.to_string()))
+ .unwrap();
+
+ let cbor = hex::decode("a119ef64a50181825820248aba8dce1e4b0a5e53509d07c42ac34f970ec452293a84763bb77359b5263f01025820ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c0358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad0418640500").unwrap();
+
+ let path1 = "m/1852'/1815'/0'/2/0".to_string();
+ let path2 = "m/1852'/1815'/0'/2/1".to_string();
+
+ let sig1 = sign_voting_registration(&path1, &cbor, icarus_master_key.clone()).unwrap();
+ let sig2 = sign_voting_registration(&path2, &cbor, icarus_master_key).unwrap();
+
+ assert_ne!(sig1.get_signature(), sig2.get_signature());
+ }
}
diff --git a/rust/apps/cardano/src/slip23.rs b/rust/apps/cardano/src/slip23.rs
index 1fec8ca..87d5711 100644
--- a/rust/apps/cardano/src/slip23.rs
+++ b/rust/apps/cardano/src/slip23.rs
@@ -1,31 +1,11 @@
-use crate::errors::{CardanoError, R};
+use crate::errors::{CardanoError, Result};
use alloc::{format, string::ToString, vec::Vec};
use cryptoxide::hashing::sha512;
use ed25519_bip32_core::{DerivationScheme, XPrv};
use keystore::algorithms::crypto::hmac_sha512;
-#[derive(Debug, Clone)]
-pub struct CardanoHDNode {
- pub xprv: XPrv,
- pub fingerprint: [u8; 4],
-}
-
-impl CardanoHDNode {
- pub fn new(xprv: XPrv) -> Self {
- let fingerprint = Self::calculate_fingerprint(&xprv);
- Self { xprv, fingerprint }
- }
-
- fn calculate_fingerprint(xprv: &XPrv) -> [u8; 4] {
- let pubkey = xprv.public().public_key();
- let mut fingerprint = [0u8; 4];
- fingerprint.copy_from_slice(&pubkey[..4]);
- fingerprint
- }
-}
-
// https://github.com/satoshilabs/slips/blob/master/slip-0023.md
-pub fn from_seed_slip23(seed: &[u8]) -> R<CardanoHDNode> {
+pub fn from_seed_slip23(seed: &[u8]) -> Result<XPrv> {
if seed.is_empty() {
return Err(CardanoError::InvalidSeed("seed is empty".to_string()));
}
@@ -57,28 +37,21 @@ pub fn from_seed_slip23(seed: &[u8]) -> R<CardanoHDNode> {
// c := IR (root chain code)
extended_key[64..96].copy_from_slice(ir);
- // Create XPrv using normalize_bytes_force3rd
- let xprv = XPrv::normalize_bytes_force3rd(extended_key);
-
- let hd_node = CardanoHDNode::new(xprv);
-
- Ok(hd_node)
+ Ok(XPrv::normalize_bytes_force3rd(extended_key))
}
-pub fn from_seed_slip23_path(seed: &[u8], path: &str) -> R<CardanoHDNode> {
- let root_node = from_seed_slip23(seed)?;
-
+pub fn from_seed_slip23_path(seed: &[u8], path: &str) -> Result<XPrv> {
+ let mut current_xprv = from_seed_slip23(seed)?;
let components = parse_derivation_path(path)?;
- let mut current_xprv = root_node.xprv;
for component in components {
current_xprv = current_xprv.derive(DerivationScheme::V2, component);
}
- Ok(CardanoHDNode::new(current_xprv))
+ Ok(current_xprv)
}
-fn parse_derivation_path(path: &str) -> R<Vec<u32>> {
+fn parse_derivation_path(path: &str) -> Result<Vec<u32>> {
let mut components = Vec::new();
let path = path.strip_prefix("m/").unwrap_or(path);
@@ -119,7 +92,7 @@ mod tests {
let result = from_seed_slip23(&seed);
assert!(result.is_ok());
- let pubkey = result.unwrap().xprv.public().public_key();
+ let pubkey = result.unwrap().public().public_key();
assert_eq!(pubkey.len(), 32);
assert_eq!(
"83e3ecaf57f90f022c45e10d1b8cb78499c30819515ad9a81ad82139fdb12a90",
@@ -144,11 +117,79 @@ mod tests {
let path = "m/1852'/1815'/0'/0/0";
let result = from_seed_slip23_path(&seed, path);
assert!(result.is_ok());
- let pubkey = result.unwrap().xprv.public().public_key();
+ let pubkey = result.unwrap().public().public_key();
assert_eq!(pubkey.len(), 32);
assert_eq!(
"4510fd55f00653b0dec9153bdc65feba664ccd543a66f5a1438c759a0bc41e1c",
hex::encode(pubkey)
);
}
+
+ #[test]
+ fn test_from_seed_slip23_empty_seed() {
+ let seed = vec![];
+ let result = from_seed_slip23(&seed);
+ assert!(result.is_err());
+ assert!(matches!(result.unwrap_err(), CardanoError::InvalidSeed(_)));
+ }
+
+ #[test]
+ fn test_from_seed_slip23_different_seeds() {
+ let seed1 = hex::decode("578d685d20b602683dc5171df411d3e2").unwrap();
+ let seed2 = hex::decode("00000000000000000000000000000000").unwrap();
+
+ let result1 = from_seed_slip23(&seed1).unwrap();
+ let result2 = from_seed_slip23(&seed2).unwrap();
+
+ assert_ne!(result1.public().public_key(), result2.public().public_key());
+ }
+
+ #[test]
+ fn test_parse_derivation_path_without_prefix() {
+ let path = "1852'/1815'/0'/0/0";
+ let result = parse_derivation_path(path);
+ assert!(result.is_ok());
+ assert_eq!(
+ result.unwrap(),
+ vec![2147485500, 2147485463, 2147483648, 0, 0]
+ );
+ }
+
+ #[test]
+ fn test_parse_derivation_path_mixed_hardened() {
+ let path = "m/1852'/1815/0'/0/0";
+ let result = parse_derivation_path(path);
+ assert!(result.is_ok());
+ let components = result.unwrap();
+ assert_eq!(components[0], 2147485500); // hardened
+ assert_eq!(components[1], 1815); // not hardened
+ assert_eq!(components[2], 2147483648); // hardened
+ }
+
+ #[test]
+ fn test_parse_derivation_path_invalid_component() {
+ let path = "m/1852'/invalid/0'";
+ let result = parse_derivation_path(path);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_parse_derivation_path_empty() {
+ let path = "";
+ let result = parse_derivation_path(path);
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), Vec::<u32>::new());
+ }
+
+ #[test]
+ fn test_from_seed_slip23_path_different_paths() {
+ let seed = hex::decode("578d685d20b602683dc5171df411d3e2").unwrap();
+ let path1 = "m/1852'/1815'/0'/0/0";
+ let path2 = "m/1852'/1815'/0'/0/1";
+
+ let result1 = from_seed_slip23_path(&seed, path1).unwrap();
+ let result2 = from_seed_slip23_path(&seed, path2).unwrap();
+
+ assert_ne!(result1.public().public_key(), result2.public().public_key());
+ }
}
diff --git a/rust/apps/cardano/src/structs.rs b/rust/apps/cardano/src/structs.rs
index b55a99a..e006e76 100644
--- a/rust/apps/cardano/src/structs.rs
+++ b/rust/apps/cardano/src/structs.rs
@@ -1,5 +1,5 @@
use crate::address::derive_pubkey_hash;
-use crate::errors::{CardanoError, R};
+use crate::errors::{CardanoError, Result};
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
@@ -161,13 +161,15 @@ impl_public_struct!(ParsedCardanoMultiAsset {
});
impl ParsedCardanoSignData {
- pub fn build(sign_data: Vec<u8>, derivation_path: String, xpub: String) -> R<Self> {
+ pub fn build(sign_data: Vec<u8>, derivation_path: String, xpub: String) -> Result<Self> {
let sign_structure = CardanoSignStructure::from_cbor(sign_data.clone());
match sign_structure {
Ok(sign_structure) => {
let raw_payload = sign_structure.get_payload();
- let payload = String::from_utf8(hex::decode(raw_payload.clone()).unwrap())
- .unwrap_or_else(|_| raw_payload.clone());
+ let payload = hex::decode(raw_payload.clone())
+ .ok()
+ .and_then(|bytes| String::from_utf8(bytes).ok())
+ .unwrap_or_else(|| raw_payload.clone());
Ok(Self {
payload,
derivation_path,
@@ -191,13 +193,15 @@ impl ParsedCardanoSignCip8Data {
derivation_path: String,
xpub: String,
hash_payload: bool,
- ) -> R<Self> {
+ ) -> Result<Self> {
let sign_structure = CardanoSignStructure::from_cbor(sign_data.clone());
match sign_structure {
Ok(sign_structure) => {
let raw_payload = sign_structure.get_payload();
- let payload = String::from_utf8(hex::decode(raw_payload.clone()).unwrap())
- .unwrap_or_else(|_| raw_payload.clone());
+ let payload = hex::decode(raw_payload.clone())
+ .ok()
+ .and_then(|bytes| String::from_utf8(bytes).ok())
+ .unwrap_or_else(|| raw_payload.clone());
let mut message_hash = hex::encode(raw_payload);
if hash_payload {
let hash = blake2b_224(payload.as_bytes());
@@ -211,7 +215,7 @@ impl ParsedCardanoSignCip8Data {
hash_payload,
})
}
- Err(e) => Ok(Self {
+ Err(_e) => Ok(Self {
payload: hex::encode(sign_data.clone()),
derivation_path,
message_hash: hex::encode(sign_data),
@@ -223,7 +227,7 @@ impl ParsedCardanoSignCip8Data {
}
impl ParsedCardanoTx {
- pub fn from_cardano_tx(tx: Transaction, context: ParseContext) -> R<Self> {
+ pub fn from_cardano_tx(tx: Transaction, context: ParseContext) -> Result<Self> {
let network_id = Self::judge_network_id(&tx);
let network = match network_id {
1 => "Cardano Mainnet".to_string(),
@@ -286,18 +290,31 @@ impl ParsedCardanoTx {
let voters = v.get_voters();
let mut voting_procedures = vec![];
for voter_index in 0..voters.len() {
- let voter = voters.get(voter_index).unwrap();
+ let voter = match voters.get(voter_index) {
+ Some(v) => v,
+ None => continue,
+ };
let actions = v.get_governance_action_ids_by_voter(&voter);
for i in 0..actions.len() {
- let action = actions.get(i).unwrap();
- let procedure = v.get(&voter, &action);
- let vote = match procedure.unwrap().vote_kind() {
+ let action = match actions.get(i) {
+ Some(a) => a,
+ None => continue,
+ };
+ let procedure = match v.get(&voter, &action) {
+ Some(p) => p,
+ None => continue,
+ };
+ let vote = match procedure.vote_kind() {
VoteKind::No => "No".to_string(),
VoteKind::Yes => "Yes".to_string(),
VoteKind::Abstain => "Abstain".to_string(),
};
+ let voter_hash = match voter.to_key_hash() {
+ Some(h) => h.to_string(),
+ None => continue,
+ };
voting_procedures.push(VotingProcedure {
- voter: voter.to_key_hash().unwrap().to_string(),
+ voter: voter_hash,
transaction_id: action.transaction_id().to_string(),
index: action.index().to_string(),
vote,
@@ -357,7 +374,7 @@ impl ParsedCardanoTx {
}
}
- fn parse_auxiliary_data(tx: &Transaction) -> R<Option<String>> {
+ fn parse_auxiliary_data(tx: &Transaction) -> Result<Option<String>> {
tx.auxiliary_data()
.map(|v| {
v.to_json()
@@ -366,7 +383,7 @@ impl ParsedCardanoTx {
.transpose()
}
- fn parse_certificates(tx: &Transaction, network_id: u8) -> R<Vec<CardanoCertificate>> {
+ fn parse_certificates(tx: &Transaction, network_id: u8) -> Result<Vec<CardanoCertificate>> {
let mut certs = vec![];
if let Some(_certs) = tx.body().certs() {
let len = _certs.len();
@@ -442,7 +459,11 @@ impl ParsedCardanoTx {
_cert
.drep()
.to_key_hash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid DRep key hash".to_string(),
+ )
+ })?
.to_bech32("drep")
.map_err(|e| CardanoError::InvalidTransaction(e.to_string()))?,
LABEL_DREP.to_string(),
@@ -451,7 +472,11 @@ impl ParsedCardanoTx {
_cert
.drep()
.to_script_hash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid DRep script hash".to_string(),
+ )
+ })?
.to_bech32("")
.map_err(|e| CardanoError::InvalidTransaction(e.to_string()))?,
LABEL_DREP.to_string(),
@@ -531,12 +556,20 @@ impl ParsedCardanoTx {
CredKind::Key => _cert
.committee_hot_credential()
.to_keyhash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid committee hot key hash".to_string(),
+ )
+ })?
.to_string(),
CredKind::Script => _cert
.committee_hot_credential()
.to_scripthash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid committee hot script hash".to_string(),
+ )
+ })?
.to_string(),
},
},
@@ -546,12 +579,20 @@ impl ParsedCardanoTx {
CredKind::Key => _cert
.committee_cold_credential()
.to_keyhash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid committee cold key hash".to_string(),
+ )
+ })?
.to_string(),
CredKind::Script => _cert
.committee_cold_credential()
.to_scripthash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid committee cold script hash".to_string(),
+ )
+ })?
.to_string(),
},
},
@@ -568,12 +609,20 @@ impl ParsedCardanoTx {
CredKind::Key => _cert
.committee_cold_credential()
.to_keyhash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid committee cold key hash".to_string(),
+ )
+ })?
.to_string(),
CredKind::Script => _cert
.committee_cold_credential()
.to_scripthash()
- .unwrap()
+ .ok_or_else(|| {
+ CardanoError::InvalidTransaction(
+ "Invalid committee cold script hash".to_string(),
+ )
+ })?
.to_string(),
},
}];
@@ -908,7 +957,7 @@ impl ParsedCardanoTx {
Ok(certs)
}
- fn parse_withdrawals(tx: &Transaction) -> R<Vec<CardanoWithdrawal>> {
+ fn parse_withdrawals(tx: &Transaction) -> Result<Vec<CardanoWithdrawal>> {
let mut withdrawals = vec![];
if let Some(_withdrawals) = tx.body().withdrawals() {
let keys = _withdrawals.keys();
@@ -1019,7 +1068,7 @@ impl ParsedCardanoTx {
map
}
- pub fn verify(tx: Transaction, context: ParseContext) -> R<()> {
+ pub fn verify(tx: Transaction, context: ParseContext) -> Result<()> {
let network_id = Self::judge_network_id(&tx);
let parsed_inputs = Self::parse_inputs(&tx, &context, network_id)?;
@@ -1052,7 +1101,7 @@ impl ParsedCardanoTx {
tx: &Transaction,
context: &ParseContext,
_network_id: u8,
- ) -> R<Vec<ParsedCardanoInput>> {
+ ) -> Result<Vec<ParsedCardanoInput>> {
let inputs_len = tx.body().inputs().len();
let mut parsed_inputs: Vec<ParsedCardanoInput> = vec![];
for i in 0..inputs_len {
@@ -1168,7 +1217,7 @@ impl ParsedCardanoTx {
Ok(parsed_inputs)
}
- fn parse_outputs(tx: &Transaction) -> R<Vec<ParsedCardanoOutput>> {
+ fn parse_outputs(tx: &Transaction) -> Result<Vec<ParsedCardanoOutput>> {
let outputs_len = tx.body().outputs().len();
let mut parsed_outputs = vec![];
for i in 0..outputs_len {
@@ -1233,6 +1282,8 @@ fn normalize_value(value: u64) -> String {
#[cfg(test)]
mod tests {
use super::*;
+ use bitcoin::bip32::DerivationPath;
+ use core::str::FromStr;
use ur_registry::cardano::cardano_sign_request::CardanoSignRequest;
#[test]
@@ -1249,6 +1300,41 @@ mod tests {
assert_eq!(result, "1");
}
+ #[test]
+ fn test_normalize_coin_zero() {
+ let value = 0u64;
+ let result = normalize_coin(value);
+ assert_eq!(result, "0 ADA");
+ }
+
+ #[test]
+ fn test_normalize_coin_fractional() {
+ let value = 500_000u64; // 0.5 ADA
+ let result = normalize_coin(value);
+ assert_eq!(result, "0.5 ADA");
+ }
+
+ #[test]
+ fn test_normalize_coin_large() {
+ let value = 1_000_000_000_000u64; // 1,000,000 ADA
+ let result = normalize_coin(value);
+ assert!(result.contains("ADA"));
+ }
+
+ #[test]
+ fn test_normalize_value_zero() {
+ let value = 0u64;
+ let result = normalize_value(value);
+ assert_eq!(result, "0");
+ }
+
+ #[test]
+ fn test_normalize_value_fractional() {
+ let value = 500_000u64; // 0.5
+ let result = normalize_value(value);
+ assert_eq!(result, "0.5");
+ }
+
#[test]
fn test_parse_sign_data() {
let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
@@ -1263,6 +1349,55 @@ mod tests {
assert_eq!(hex::encode(data.get_payload()), "616263313233");
}
+ #[test]
+ fn test_parse_sign_data_invalid_cbor_fallback() {
+ // Invalid CBOR should fallback to hex encoding
+ let invalid_cbor = vec![0xff, 0xff, 0xff]; // Invalid CBOR
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c";
+ let data = ParsedCardanoSignData::build(
+ invalid_cbor.clone(),
+ "m/1852'/1815'/0'/0/0".to_string(),
+ xpub.to_string(),
+ )
+ .unwrap();
+ assert_eq!(data.get_derivation_path(), "m/1852'/1815'/0'/0/0");
+ assert_eq!(data.get_message_hash(), hex::encode(&invalid_cbor));
+ }
+
+ #[test]
+ fn test_parse_sign_cip8_data_without_hash() {
+ let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c";
+ let data = ParsedCardanoSignCip8Data::build(
+ hex::decode(payload).unwrap(),
+ "m/1852'/1815'/0'/0/0".to_string(),
+ xpub.to_string(),
+ false, // hash_payload = false
+ )
+ .unwrap();
+ assert_eq!(data.get_derivation_path(), "m/1852'/1815'/0'/0/0");
+ assert_eq!(data.get_hash_payload(), false);
+ assert!(!data.get_message_hash().is_empty());
+ }
+
+ #[test]
+ fn test_parse_sign_cip8_data_with_hash() {
+ let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c";
+ let data = ParsedCardanoSignCip8Data::build(
+ hex::decode(payload).unwrap(),
+ "m/1852'/1815'/0'/0/0".to_string(),
+ xpub.to_string(),
+ true, // hash_payload = true
+ )
+ .unwrap();
+ assert_eq!(data.get_derivation_path(), "m/1852'/1815'/0'/0/0");
+ assert_eq!(data.get_hash_payload(), true);
+ // When hash_payload is true, message_hash should be blake2b_224 hash of payload
+ assert!(!data.get_message_hash().is_empty());
+ assert_eq!(data.get_message_hash().len(), 56); // blake2b_224 produces 28 bytes = 56 hex chars
+ }
+
#[test]
fn test_parse_sign() {
let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
@@ -1298,4 +1433,304 @@ mod tests {
let cardano_tx = ParsedCardanoTx::from_cardano_tx(tx, context);
assert!(cardano_tx.is_ok());
}
+
+ #[test]
+ fn test_judge_network_id_with_network_id() {
+ // Test with explicit network_id in transaction body
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let tx = Transaction::from_hex(&hex::encode(sign_data)).unwrap();
+ let network_id = ParsedCardanoTx::judge_network_id(&tx);
+ // Should return network_id based on outputs address
+ assert!(network_id == 0 || network_id == 1);
+ }
+
+ #[test]
+ fn test_parse_auxiliary_data_none() {
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let tx = Transaction::from_hex(&hex::encode(sign_data)).unwrap();
+ let aux_data = ParsedCardanoTx::parse_auxiliary_data(&tx);
+ assert!(aux_data.is_ok());
+ // This transaction likely doesn't have auxiliary data
+ assert_eq!(aux_data.unwrap(), None);
+ }
+
+ #[test]
+ fn test_parse_certificates_empty() {
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let tx = Transaction::from_hex(&hex::encode(sign_data)).unwrap();
+ let network_id = ParsedCardanoTx::judge_network_id(&tx);
+ let certs = ParsedCardanoTx::parse_certificates(&tx, network_id);
+ assert!(certs.is_ok());
+ assert_eq!(certs.unwrap().len(), 0);
+ }
+
+ #[test]
+ fn test_parse_withdrawals_empty() {
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let tx = Transaction::from_hex(&hex::encode(sign_data)).unwrap();
+ let withdrawals = ParsedCardanoTx::parse_withdrawals(&tx);
+ assert!(withdrawals.is_ok());
+ assert_eq!(withdrawals.unwrap().len(), 0);
+ }
+
+ #[test]
+ fn test_parse_context_new() {
+ let utxos = vec![];
+ let cert_keys = vec![];
+ let xpub = Some("test_xpub".to_string());
+ let master_fingerprint = vec![0x52, 0x74, 0x47, 0x03];
+ let context = ParseContext::new(
+ utxos.clone(),
+ cert_keys.clone(),
+ xpub.clone(),
+ master_fingerprint.clone(),
+ );
+ assert_eq!(context.get_utxos().len(), 0);
+ assert_eq!(context.get_cert_keys().len(), 0);
+ assert_eq!(context.get_cardano_xpub(), xpub);
+ assert_eq!(context.get_master_fingerprint(), master_fingerprint);
+ }
+
+ #[test]
+ fn test_voting_procedure_new() {
+ let procedure = VotingProcedure {
+ voter: "voter_hash".to_string(),
+ transaction_id: "tx_id".to_string(),
+ index: "0".to_string(),
+ vote: "Yes".to_string(),
+ };
+ assert_eq!(procedure.get_voter(), "voter_hash");
+ assert_eq!(procedure.get_transaction_id(), "tx_id");
+ assert_eq!(procedure.get_index(), "0");
+ assert_eq!(procedure.get_vote(), "Yes");
+ }
+
+ #[test]
+ fn test_voting_proposal_new() {
+ let proposal = VotingProposal {
+ anchor: "anchor_hash".to_string(),
+ };
+ assert_eq!(proposal.get_anchor(), "anchor_hash");
+ }
+
+ #[test]
+ fn test_cardano_certificate_new() {
+ let fields = vec![CertField {
+ label: "Address".to_string(),
+ value: "addr1...".to_string(),
+ }];
+ let cert = CardanoCertificate::new("Stake Pool Delegation".to_string(), fields.clone());
+ assert_eq!(cert.get_cert_type(), "Stake Pool Delegation");
+ assert_eq!(cert.get_fields().len(), 1);
+ }
+
+ #[test]
+ fn test_cardano_withdrawal_new() {
+ let withdrawal = CardanoWithdrawal {
+ address: "addr1...".to_string(),
+ amount: "1 ADA".to_string(),
+ };
+ assert_eq!(withdrawal.get_address(), "addr1...");
+ assert_eq!(withdrawal.get_amount(), "1 ADA");
+ }
+
+ #[test]
+ fn test_cert_field_new() {
+ let field = CertField {
+ label: "Address".to_string(),
+ value: "addr1...".to_string(),
+ };
+ assert_eq!(field.get_label(), "Address");
+ assert_eq!(field.get_value(), "addr1...");
+ }
+
+ #[test]
+ fn test_cardano_utxo_new() {
+ let mfp = vec![0x52, 0x74, 0x47, 0x03];
+ let path = DerivationPath::from_str("m/1852'/1815'/0'/0/0").unwrap();
+ let tx_hash = vec![0x01, 0x02, 0x03];
+ let utxo = CardanoUtxo::new(
+ mfp.clone(),
+ "addr1...".to_string(),
+ path.clone(),
+ 1000000,
+ tx_hash.clone(),
+ 0,
+ );
+ assert_eq!(utxo.get_master_fingerprint(), mfp);
+ assert_eq!(utxo.get_address(), "addr1...");
+ assert_eq!(utxo.get_value(), 1000000);
+ assert_eq!(utxo.get_index(), 0);
+ }
+
+ #[test]
+ fn test_cardano_cert_key_new() {
+ let mfp = vec![0x52, 0x74, 0x47, 0x03];
+ let key_hash = vec![0x01, 0x02, 0x03];
+ let path = DerivationPath::from_str("m/1852'/1815'/0'/2/0").unwrap();
+ let cert_key = CardanoCertKey::new(mfp.clone(), key_hash.clone(), path.clone());
+ assert_eq!(cert_key.get_master_fingerprint(), mfp);
+ assert_eq!(cert_key.get_key_hash(), key_hash);
+ }
+
+ #[test]
+ fn test_sign_data_result_new() {
+ let pub_key = vec![0x01, 0x02, 0x03];
+ let signature = vec![0x04, 0x05, 0x06];
+ let result = SignDataResult::new(pub_key.clone(), signature.clone());
+ assert_eq!(result.get_pub_key(), pub_key);
+ assert_eq!(result.get_signature(), signature);
+ }
+
+ #[test]
+ fn test_sign_voting_registration_result_new() {
+ let signature = vec![0x01, 0x02, 0x03];
+ let result = SignVotingRegistrationResult::new(signature.clone());
+ assert_eq!(result.get_signature(), signature);
+ }
+
+ #[test]
+ fn test_cardano_from_new() {
+ let from = CardanoFrom {
+ address: "addr1...".to_string(),
+ amount: "1 ADA".to_string(),
+ path: Some("m/1852'/1815'/0'/0/0".to_string()),
+ value: 1000000,
+ };
+ assert_eq!(from.get_address(), "addr1...");
+ assert_eq!(from.get_amount(), "1 ADA");
+ assert_eq!(from.get_path(), Some("m/1852'/1815'/0'/0/0".to_string()));
+ assert_eq!(from.get_value(), 1000000);
+ }
+
+ #[test]
+ fn test_cardano_to_new() {
+ let to = CardanoTo {
+ address: "addr1...".to_string(),
+ amount: "2 ADA".to_string(),
+ assets: BTreeMap::new(),
+ assets_text: Some("assets".to_string()),
+ value: 2000000,
+ };
+ assert_eq!(to.get_address(), "addr1...");
+ assert_eq!(to.get_amount(), "2 ADA");
+ assert_eq!(to.get_value(), 2000000);
+ }
+
+ #[test]
+ fn test_cardano_certificate_multiple_fields() {
+ let fields = vec![
+ CertField {
+ label: "Address".to_string(),
+ value: "addr1...".to_string(),
+ },
+ CertField {
+ label: "Pool".to_string(),
+ value: "pool1...".to_string(),
+ },
+ ];
+ let cert = CardanoCertificate::new("Stake Pool Delegation".to_string(), fields.clone());
+ assert_eq!(cert.get_cert_type(), "Stake Pool Delegation");
+ assert_eq!(cert.get_fields().len(), 2);
+ }
+
+ #[test]
+ fn test_parse_context_with_data() {
+ let utxo = CardanoUtxo::new(
+ vec![0x52, 0x74, 0x47, 0x03],
+ "addr1...".to_string(),
+ DerivationPath::from_str("m/1852'/1815'/0'/0/0").unwrap(),
+ 1000000,
+ vec![0x01],
+ 0,
+ );
+ let utxos = vec![utxo];
+ let cert_key = CardanoCertKey::new(
+ vec![0x52, 0x74, 0x47, 0x03],
+ vec![0x01],
+ DerivationPath::from_str("m/1852'/1815'/0'/2/0").unwrap(),
+ );
+ let cert_keys = vec![cert_key];
+ let xpub = Some("test_xpub".to_string());
+ let master_fingerprint = vec![0x52, 0x74, 0x47, 0x03];
+ let context = ParseContext::new(
+ utxos.clone(),
+ cert_keys.clone(),
+ xpub.clone(),
+ master_fingerprint.clone(),
+ );
+ assert_eq!(context.get_utxos().len(), 1);
+ assert_eq!(context.get_cert_keys().len(), 1);
+ }
+
+ #[test]
+ fn test_normalize_coin_small_fractional() {
+ let value = 1u64; // 0.000001 ADA
+ let result = normalize_coin(value);
+ assert!(result.contains("ADA"));
+ }
+
+ #[test]
+ fn test_normalize_value_small_fractional() {
+ let value = 1u64; // 0.000001
+ let result = normalize_value(value);
+ assert!(!result.is_empty());
+ }
+
+ #[test]
+ fn test_parse_sign_cip8_data_invalid_cbor_fallback() {
+ let invalid_cbor = vec![0xff, 0xff, 0xff];
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c";
+ let data = ParsedCardanoSignCip8Data::build(
+ invalid_cbor.clone(),
+ "m/1852'/1815'/0'/0/0".to_string(),
+ xpub.to_string(),
+ false,
+ )
+ .unwrap();
+ assert_eq!(data.get_derivation_path(), "m/1852'/1815'/0'/0/0");
+ assert_eq!(data.get_hash_payload(), false);
+ }
+
+ #[test]
+ fn test_voting_procedure_different_votes() {
+ let yes_vote = VotingProcedure {
+ voter: "voter".to_string(),
+ transaction_id: "tx".to_string(),
+ index: "0".to_string(),
+ vote: "Yes".to_string(),
+ };
+ let no_vote = VotingProcedure {
+ voter: "voter".to_string(),
+ transaction_id: "tx".to_string(),
+ index: "0".to_string(),
+ vote: "No".to_string(),
+ };
+ assert_ne!(yes_vote.get_vote(), no_vote.get_vote());
+ }
+
+ #[test]
+ fn test_cardano_withdrawal_different_amounts() {
+ let w1 = CardanoWithdrawal {
+ address: "addr1...".to_string(),
+ amount: "1 ADA".to_string(),
+ };
+ let w2 = CardanoWithdrawal {
+ address: "addr1...".to_string(),
+ amount: "2 ADA".to_string(),
+ };
+ assert_eq!(w1.get_address(), w2.get_address());
+ assert_ne!(w1.get_amount(), w2.get_amount());
+ }
+
+ #[test]
+ fn test_cardano_from_without_path() {
+ let from = CardanoFrom {
+ address: "addr1...".to_string(),
+ amount: "1 ADA".to_string(),
+ path: None,
+ value: 1000000,
+ };
+ assert_eq!(from.get_path(), None);
+ }
}
diff --git a/rust/apps/cardano/src/transaction.rs b/rust/apps/cardano/src/transaction.rs
index 82e5939..ecefcc7 100644
--- a/rust/apps/cardano/src/transaction.rs
+++ b/rust/apps/cardano/src/transaction.rs
@@ -1,6 +1,6 @@
use crate::structs::{ParseContext, ParsedCardanoSignData, ParsedCardanoTx, SignDataResult};
use crate::{
- errors::{CardanoError, R},
+ errors::{CardanoError, Result},
structs::ParsedCardanoSignCip8Data,
};
use alloc::collections::BTreeMap;
@@ -13,7 +13,7 @@ use ed25519_bip32_core::XPrv;
use hex;
use ur_registry::crypto_key_path::CryptoKeyPath;
-pub fn parse_tx(tx: Vec<u8>, context: ParseContext) -> R<ParsedCardanoTx> {
+pub fn parse_tx(tx: Vec<u8>, context: ParseContext) -> Result<ParsedCardanoTx> {
let cardano_tx = cardano_serialization_lib::protocol_types::FixedTransaction::from_bytes(tx)?;
ParsedCardanoTx::from_cardano_tx(cardano_tx, context)
}
@@ -22,7 +22,7 @@ pub fn parse_sign_data(
sign_data: Vec<u8>,
derviation_path: String,
xpub: String,
-) -> R<ParsedCardanoSignData> {
+) -> Result<ParsedCardanoSignData> {
ParsedCardanoSignData::build(sign_data, derviation_path, xpub)
}
@@ -31,31 +31,32 @@ pub fn parse_sign_cip8_data(
derviation_path: String,
xpub: String,
hash_payload: bool,
-) -> R<ParsedCardanoSignCip8Data> {
+) -> Result<ParsedCardanoSignCip8Data> {
ParsedCardanoSignCip8Data::build(sign_data, derviation_path, xpub, hash_payload)
}
-pub fn check_tx(tx: Vec<u8>, context: ParseContext) -> R<()> {
+pub fn check_tx(tx: Vec<u8>, context: ParseContext) -> Result<()> {
let cardano_tx = cardano_serialization_lib::protocol_types::FixedTransaction::from_bytes(tx)?;
ParsedCardanoTx::verify(cardano_tx, context)
}
-pub fn calc_icarus_master_key(entropy: &[u8], passphrase: &[u8]) -> XPrv {
+pub fn calc_icarus_master_key(entropy: &[u8], passphrase: &[u8]) -> Result<XPrv> {
keystore::algorithms::ed25519::bip32_ed25519::get_icarus_master_key_by_entropy(
entropy, passphrase,
)
.map_err(|e| CardanoError::SigningFailed(e.to_string()))
- .unwrap()
}
-pub fn sign_data(path: &String, payload: &str, icarus_master_key: XPrv) -> R<SignDataResult> {
+pub fn sign_data(path: &String, payload: &str, icarus_master_key: XPrv) -> Result<SignDataResult> {
let bip32_signing_key =
keystore::algorithms::ed25519::bip32_ed25519::derive_extended_privkey_by_xprv(
&icarus_master_key,
path,
)
- .unwrap();
- let signed_data = bip32_signing_key.sign::<Vec<u8>>(&hex::decode(payload).unwrap());
+ .map_err(|e| CardanoError::SigningFailed(e.to_string()))?;
+ let signed_data = bip32_signing_key.sign::<Vec<u8>>(
+ &hex::decode(payload).map_err(|e| CardanoError::InvalidTransaction(e.to_string()))?,
+ );
let pub_key = bip32_signing_key.public().public_key().to_vec();
Ok(SignDataResult::new(
pub_key,
@@ -67,14 +68,17 @@ pub fn sign_tx_hash(
tx_hash: &String,
paths: &Vec<CryptoKeyPath>,
icarus_master_key: XPrv,
-) -> R<Vec<u8>> {
- let tx_hash = hex::decode(tx_hash).unwrap();
+) -> Result<Vec<u8>> {
+ let tx_hash =
+ hex::decode(tx_hash).map_err(|e| CardanoError::InvalidTransaction(e.to_string()))?;
let mut witness_set = cardano_serialization_lib::TransactionWitnessSet::new();
let mut vkeys = cardano_serialization_lib::Vkeywitnesses::new();
for path in paths {
match keystore::algorithms::ed25519::bip32_ed25519::derive_extended_pubkey_by_xprv(
&icarus_master_key,
- &path.get_path().unwrap(),
+ &path
+ .get_path()
+ .ok_or(CardanoError::DerivationError("Path is None".to_string()))?,
)
.map(|v| v.public_key())
.map_err(|e| CardanoError::SigningFailed(e.to_string()))
@@ -83,12 +87,17 @@ pub fn sign_tx_hash(
let signature = keystore::algorithms::ed25519::bip32_ed25519::sign_message_by_xprv(
&icarus_master_key,
&tx_hash,
- &path.get_path().unwrap(),
+ &path
+ .get_path()
+ .ok_or(CardanoError::DerivationError("Path is None".to_string()))?,
)
.map_err(|e| CardanoError::SigningFailed(e.to_string()))?;
// construct vkeywitness
vkeys.add(&Vkeywitness::new(
- Vkey::new(&PublicKey::from_bytes(&pubkey).unwrap()),
+ Vkey::new(
+ &PublicKey::from_bytes(&pubkey)
+ .map_err(|e| CardanoError::SigningFailed(e.to_string()))?,
+ ),
Ed25519Signature::from_bytes(signature.to_vec())
.map_err(|e| CardanoError::SigningFailed(e.to_string()))?,
));
@@ -100,7 +109,7 @@ pub fn sign_tx_hash(
Ok(witness_set.to_bytes())
}
-pub fn sign_tx(tx: Vec<u8>, context: ParseContext, icarus_master_key: XPrv) -> R<Vec<u8>> {
+pub fn sign_tx(tx: Vec<u8>, context: ParseContext, icarus_master_key: XPrv) -> Result<Vec<u8>> {
let cardano_tx = cardano_serialization_lib::protocol_types::FixedTransaction::from_bytes(tx)?;
let hash = blake2b_256(cardano_tx.raw_body().as_ref());
let mut witness_set = cardano_serialization_lib::TransactionWitnessSet::new();
@@ -231,7 +240,7 @@ mod test {
fn test_sign_data() {
let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
let passphrase = b"";
- let icarus_master_key = calc_icarus_master_key(&entropy, passphrase);
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
let path = "m/1852'/1815'/0'/0/0".to_string();
let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
let sign_data_result = sign_data(&path, payload, icarus_master_key).unwrap();
@@ -243,4 +252,228 @@ mod test {
"2ae9d64b6a954febcc848afaa6ca1e9c49559e23fe68d085631ea2a020b695ff"
);
}
+
+ #[test]
+ fn test_parse_sign_data() {
+ let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
+ let derivation_path = "m/1852'/1815'/0'/0/0".to_string();
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c".to_string();
+ let result = parse_sign_data(
+ hex::decode(payload).unwrap(),
+ derivation_path.clone(),
+ xpub.clone(),
+ );
+ assert!(result.is_ok());
+ let data = result.unwrap();
+ assert_eq!(data.get_derivation_path(), derivation_path);
+ }
+
+ #[test]
+ fn test_parse_sign_cip8_data() {
+ let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
+ let derivation_path = "m/1852'/1815'/0'/0/0".to_string();
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c".to_string();
+
+ // Test with hash_payload = false
+ let result = parse_sign_cip8_data(
+ hex::decode(payload).unwrap(),
+ derivation_path.clone(),
+ xpub.clone(),
+ false,
+ );
+ assert!(result.is_ok());
+ let data = result.unwrap();
+ assert_eq!(data.get_derivation_path(), derivation_path);
+ assert_eq!(data.get_hash_payload(), false);
+
+ // Test with hash_payload = true
+ let result =
+ parse_sign_cip8_data(hex::decode(payload).unwrap(), derivation_path, xpub, true);
+ assert!(result.is_ok());
+ let data = result.unwrap();
+ assert_eq!(data.get_hash_payload(), true);
+ }
+
+ #[test]
+ fn test_calc_icarus_master_key() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let master_key = calc_icarus_master_key(&entropy, passphrase);
+ assert!(master_key.is_ok());
+ }
+
+ #[test]
+ fn test_calc_icarus_master_key_with_passphrase() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"test_passphrase";
+ let master_key = calc_icarus_master_key(&entropy, passphrase);
+ assert!(master_key.is_ok());
+ }
+
+ #[test]
+ fn test_parse_tx() {
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let xpub = hex::encode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c");
+ let master_fingerprint = hex::decode("52744703").unwrap();
+ let context = ParseContext::new(vec![], vec![], Some(xpub), master_fingerprint);
+ let result = parse_tx(sign_data, context);
+ assert!(result.is_ok());
+ let parsed_tx = result.unwrap();
+ assert!(!parsed_tx.get_fee().is_empty());
+ }
+
+ #[test]
+ fn test_parse_tx_invalid_bytes() {
+ let invalid_tx = vec![0xff, 0xff, 0xff];
+ let xpub = hex::encode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c");
+ let master_fingerprint = hex::decode("52744703").unwrap();
+ let context = ParseContext::new(vec![], vec![], Some(xpub), master_fingerprint);
+ let result = parse_tx(invalid_tx, context);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_check_tx() {
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let xpub = hex::encode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c");
+ let master_fingerprint = hex::decode("52744703").unwrap();
+ let context = ParseContext::new(vec![], vec![], Some(xpub), master_fingerprint);
+ let result = check_tx(sign_data, context);
+ // May succeed or fail depending on transaction validation
+ // Just verify it doesn't panic
+ let _ = result;
+ }
+
+ #[test]
+ fn test_check_tx_invalid_bytes() {
+ let invalid_tx = vec![0xff, 0xff, 0xff];
+ let xpub = hex::encode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c");
+ let master_fingerprint = hex::decode("52744703").unwrap();
+ let context = ParseContext::new(vec![], vec![], Some(xpub), master_fingerprint);
+ let result = check_tx(invalid_tx, context);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_sign_data_invalid_hex() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let path = "m/1852'/1815'/0'/0/0".to_string();
+ let invalid_payload = "invalid_hex";
+ let result = sign_data(&path, invalid_payload, icarus_master_key);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_sign_data_different_paths() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let payload = "846a5369676e6174757265315882a301270458390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad676164647265737358390069fa1bd9338574702283d8fb71f8cce1831c3ea4854563f5e4043aea33a4f1f468454744b2ff3644b2ab79d48e76a3187f902fe8a1bcfaad4043abc123";
+ let path1 = "m/1852'/1815'/0'/0/0".to_string();
+ let path2 = "m/1852'/1815'/0'/0/1".to_string();
+ let sig1 = sign_data(&path1, payload, icarus_master_key.clone()).unwrap();
+ let sig2 = sign_data(&path2, payload, icarus_master_key).unwrap();
+ assert_ne!(sig1.get_signature(), sig2.get_signature());
+ assert_ne!(sig1.get_pub_key(), sig2.get_pub_key());
+ }
+
+ #[test]
+ fn test_sign_tx_hash_empty_paths() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let tx_hash = hex::encode([0u8; 32]);
+ let paths = vec![];
+ let result = sign_tx_hash(&tx_hash, &paths, icarus_master_key);
+ assert!(result.is_ok());
+ let witness_bytes = result.unwrap();
+ assert!(!witness_bytes.is_empty());
+ }
+
+ #[test]
+ fn test_sign_tx_hash_with_path() {
+ use ur_registry::crypto_key_path::{CryptoKeyPath, PathComponent};
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let tx_hash = hex::encode([0u8; 32]);
+ let path_component = PathComponent::new(Some(0), false).unwrap();
+ let source_fingerprint: [u8; 4] = [18, 52, 86, 120];
+ let crypto_key_path =
+ CryptoKeyPath::new(vec![path_component], Some(source_fingerprint), None);
+ let paths = vec![crypto_key_path];
+ let result = sign_tx_hash(&tx_hash, &paths, icarus_master_key);
+ assert!(result.is_ok());
+ let witness_bytes = result.unwrap();
+ assert!(!witness_bytes.is_empty());
+ }
+
+ #[test]
+ fn test_sign_tx_hash_path_none() {
+ use ur_registry::crypto_key_path::CryptoKeyPath;
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let tx_hash = hex::encode([0u8; 32]);
+ let source_fingerprint: [u8; 4] = [18, 52, 86, 120];
+ let crypto_key_path = CryptoKeyPath::new(vec![], Some(source_fingerprint), None);
+ let paths = vec![crypto_key_path];
+ let result = sign_tx_hash(&tx_hash, &paths, icarus_master_key);
+ assert!(result.is_err());
+ assert!(matches!(
+ result.unwrap_err(),
+ CardanoError::DerivationError(_)
+ ));
+ }
+
+ #[test]
+ fn test_sign_tx_hash_invalid_hex() {
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let invalid_hash = "invalid_hex".to_string();
+ let paths = vec![];
+ let result = sign_tx_hash(&invalid_hash, &paths, icarus_master_key);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_sign_tx_empty_context() {
+ let sign_data = hex::decode("84a400828258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99038258204e3a6e7fdcb0d0efa17bf79c13aed2b4cb9baf37fb1aa2e39553d5bd720c5c99040182a200581d6179df4c75f7616d7d1fd39cbc1a6ea6b40a0d7b89fea62fc0909b6c370119c350a200581d61c9b0c9761fd1dc0404abd55efc895026628b5035ac623c614fbad0310119c35002198ecb0300a0f5f6").unwrap();
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let xpub = hex::encode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c");
+ let master_fingerprint = hex::decode("52744703").unwrap();
+ let context = ParseContext::new(vec![], vec![], Some(xpub), master_fingerprint);
+ let result = sign_tx(sign_data, context, icarus_master_key);
+ assert!(result.is_ok());
+ let witness_bytes = result.unwrap();
+ assert!(!witness_bytes.is_empty());
+ }
+
+ #[test]
+ fn test_sign_tx_invalid_bytes() {
+ let invalid_tx = vec![0xff, 0xff, 0xff];
+ let entropy = hex::decode("7a4362fd9792e60d97ee258f43fd21af").unwrap();
+ let passphrase = b"";
+ let icarus_master_key = calc_icarus_master_key(&entropy, passphrase).unwrap();
+ let xpub = hex::encode("ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c");
+ let master_fingerprint = hex::decode("52744703").unwrap();
+ let context = ParseContext::new(vec![], vec![], Some(xpub), master_fingerprint);
+ let result = sign_tx(invalid_tx, context, icarus_master_key);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_parse_sign_data_invalid_hex() {
+ let invalid_payload = vec![0xff, 0xff, 0xff];
+ let derivation_path = "m/1852'/1815'/0'/0/0".to_string();
+ let xpub = "ca0e65d9bb8d0dca5e88adc5e1c644cc7d62e5a139350330281ed7e3a6938d2c".to_string();
+ let result = parse_sign_data(invalid_payload, derivation_path, xpub);
+ // Should handle invalid CBOR gracefully
+ assert!(result.is_ok());
+ }
}
diff --git a/rust/rust_c/src/cardano/mod.rs b/rust/rust_c/src/cardano/mod.rs
index 6776c7a..7f09f52 100644
--- a/rust/rust_c/src/cardano/mod.rs
+++ b/rust/rust_c/src/cardano/mod.rs
@@ -26,7 +26,7 @@ use crate::common::{
errors::{RustCError, R},
ur::FRAGMENT_UNLIMITED_LENGTH,
};
-use crate::{extract_array, extract_ptr_with_type};
+use crate::{extract_array, extract_array_mut, extract_ptr_with_type};
use structs::{DisplayCardanoCatalyst, DisplayCardanoSignData, DisplayCardanoTx};
use ur_registry::cardano::cardano_sign_data_signature::CardanoSignDataSignature;
use ur_registry::cardano::cardano_sign_request::CardanoSignRequest;
@@ -48,6 +48,8 @@ use ur_registry::{
cardano::cardano_sign_data_request::CardanoSignDataRequest,
registry_types::CARDANO_SIGN_CIP8_DATA_SIGNATURE,
};
+use zeroize::Zeroize;
+
pub mod cip8_cbor_data_ledger;
pub mod address;
@@ -69,13 +71,16 @@ pub unsafe extern "C" fn cardano_check_catalyst(
let cardano_catalyst_request =
extract_ptr_with_type!(ptr, CardanoCatalystVotingRegistrationRequest);
let mfp = extract_array!(master_fingerprint, u8, 4);
- let ur_mfp = cardano_catalyst_request
+ let ur_mfp = match cardano_catalyst_request
.get_derivation_path()
.get_source_fingerprint()
- .ok_or(RustCError::InvalidMasterFingerprint);
+ {
+ Some(fp) => fp,
+ None => return TransactionCheckResult::from(RustCError::InvalidMasterFingerprint).c_ptr(),
+ };
if let Ok(mfp) = mfp.try_into() as Result<[u8; 4], _> {
- if hex::encode(mfp) != hex::encode(ur_mfp.unwrap()) {
+ if mfp != ur_mfp {
return TransactionCheckResult::from(RustCError::MasterFingerprintMismatch).c_ptr();
}
}
@@ -120,10 +125,15 @@ pub unsafe extern "C" fn cardano_get_catalyst_root_index(
extract_ptr_with_type!(ptr, CardanoCatalystVotingRegistrationRequest);
let derviation_path: CryptoKeyPath = cardano_catalyst_request.get_derivation_path();
match derviation_path.get_components().get(2) {
- Some(_data) => {
- let index = _data.get_index().unwrap();
- SimpleResponse::success(convert_c_char(index.to_string())).simple_c_ptr()
- }
+ Some(_data) => match _data.get_index() {
+ Some(index) => {
+ SimpleResponse::success(convert_c_char(index.to_string())).simple_c_ptr()
+ }
+ None => SimpleResponse::from(CardanoError::InvalidTransaction(
+ "invalid path index".to_string(),
+ ))
+ .simple_c_ptr(),
+ },
None => SimpleResponse::from(CardanoError::InvalidTransaction("invalid path".to_string()))
.simple_c_ptr(),
}
@@ -136,10 +146,15 @@ pub unsafe extern "C" fn cardano_get_sign_data_root_index(
let cardano_sign_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignDataRequest);
let derviation_path: CryptoKeyPath = cardano_sign_data_reqeust.get_derivation_path();
match derviation_path.get_components().get(2) {
- Some(_data) => {
- let index = _data.get_index().unwrap();
- SimpleResponse::success(convert_c_char(index.to_string())).simple_c_ptr()
- }
+ Some(_data) => match _data.get_index() {
+ Some(index) => {
+ SimpleResponse::success(convert_c_char(index.to_string())).simple_c_ptr()
+ }
+ None => SimpleResponse::from(CardanoError::InvalidTransaction(
+ "invalid path index".to_string(),
+ ))
+ .simple_c_ptr(),
+ },
None => SimpleResponse::from(CardanoError::InvalidTransaction("invalid path".to_string()))
.simple_c_ptr(),
}
@@ -152,10 +167,15 @@ pub unsafe extern "C" fn cardano_get_sign_cip8_data_root_index(
let cardano_sign_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignCip8DataRequest);
let derviation_path: CryptoKeyPath = cardano_sign_data_reqeust.get_derivation_path();
match derviation_path.get_components().get(2) {
- Some(_data) => {
- let index = _data.get_index().unwrap();
- SimpleResponse::success(convert_c_char(index.to_string())).simple_c_ptr()
- }
+ Some(_data) => match _data.get_index() {
+ Some(index) => {
+ SimpleResponse::success(convert_c_char(index.to_string())).simple_c_ptr()
+ }
+ None => SimpleResponse::from(CardanoError::InvalidTransaction(
+ "invalid path index".to_string(),
+ ))
+ .simple_c_ptr(),
+ },
None => SimpleResponse::from(CardanoError::InvalidTransaction("invalid path".to_string()))
.simple_c_ptr(),
}
@@ -213,13 +233,16 @@ pub unsafe extern "C" fn cardano_check_sign_data(
) -> PtrT<TransactionCheckResult> {
let cardano_sign_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignDataRequest);
let mfp = extract_array!(master_fingerprint, u8, 4);
- let ur_mfp = cardano_sign_data_reqeust
+ let ur_mfp = match cardano_sign_data_reqeust
.get_derivation_path()
.get_source_fingerprint()
- .ok_or(RustCError::InvalidMasterFingerprint);
+ {
+ Some(fp) => fp,
+ None => return TransactionCheckResult::from(RustCError::InvalidMasterFingerprint).c_ptr(),
+ };
if let Ok(mfp) = mfp.try_into() as Result<[u8; 4], _> {
- if hex::encode(mfp) != hex::encode(ur_mfp.unwrap()) {
+ if mfp != ur_mfp {
return TransactionCheckResult::from(RustCError::MasterFingerprintMismatch).c_ptr();
}
}
@@ -234,13 +257,16 @@ pub unsafe extern "C" fn cardano_check_sign_cip8_data(
) -> PtrT<TransactionCheckResult> {
let cardano_sign_cip8_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignCip8DataRequest);
let mfp = extract_array!(master_fingerprint, u8, 4);
- let ur_mfp = cardano_sign_cip8_data_reqeust
+ let ur_mfp = match cardano_sign_cip8_data_reqeust
.get_derivation_path()
.get_source_fingerprint()
- .ok_or(RustCError::InvalidMasterFingerprint);
+ {
+ Some(fp) => fp,
+ None => return TransactionCheckResult::from(RustCError::InvalidMasterFingerprint).c_ptr(),
+ };
if let Ok(mfp) = mfp.try_into() as Result<[u8; 4], _> {
- if hex::encode(mfp) != hex::encode(ur_mfp.unwrap()) {
+ if mfp != ur_mfp {
return TransactionCheckResult::from(RustCError::MasterFingerprintMismatch).c_ptr();
}
}
@@ -326,7 +352,7 @@ pub unsafe extern "C" fn cardano_parse_sign_tx_hash(
.iter()
.map(|v| v.get_path())
.collect::<Option<Vec<String>>>()
- .unwrap_or(vec![]);
+ .unwrap_or_default();
let address_list = sign_hash_request.get_address_list();
let network = "Cardano".to_string();
let result = DisplayCardanoSignTxHash::new(network, paths, message, address_list);
@@ -368,9 +394,12 @@ fn generate_master_key(
is_slip39: bool,
) -> Result<XPrv, CardanoError> {
if is_slip39 {
- app_cardano::slip23::from_seed_slip23(entropy).map(|v| v.xprv)
+ app_cardano::slip23::from_seed_slip23(entropy)
} else {
- Ok(calc_icarus_master_key(entropy, passphrase.as_bytes()))
+ match calc_icarus_master_key(entropy, passphrase.as_bytes()) {
+ Ok(v) => Ok(v),
+ Err(e) => Err(e),
+ }
}
}
@@ -395,10 +424,15 @@ pub unsafe extern "C" fn cardano_parse_sign_data(
) -> PtrT<TransactionParseResult<DisplayCardanoSignData>> {
let cardano_sign_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignDataRequest);
let sign_data = cardano_sign_data_reqeust.get_sign_data();
- let derviation_path = cardano_sign_data_reqeust
- .get_derivation_path()
- .get_path()
- .unwrap();
+ let derviation_path = match cardano_sign_data_reqeust.get_derivation_path().get_path() {
+ Some(path) => path,
+ None => {
+ return TransactionParseResult::from(CardanoError::InvalidTransaction(
+ "Invalid derivation path".to_string(),
+ ))
+ .c_ptr()
+ }
+ };
let xpub = cardano_sign_data_reqeust.get_xpub();
let parsed_data =
app_cardano::transaction::parse_sign_data(sign_data, derviation_path, hex::encode(xpub));
@@ -414,10 +448,18 @@ pub unsafe extern "C" fn cardano_parse_sign_cip8_data(
) -> PtrT<TransactionParseResult<DisplayCardanoSignData>> {
let cardano_sign_cip8_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignCip8DataRequest);
let sign_data = cardano_sign_cip8_data_reqeust.get_sign_data();
- let derviation_path = cardano_sign_cip8_data_reqeust
+ let derviation_path = match cardano_sign_cip8_data_reqeust
.get_derivation_path()
.get_path()
- .unwrap();
+ {
+ Some(path) => path,
+ None => {
+ return TransactionParseResult::from(CardanoError::InvalidTransaction(
+ "Invalid derivation path".to_string(),
+ ))
+ .c_ptr()
+ }
+ };
let xpub = cardano_sign_cip8_data_reqeust.get_xpub();
let parsed_data = app_cardano::transaction::parse_sign_cip8_data(
sign_data,
@@ -489,12 +531,13 @@ pub unsafe extern "C" fn cardano_sign_catalyst(
passphrase: PtrString,
is_slip39: bool,
) -> PtrT<UREncodeResult> {
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy: &mut [u8] = extract_array_mut!(entropy, u8, entropy_len as usize);
let passphrase = recover_c_char(passphrase);
let master_key = match generate_master_key(entropy, &passphrase, is_slip39) {
Ok(v) => v,
Err(e) => return UREncodeResult::from(e).c_ptr(),
};
+ entropy.zeroize();
cardano_sign_catalyst_by_icarus(ptr, master_key)
}
@@ -505,11 +548,18 @@ unsafe fn cardano_sign_catalyst_by_icarus(
let cardano_catalyst_request =
extract_ptr_with_type!(ptr, CardanoCatalystVotingRegistrationRequest);
+ let path = match cardano_catalyst_request.get_derivation_path().get_path() {
+ Some(p) => p,
+ None => {
+ return UREncodeResult::from(CardanoError::InvalidTransaction(
+ "Invalid derivation path".to_string(),
+ ))
+ .c_ptr()
+ }
+ };
+
governance::sign(
- &cardano_catalyst_request
- .get_derivation_path()
- .get_path()
- .unwrap(),
+ &path,
cardano_catalyst_request.get_delegations(),
&cardano_catalyst_request.get_stake_pub(),
&cardano_catalyst_request.get_payment_address(),
@@ -587,12 +637,13 @@ pub unsafe extern "C" fn cardano_sign_sign_data(
passphrase: PtrString,
is_slip39: bool,
) -> PtrT<UREncodeResult> {
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy = extract_array_mut!(entropy, u8, entropy_len as usize);
let passphrase = recover_c_char(passphrase);
let master_key = match generate_master_key(entropy, &passphrase, is_slip39) {
Ok(v) => v,
Err(e) => return UREncodeResult::from(e).c_ptr(),
};
+ entropy.zeroize();
cardano_sign_sign_data_by_icarus(ptr, master_key)
}
@@ -604,11 +655,18 @@ unsafe fn cardano_sign_sign_data_by_icarus(
let cardano_sign_data_reqeust = extract_ptr_with_type!(ptr, CardanoSignDataRequest);
let sign_data = cardano_sign_data_reqeust.get_sign_data();
+ let path = match cardano_sign_data_reqeust.get_derivation_path().get_path() {
+ Some(p) => p,
+ None => {
+ return UREncodeResult::from(CardanoError::InvalidTransaction(
+ "Invalid derivation path".to_string(),
+ ))
+ .c_ptr()
+ }
+ };
+
let result = app_cardano::transaction::sign_data(
- &cardano_sign_data_reqeust
- .get_derivation_path()
- .get_path()
- .unwrap(),
+ &path,
hex::encode(sign_data).as_str(),
icarus_master_key,
)
@@ -648,7 +706,7 @@ pub unsafe extern "C" fn cardano_sign_sign_cip8_data(
passphrase: PtrString,
is_slip39: bool,
) -> PtrT<UREncodeResult> {
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy = extract_array_mut!(entropy, u8, entropy_len as usize);
let passphrase = recover_c_char(passphrase);
let master_key = match generate_master_key(entropy, &passphrase, is_slip39) {
Ok(v) => v,
@@ -721,13 +779,13 @@ pub unsafe extern "C" fn cardano_sign_tx(
enable_blind_sign: bool,
is_slip39: bool,
) -> PtrT<UREncodeResult> {
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy = extract_array_mut!(entropy, u8, entropy_len as usize);
let passphrase = recover_c_char(passphrase);
let master_key = match generate_master_key(entropy, &passphrase, is_slip39) {
Ok(v) => v,
Err(e) => return UREncodeResult::from(e).c_ptr(),
};
-
+ entropy.zeroize();
if enable_blind_sign {
cardano_sign_tx_hash_by_icarus(ptr, master_key)
} else {
@@ -762,12 +820,13 @@ pub unsafe extern "C" fn cardano_sign_tx_unlimited(
passphrase: PtrString,
is_slip39: bool,
) -> PtrT<UREncodeResult> {
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy = extract_array_mut!(entropy, u8, entropy_len as usize);
let passphrase = recover_c_char(passphrase);
let master_key = match generate_master_key(entropy, &passphrase, is_slip39) {
Ok(v) => v,
Err(e) => return UREncodeResult::from(e).c_ptr(),
};
+ entropy.zeroize();
cardano_sign_tx_by_icarus_unlimited(ptr, master_fingerprint, cardano_xpub, master_key)
}
@@ -783,12 +842,13 @@ pub unsafe extern "C" fn cardano_get_pubkey_by_slip23(
))
.simple_c_ptr();
}
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy = extract_array_mut!(entropy, u8, entropy_len as usize);
let path = recover_c_char(path).to_lowercase();
let xpub = app_cardano::slip23::from_seed_slip23_path(entropy, path.as_str());
+ entropy.zeroize();
match xpub {
Ok(xpub) => {
- SimpleResponse::success(convert_c_char(xpub.xprv.public().to_string())).simple_c_ptr()
+ SimpleResponse::success(convert_c_char(xpub.public().to_string())).simple_c_ptr()
}
Err(e) => SimpleResponse::from(e).simple_c_ptr(),
}
@@ -870,33 +930,52 @@ unsafe fn cardano_sign_sign_cip8_data_by_icarus(
if cardano_sign_data_reqeust.get_hash_payload() {
sign_data = blake2b_224(&sign_data).to_vec();
}
- let mut address_field = vec![];
- let address_type = cardano_sign_data_reqeust.get_address_type();
- if address_type.as_str() == "ADDRESS" {
- address_field = bitcoin::bech32::decode(
- cardano_sign_data_reqeust
- .get_address_bench32()
- .unwrap()
- .as_str(),
- )
- .unwrap()
- .1;
- } else {
- let public_key = cardano_sign_data_reqeust.get_xpub();
- address_field = blake2b_224(&public_key).to_vec();
- }
+ let address_field = {
+ let address_type = cardano_sign_data_reqeust.get_address_type();
+ if address_type.as_str() == "ADDRESS" {
+ let bech = match cardano_sign_data_reqeust.get_address_bench32() {
+ Some(s) => s,
+ None => {
+ return UREncodeResult::from(RustCError::InvalidData(
+ "missing address".to_string(),
+ ))
+ .c_ptr()
+ }
+ };
+ match bitcoin::bech32::decode(bech.as_str()) {
+ Ok((_hrp, data)) => data,
+ Err(_) => {
+ return UREncodeResult::from(RustCError::InvalidData(
+ "invalid bech32".to_string(),
+ ))
+ .c_ptr()
+ }
+ }
+ } else {
+ let public_key = cardano_sign_data_reqeust.get_xpub();
+ blake2b_224(&public_key).to_vec()
+ }
+ };
let cip8_data = CardanoCip8SigStructureLedgerType {
address_field: address_field.clone(),
payload: sign_data,
};
- let cip8_cbor_data_ledger_type =
- hex::encode(minicbor::to_vec(&cip8_data).unwrap()).to_uppercase();
+ let cip8_cbor_data_ledger_type = match minicbor::to_vec(&cip8_data) {
+ Ok(v) => hex::encode(v).to_uppercase(),
+ Err(e) => return UREncodeResult::from(RustCError::InvalidData(e.to_string())).c_ptr(),
+ };
+ let path = match cardano_sign_data_reqeust.get_derivation_path().get_path() {
+ Some(p) => p,
+ None => {
+ return UREncodeResult::from(CardanoError::InvalidTransaction(
+ "Invalid derivation path".to_string(),
+ ))
+ .c_ptr()
+ }
+ };
let result = app_cardano::transaction::sign_data(
- &cardano_sign_data_reqeust
- .get_derivation_path()
- .get_path()
- .unwrap(),
+ &path,
cip8_cbor_data_ledger_type.as_str(),
icarus_master_key,
)
diff --git a/rust/rust_c/src/common/mod.rs b/rust/rust_c/src/common/mod.rs
index 1880268..dac8c2b 100644
--- a/rust/rust_c/src/common/mod.rs
+++ b/rust/rust_c/src/common/mod.rs
@@ -23,7 +23,8 @@ use structs::SimpleResponse;
use types::{PtrBytes, PtrString};
use utils::{convert_c_char, recover_c_char};
-use crate::extract_array;
+use crate::{extract_array, extract_array_mut};
+use zeroize::Zeroize;
pub mod errors;
pub mod ffi;
@@ -200,12 +201,14 @@ pub unsafe extern "C" fn get_icarus_master_key(
entropy_len: u32,
passphrase: PtrString,
) -> *mut SimpleResponse<c_char> {
- let entropy = extract_array!(entropy, u8, entropy_len as usize);
+ let mut entropy: &mut [u8] = extract_array_mut!(entropy, u8, entropy_len as usize);
let passphrase = recover_c_char(passphrase);
let master_key = keystore::algorithms::ed25519::bip32_ed25519::get_icarus_master_key_by_entropy(
entropy,
passphrase.as_bytes(),
);
+ entropy.zeroize();
+
match master_key {
Ok(result) => SimpleResponse::success(convert_c_char(result.encode_hex())).simple_c_ptr(),
Err(e) => SimpleResponse::from(e).simple_c_ptr(),
diff --git a/src/crypto/account_public_info.c b/src/crypto/account_public_info.c
index 5c924c5..9e0a552 100644
--- a/src/crypto/account_public_info.c
+++ b/src/crypto/account_public_info.c
@@ -871,7 +871,7 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
MnemonicType mnemonicType = GetMnemonicType();
bool isSlip39 = mnemonicType == MNEMONIC_TYPE_SLIP39;
bool isBip39 = mnemonicType == MNEMONIC_TYPE_BIP39;
- int len = isSlip39 ? GetCurrentAccountEntropyLen() : sizeof(seed) ;
+ int seedLen = GetCurrentAccountSeedLen();
do {
GuiApiEmitSignal(SIG_START_GENERATE_XPUB, NULL, 0);
char* icarusMasterKey = NULL;
@@ -909,7 +909,7 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
#ifdef WEB3_VERSION
if (mnemonicType == MNEMONIC_TYPE_TON) {
//store public key for ton wallet;
- xPubResult = ProcessKeyType(seed, len, g_chainTable[XPUB_TYPE_TON_NATIVE].cryptoKey, g_chainTable[XPUB_TYPE_TON_NATIVE].path, NULL, NULL);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[XPUB_TYPE_TON_NATIVE].cryptoKey, g_chainTable[XPUB_TYPE_TON_NATIVE].path, NULL, NULL);
CHECK_AND_FREE_XPUB(xPubResult)
ASSERT(xPubResult->data);
g_accountPublicInfo[XPUB_TYPE_TON_NATIVE].value = SRAM_MALLOC(strnlen_s(xPubResult->data, SIMPLERESPONSE_C_CHAR_MAX_LEN) + 1);
@@ -943,28 +943,28 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
if (g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED) {
char* zcashUfvk = NULL;
SimpleResponse_c_char *zcash_ufvk_response = NULL;
- zcash_ufvk_response = derive_zcash_ufvk(seed, len, g_chainTable[i].path);
+ zcash_ufvk_response = derive_zcash_ufvk(seed, seedLen, g_chainTable[i].path);
CHECK_AND_FREE_XPUB(zcash_ufvk_response)
zcashUfvk = zcash_ufvk_response->data;
- SimpleResponse_u8 *iv_response = rust_derive_iv_from_seed(seed, len);
+ SimpleResponse_u8 *iv_response = rust_derive_iv_from_seed(seed, seedLen);
//iv_response won't fail
uint8_t iv_bytes[16];
memcpy_s(iv_bytes, 16, iv_response->data, 16);
free_simple_response_u8(iv_response);
xPubResult = rust_aes256_cbc_encrypt(zcashUfvk, password, iv_bytes, 16);
} else {
- xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
}
#endif
#ifdef WEB3_VERSION
if (g_chainTable[i].cryptoKey == BIP32_ED25519 && isSlip39) {
- xPubResult = cardano_get_pubkey_by_slip23(seed, len, g_chainTable[i].path);
+ xPubResult = cardano_get_pubkey_by_slip23(seed, seedLen, g_chainTable[i].path);
} else {
- xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
}
#endif
#ifdef BTC_ONLY
- xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
#endif
if (g_chainTable[i].cryptoKey == RSA_KEY && xPubResult == NULL) {
continue;
@@ -992,12 +992,14 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
SetWalletDataHash(accountIndex, hash);
CLEAR_ARRAY(hash);
uint32_t size = strlen(jsonString);
- len = Gd25FlashWriteBuffer(addr, (uint8_t *)&size, 4);
+ int len = Gd25FlashWriteBuffer(addr, (uint8_t *)&size, 4);
ASSERT(len == 4);
len = Gd25FlashWriteBuffer(addr + 4, (uint8_t *)jsonString, size);
ASSERT(len == size);
printf("regenerate jsonString=%s\r\n", jsonString);
if (!isSlip39) {
+ memset_s(cip3_response->data, strlen(cip3_response->data), 0, strlen(cip3_response->data));
+ memset_s(ledger_bitbox02_response->data, strlen(ledger_bitbox02_response->data), 0, strlen(ledger_bitbox02_response->data));
free_simple_response_c_char(cip3_response);
free_simple_response_c_char(ledger_bitbox02_response);
}
@@ -1071,7 +1073,7 @@ int32_t TempAccountPublicInfo(uint8_t accountIndex, const char *password, bool s
if (isTon) {
ASSERT(false);
}
- int len = isSlip39 ? GetCurrentAccountEntropyLen() : sizeof(seed);
+ int seedLen = GetCurrentAccountSeedLen();
char *passphrase = GetPassphrase(accountIndex);
SetIsTempAccount(passphrase != NULL && passphrase[0] != 0);
@@ -1126,29 +1128,29 @@ int32_t TempAccountPublicInfo(uint8_t accountIndex, const char *password, bool s
if (g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED) {
char* zcashUfvk = NULL;
SimpleResponse_c_char *zcash_ufvk_response = NULL;
- zcash_ufvk_response = derive_zcash_ufvk(seed, len, g_chainTable[i].path);
+ zcash_ufvk_response = derive_zcash_ufvk(seed, seedLen, g_chainTable[i].path);
CHECK_AND_FREE_XPUB(zcash_ufvk_response)
zcashUfvk = zcash_ufvk_response->data;
- SimpleResponse_u8 *iv_response = rust_derive_iv_from_seed(seed, len);
+ SimpleResponse_u8 *iv_response = rust_derive_iv_from_seed(seed, seedLen);
//iv_response won't fail
uint8_t iv_bytes[16];
memcpy_s(iv_bytes, 16, iv_response->data, 16);
free_simple_response_u8(iv_response);
xPubResult = rust_aes256_cbc_encrypt(zcashUfvk, password, iv_bytes, 16);
} else {
- xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
}
#endif
#ifdef WEB3_VERSION
if (g_chainTable[i].cryptoKey == BIP32_ED25519 && isSlip39) {
// ada slip23
- xPubResult = cardano_get_pubkey_by_slip23(seed, len, g_chainTable[i].path);
+ xPubResult = cardano_get_pubkey_by_slip23(seed, seedLen, g_chainTable[i].path);
} else {
- xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
}
#endif
#ifdef BTC_ONLY
- xPubResult = ProcessKeyType(seed, len, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
+ xPubResult = ProcessKeyType(seed, seedLen, g_chainTable[i].cryptoKey, g_chainTable[i].path, icarusMasterKey, ledgerBitbox02Key);
#endif
if (g_chainTable[i].cryptoKey == RSA_KEY && xPubResult == NULL) {
continue;
diff --git a/src/crypto/utils/hash_and_salt.c b/src/crypto/utils/hash_and_salt.c
index 535c640..3b8ea44 100644
--- a/src/crypto/utils/hash_and_salt.c
+++ b/src/crypto/utils/hash_and_salt.c
@@ -8,6 +8,7 @@
#include "drv_trng.h"
#include "log_print.h"
#include "drv_mpu.h"
+#include "user_memory.h"
#define SALT_DATA_LEN 32
diff --git a/src/managers/account_manager.c b/src/managers/account_manager.c
index fe7d042..6412627 100644
--- a/src/managers/account_manager.c
+++ b/src/managers/account_manager.c
@@ -461,6 +461,13 @@ uint32_t GetCurrentAccountEntropyLen(void)
return g_currentAccountInfo.entropyLen;
}
+// For BIP39, seed length is fixed 64 bytes (derived via PBKDF2 from mnemonic).
+// For SLIP39, the seed is produced by MS flow and we use the current account entropy length.
+uint32_t GetCurrentAccountSeedLen(void)
+{
+ return (GetMnemonicType() == MNEMONIC_TYPE_SLIP39) ? GetCurrentAccountEntropyLen() : SEED_LEN;
+}
+
/// @brief Save g_currentAccountInfo to SE.
/// @return err code.
int32_t SaveCurrentAccountInfo(void)
diff --git a/src/managers/account_manager.h b/src/managers/account_manager.h
index 1f78079..712b210 100644
--- a/src/managers/account_manager.h
+++ b/src/managers/account_manager.h
@@ -88,6 +88,7 @@ uint8_t GetCurrentPasswordErrorCount(void);
uint32_t GetLastLockDeviceTime(void);
void SetLastLockDeviceTime(uint32_t timeStamp);
uint32_t GetCurrentAccountEntropyLen(void);
+uint32_t GetCurrentAccountSeedLen(void);
uint8_t *GetCurrentAccountMfp(void);
int32_t GetAccountInfo(uint8_t accountIndex, AccountInfo_t *pInfo);
diff --git a/src/ui/gui_chain/multi/web3/gui_ada.c b/src/ui/gui_chain/multi/web3/gui_ada.c
index 3009ff6..c5b5b83 100644
--- a/src/ui/gui_chain/multi/web3/gui_ada.c
+++ b/src/ui/gui_chain/multi/web3/gui_ada.c
@@ -18,14 +18,13 @@ static struct URParseResult *g_urResult = NULL;
static struct URParseMultiResult *g_urMultiResult = NULL;
static void *g_parseResult = NULL;
static char g_adaBaseAddr[ADA_ADD_MAX_LEN];
-static char *xpub = NULL;
+static char *g_xpub = NULL;
static void Try2FixAdaPathType();
static bool IsLocalAdaPath(char *path);
AdaXPubType GetAdaXPubType(void)
{
return GetAccountReceivePath("ADA");
- // return g_adaXpubTypes[GetCurrentAccountIndex()];
}
void SetReceivePageAdaXPubType(AdaXPubType type)
@@ -102,12 +101,12 @@ void *GuiGetAdaData(void)
CHECK_CHAIN_BREAK(path);
char *adaPath = path->data;
if (!IsLocalAdaPath(adaPath)) {
- xpub = NULL;
+ g_xpub = NULL;
} else {
uint8_t xpubIndex = GetXPubIndexByPath(adaPath);
- xpub = GetCurrentAccountPublicKey(xpubIndex);
+ g_xpub = GetCurrentAccountPublicKey(xpubIndex);
}
- TransactionParseResult_DisplayCardanoTx *parseResult = cardano_parse_tx(data, mfp, xpub);
+ TransactionParseResult_DisplayCardanoTx *parseResult = cardano_parse_tx(data, mfp, g_xpub);
CHECK_CHAIN_BREAK(parseResult);
g_parseResult = (void *)parseResult;
} while (0);
@@ -129,7 +128,6 @@ void *GuiGetAdaCatalyst(void)
void *GuiGetAdaSignTxHashData(void)
{
- printf("=========== GuiGetAdaSignTxHashData\r\n");
CHECK_FREE_PARSE_RESULT(g_parseResult);
void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
do {
@@ -233,20 +231,20 @@ PtrT_TransactionCheckResult GuiGetAdaCheckResult(void)
}
char *adaPath = path->data;
if (!IsLocalAdaPath(adaPath)) {
- xpub = NULL;
+ g_xpub = NULL;
} else {
- xpub = GetCurrentAccountPublicKey(GetXPubIndexByPath(adaPath));
+ g_xpub = GetCurrentAccountPublicKey(GetXPubIndexByPath(adaPath));
}
- PtrT_TransactionCheckResult result = cardano_check_tx(data, mfp, xpub);
+ PtrT_TransactionCheckResult result = cardano_check_tx(data, mfp, g_xpub);
if (result->error_code != 0) {
free_TransactionCheckResult(result);
Try2FixAdaPathType();
if (!IsLocalAdaPath(adaPath)) {
- xpub = NULL;
+ g_xpub = NULL;
} else {
- xpub = GetCurrentAccountPublicKey(GetXPubIndexByPath(adaPath));
+ g_xpub = GetCurrentAccountPublicKey(GetXPubIndexByPath(adaPath));
}
- result = cardano_check_tx(data, mfp, xpub);
+ result = cardano_check_tx(data, mfp, g_xpub);
}
free_simple_response_c_char(path);
return result;
@@ -752,9 +750,9 @@ UREncodeResult *GuiGetAdaSignQrCodeData(void)
if (GetAdaXPubType() == LEDGER_ADA) {
char *mnemonic = NULL;
bip39_mnemonic_from_bytes(NULL, entropy, len, &mnemonic);
- encodeResult = cardano_sign_tx_with_ledger_bitbox02(data, mfp, xpub, mnemonic, GetPassphrase(GetCurrentAccountIndex()), false);
+ encodeResult = cardano_sign_tx_with_ledger_bitbox02(data, mfp, g_xpub, mnemonic, GetPassphrase(GetCurrentAccountIndex()), false);
} else {
- encodeResult = cardano_sign_tx(data, mfp, xpub, entropy, len, GetPassphrase(GetCurrentAccountIndex()), false, isSlip39);
+ encodeResult = cardano_sign_tx(data, mfp, g_xpub, entropy, len, GetPassphrase(GetCurrentAccountIndex()), false, isSlip39);
}
ClearSecretCache();
CHECK_CHAIN_BREAK(encodeResult);
@@ -786,9 +784,9 @@ UREncodeResult *GuiGetAdaSignTxHashQrCodeData(void)
if (GetAdaXPubType() == LEDGER_ADA) {
char *mnemonic = NULL;
bip39_mnemonic_from_bytes(NULL, entropy, len, &mnemonic);
- encodeResult = cardano_sign_tx_with_ledger_bitbox02(data, mfp, xpub, mnemonic, GetPassphrase(GetCurrentAccountIndex()), true);
+ encodeResult = cardano_sign_tx_with_ledger_bitbox02(data, mfp, g_xpub, mnemonic, GetPassphrase(GetCurrentAccountIndex()), true);
} else {
- encodeResult = cardano_sign_tx(data, mfp, xpub, entropy, len, GetPassphrase(GetCurrentAccountIndex()), true, isSlip39);
+ encodeResult = cardano_sign_tx(data, mfp, g_xpub, entropy, len, GetPassphrase(GetCurrentAccountIndex()), true, isSlip39);
}
ClearSecretCache();
CHECK_CHAIN_BREAK(encodeResult);
@@ -982,9 +980,9 @@ UREncodeResult *GuiGetAdaSignUrDataUnlimited(void)
if (GetAdaXPubType() == LEDGER_ADA) {
char *mnemonic = NULL;
bip39_mnemonic_from_bytes(NULL, entropy, len, &mnemonic);
- encodeResult = cardano_sign_tx_with_ledger_bitbox02_unlimited(data, mfp, xpub, mnemonic, GetPassphrase(GetCurrentAccountIndex()));
+ encodeResult = cardano_sign_tx_with_ledger_bitbox02_unlimited(data, mfp, g_xpub, mnemonic, GetPassphrase(GetCurrentAccountIndex()));
} else {
- encodeResult = cardano_sign_tx_unlimited(data, mfp, xpub, entropy, len, GetPassphrase(GetCurrentAccountIndex()), isSlip39);
+ encodeResult = cardano_sign_tx_unlimited(data, mfp, g_xpub, entropy, len, GetPassphrase(GetCurrentAccountIndex()), isSlip39);
}
ClearSecretCache();
CHECK_CHAIN_BREAK(encodeResult);
@@ -995,58 +993,11 @@ UREncodeResult *GuiGetAdaSignUrDataUnlimited(void)
ChainType GetAdaXPubTypeByIndexAndDerivationType(AdaXPubType type, uint16_t index)
{
- switch (index) {
- case 0:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_0 : XPUB_TYPE_LEDGER_ADA_0;
- case 1:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_1 : XPUB_TYPE_LEDGER_ADA_1;
- case 2:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_2 : XPUB_TYPE_LEDGER_ADA_2;
- case 3:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_3 : XPUB_TYPE_LEDGER_ADA_3;
- case 4:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_4 : XPUB_TYPE_LEDGER_ADA_4;
- case 5:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_5 : XPUB_TYPE_LEDGER_ADA_5;
- case 6:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_6 : XPUB_TYPE_LEDGER_ADA_6;
- case 7:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_7 : XPUB_TYPE_LEDGER_ADA_7;
- case 8:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_8 : XPUB_TYPE_LEDGER_ADA_8;
- case 9:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_9 : XPUB_TYPE_LEDGER_ADA_9;
- case 10:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_10 : XPUB_TYPE_LEDGER_ADA_10;
- case 11:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_11 : XPUB_TYPE_LEDGER_ADA_11;
- case 12:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_12 : XPUB_TYPE_LEDGER_ADA_12;
- case 13:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_13 : XPUB_TYPE_LEDGER_ADA_13;
- case 14:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_14 : XPUB_TYPE_LEDGER_ADA_14;
- case 15:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_15 : XPUB_TYPE_LEDGER_ADA_15;
- case 16:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_16 : XPUB_TYPE_LEDGER_ADA_16;
- case 17:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_17 : XPUB_TYPE_LEDGER_ADA_17;
- case 18:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_18 : XPUB_TYPE_LEDGER_ADA_18;
- case 19:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_19 : XPUB_TYPE_LEDGER_ADA_19;
- case 20:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_20 : XPUB_TYPE_LEDGER_ADA_20;
- case 21:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_21 : XPUB_TYPE_LEDGER_ADA_21;
- case 22:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_22 : XPUB_TYPE_LEDGER_ADA_22;
- case 23:
- return type == STANDARD_ADA ? XPUB_TYPE_ADA_23 : XPUB_TYPE_LEDGER_ADA_23;
- default:
- return XPUB_TYPE_ADA_0;
+ if (index > 23) {
+ index = 0;
}
+ ChainType base = (type == STANDARD_ADA) ? XPUB_TYPE_ADA_0 : XPUB_TYPE_LEDGER_ADA_0;
+ return (ChainType)(base + index);
}
ChainType GetAdaXPubTypeByIndex(uint16_t index)
Why this scored 51/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.