What changed, and why it matters
This commit is a large refactor of Bitcoin-related code in the Keystone 3 firmware. Most changes add unit tests, clean up formatting, and reorganize C code into smaller helper functions. There are a few small functional changes in how Taproot PSBT signing status is tracked and how seed length is obtained, but no obvious security vulnerability is introduced. The refactor also adds explicit clearing of the seed buffer after signing, which is a minor security improvement.
Review the refactor for correctness, especially the new Taproot sign-status logic and the C helper-function split. Verify that the seed zeroization and error-check changes do not introduce regressions. No immediate security patch is indicated, but standard regression testing of Bitcoin signing flows (single-sig, multisig, Taproot, SD-card PSBT) is recommended.
Security signals we found
Refactor of Bitcoin signing and PSBT parsing code
Addition of Taproot signing-status detection in wrapped_psbt.rs
Seed buffer zeroization added after signing in gui_btc.c
GetAccountSeed return value now checked with CHECK_ERRCODE_RETURN
Seed length calculation consolidated to GetCurrentAccountSeedLen()
Large test-only additions across address, cashaddr, xpub, multisig, legacy tx, and PSBT modules
Evidence from the diff
The commit touches 14 files across Rust Bitcoin address handling, multisig wallet logic, legacy and PSBT transaction parsing/signing, plus the C UI glue in gui_btc.c. The Rust side is dominated by new unit tests and minor cleanups (removing println!, formatting). wrapped_psbt.rs adds logic to detect Taproot inputs and report their sign status separately, including key-path and script-path spend detection. gui_btc.c refactors long functions into static helpers (BtcSignPsbt, BtcSignPsbtMultisig, CheckPsbt, PreparePublicKeys, etc.), replaces a ternary seed-length calculation with GetCurrentAccountSeedLen(), adds CHECK_ERRCODE_RETURN on GetAccountSeed, and adds memset_s(seed…) zeroization after signing. No CVE or vendor security disclosure is present in the materials.
Changed components
rust/apps/bitcoin/src/addresses/address.rsrust/apps/bitcoin/src/addresses/cashaddr.rsrust/apps/bitcoin/src/addresses/mod.rsrust/apps/bitcoin/src/addresses/xyzpub.rsrust/apps/bitcoin/src/multi_sig/address.rsrust/apps/bitcoin/src/multi_sig/mod.rsrust/apps/bitcoin/src/multi_sig/wallet.rsrust/apps/bitcoin/src/transactions/legacy/mod.rsrust/apps/bitcoin/src/transactions/legacy/tx_data.rsrust/apps/bitcoin/src/transactions/psbt/parsed_psbt.rsrust/apps/bitcoin/src/transactions/psbt/wrapped_psbt.rsrust/apps/zcash/src/pczt/parse.rsrust/rust_c/src/zcash/mod.rssrc/ui/gui_chain/gui_btc.cInspect captured patch +1006 / −307
diff --git a/rust/apps/bitcoin/src/addresses/address.rs b/rust/apps/bitcoin/src/addresses/address.rs
index 52b767e..ae81353 100644
--- a/rust/apps/bitcoin/src/addresses/address.rs
+++ b/rust/apps/bitcoin/src/addresses/address.rs
@@ -412,9 +412,17 @@ impl FromStr for Address {
#[cfg(test)]
mod tests {
use crate::network::NetworkT;
+ use bitcoin::secp256k1::{PublicKey as SecpPublicKey, SecretKey};
use super::*;
+ fn sample_pubkey() -> PublicKey {
+ let secp = Secp256k1::new();
+ let secret_key = SecretKey::from_slice(&[1u8; 32]).unwrap();
+ let public_key = SecpPublicKey::from_secret_key(&secp, &secret_key);
+ PublicKey::new(public_key)
+ }
+
#[test]
fn test_address_btc_p2pkh() {
let addr = Address::from_str("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2").unwrap();
@@ -534,4 +542,81 @@ mod tests {
assert_eq!(addr.network.get_unit(), "DASH");
assert_eq!(addr.to_string(), "7qd1hqQqZzMRaJA5drqkpEZL41s3JktRuZ");
}
+
+ #[test]
+ fn test_find_bech32_prefix_variants() {
+ assert_eq!(
+ find_bech32_prefix("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"),
+ "bc"
+ );
+ assert_eq!(find_bech32_prefix("nobech32prefix"), "nobech32prefix");
+ }
+
+ #[test]
+ fn test_address_from_script_roundtrip_p2pkh() {
+ let addr = Address::from_str("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2").unwrap();
+ let expected = addr.to_string();
+ let network = addr.network.clone();
+ let script = addr.script_pubkey();
+
+ let reconstructed = Address::from_script(&script, network).unwrap();
+ assert_eq!(reconstructed.to_string(), expected);
+ }
+
+ #[test]
+ fn test_address_from_script_roundtrip_p2sh() {
+ let addr = Address::from_str("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy").unwrap();
+ let expected = addr.to_string();
+ let network = addr.network.clone();
+ let script = addr.script_pubkey();
+
+ let reconstructed = Address::from_script(&script, network).unwrap();
+ assert_eq!(reconstructed.to_string(), expected);
+ }
+
+ #[test]
+ fn test_address_from_script_roundtrip_segwit() {
+ let addr = Address::from_str("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4").unwrap();
+ let expected = addr.to_string();
+ let network = addr.network.clone();
+ let script = addr.script_pubkey();
+
+ let reconstructed = Address::from_script(&script, network).unwrap();
+ assert_eq!(reconstructed.to_string(), expected);
+ }
+
+ #[test]
+ fn test_address_from_script_unrecognized() {
+ let script = ScriptBuf::from_bytes(vec![0x51]); // OP_1
+ let err = Address::from_script(&script, Network::Bitcoin).unwrap_err();
+ assert!(matches!(err, BitcoinError::AddressError(_)));
+ }
+
+ #[test]
+ fn test_address_p2pkh_invalid_network() {
+ let pk = sample_pubkey();
+ let result = Address::p2pkh(&pk, Network::AvaxBtcBridge);
+ assert!(matches!(result, Err(BitcoinError::AddressError(_))));
+ }
+
+ #[test]
+ fn test_address_p2wpkh_invalid_network() {
+ let pk = sample_pubkey();
+ let result = Address::p2wpkh(&pk, Network::Litecoin);
+ assert!(matches!(result, Err(BitcoinError::AddressError(_))));
+ }
+
+ #[test]
+ fn test_address_parse_invalid_base58_length() {
+ let long_address = "1".repeat(51);
+ let err = Address::from_str(&long_address);
+ assert!(matches!(err, Err(BitcoinError::AddressError(_))));
+ }
+
+ #[test]
+ fn test_address_p2tr_no_script_invalid_network() {
+ let pk = sample_pubkey();
+ let result = Address::p2tr_no_script(&pk, Network::Litecoin);
+ assert!(matches!(result, Err(BitcoinError::AddressError(_))));
+ }
}
diff --git a/rust/apps/bitcoin/src/addresses/cashaddr.rs b/rust/apps/bitcoin/src/addresses/cashaddr.rs
index 6ccb897..7a43e02 100644
--- a/rust/apps/bitcoin/src/addresses/cashaddr.rs
+++ b/rust/apps/bitcoin/src/addresses/cashaddr.rs
@@ -349,6 +349,10 @@ mod tests {
use hex::ToHex;
+ fn sample_cashaddr() -> &'static str {
+ "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
+ }
+
#[test]
fn test_decode_cash_addr() {
let addr_str = "qz65ywjm92m27wshfnew2w3us5vsgxqkxc55t9lqcw";
@@ -359,4 +363,73 @@ mod tests {
"76a914b5423a5b2ab6af3a174cf2e53a3c85190418163688ac"
);
}
+
+ #[test]
+ fn test_cashaddr_encode_roundtrip() {
+ let address = CashAddrCodec::decode(sample_cashaddr()).unwrap();
+ let pubkey_hash = match address.payload {
+ Payload::P2pkh { pubkey_hash } => pubkey_hash,
+ _ => panic!("expected p2pkh payload"),
+ };
+ let encoded = CashAddrCodec::encode(pubkey_hash.to_byte_array().to_vec()).unwrap();
+ assert_eq!(encoded, sample_cashaddr());
+ }
+
+ #[test]
+ fn test_cashaddr_decode_mixed_case_error() {
+ let err = CashAddrCodec::decode("qPm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a");
+ assert!(matches!(err, Err(BitcoinError::AddressError(_))));
+ }
+
+ #[test]
+ fn test_cashaddr_decode_invalid_checksum() {
+ let mut addr = sample_cashaddr().to_string();
+ addr.pop();
+ addr.push('z');
+ let err = CashAddrCodec::decode(&addr);
+ assert!(matches!(err, Err(BitcoinError::AddressError(_))));
+ }
+
+ #[test]
+ fn test_expand_prefix_appends_separator() {
+ let expanded = expand_prefix("bitcoincash");
+ assert_eq!(expanded.last(), Some(&0));
+ let expected: Vec<u8> = "bitcoincash".chars().map(|c| (c as u8) & 0x1f).collect();
+ assert_eq!(&expanded[..expected.len()], expected.as_slice());
+ }
+
+ #[test]
+ fn test_convert_bits_padding_behavior() {
+ let data = [0xff];
+ let padded = convert_bits(&data, 8, 5, true);
+ assert_eq!(padded, vec![31, 28]);
+
+ let unpadded = convert_bits(&data, 8, 5, false);
+ assert_eq!(unpadded, vec![31]);
+ }
+
+ #[test]
+ fn test_polymod_known_sequence() {
+ let checksum = polymod(&[1, 2, 3, 4, 5]);
+ assert_eq!(checksum, 34671748);
+ }
+
+ #[test]
+ fn test_base58_decode_success() {
+ // Legacy BCH address in base58 format
+ let body = Base58Codec::decode("1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu").unwrap();
+ assert_eq!(body.len(), 20);
+ }
+
+ #[test]
+ fn test_base58_decode_invalid_char() {
+ let err = Base58Codec::decode("1BpEi6DfDAUFd7GtittLSdBeYJvcoaVgg!");
+ assert!(matches!(err, Err(BitcoinError::AddressError(_))));
+ }
+
+ #[test]
+ fn test_from_base58_str_leading_zeroes() {
+ let decoded = from_base58_str("1111").unwrap();
+ assert_eq!(decoded, vec![0, 0, 0, 0]);
+ }
}
diff --git a/rust/apps/bitcoin/src/addresses/mod.rs b/rust/apps/bitcoin/src/addresses/mod.rs
index ab24a94..a77734e 100644
--- a/rust/apps/bitcoin/src/addresses/mod.rs
+++ b/rust/apps/bitcoin/src/addresses/mod.rs
@@ -54,6 +54,7 @@ pub fn get_address(hd_path: String, extended_pub_key: &String) -> Result<String>
#[cfg(test)]
mod tests {
use super::*;
+ use crate::errors::BitcoinError;
use alloc::string::String;
use alloc::string::ToString;
@@ -171,4 +172,41 @@ mod tests {
"tb1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqlqt9zj"
);
}
+
+ #[test]
+ fn test_get_address_invalid_xpub() {
+ let err =
+ get_address(String::from("M/44'/0'/0'/0/0"), &"invalid_xpub".to_string()).unwrap_err();
+ assert_eq!(
+ err,
+ BitcoinError::AddressError("xpub is not valid".to_string())
+ );
+ }
+
+ #[test]
+ fn test_get_address_network_not_supported() {
+ let extended_pubkey = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj";
+ let err = get_address(
+ String::from("M/45'/0'/0'/0/0"),
+ &extended_pubkey.to_string(),
+ )
+ .unwrap_err();
+ assert_eq!(
+ err,
+ BitcoinError::AddressError("network is not supported".to_string())
+ );
+ }
+
+ #[test]
+ fn test_get_address_invalid_hd_path_length() {
+ let extended_pubkey = "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs";
+ let err =
+ get_address(String::from("M/84'/0'/0'/0"), &extended_pubkey.to_string()).unwrap_err();
+ match err {
+ BitcoinError::SignLegacyTxError(message) => {
+ assert!(message.contains("invalid hd_path"))
+ }
+ other => panic!("unexpected error: {other:?}"),
+ }
+ }
}
diff --git a/rust/apps/bitcoin/src/addresses/xyzpub.rs b/rust/apps/bitcoin/src/addresses/xyzpub.rs
index 9d9cf62..7d448b7 100644
--- a/rust/apps/bitcoin/src/addresses/xyzpub.rs
+++ b/rust/apps/bitcoin/src/addresses/xyzpub.rs
@@ -304,4 +304,45 @@ mod tests {
assert_eq!(result, expected_dgub);
}
+
+ fn sample_xpub_bytes() -> Vec<u8> {
+ base58::decode_check(
+ "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
+ )
+ .unwrap()
+ }
+
+ #[test]
+ fn replace_version_bytes_success() {
+ let bytes = sample_xpub_bytes();
+ let replaced = replace_version_bytes(bytes, &Version::Zpub).unwrap();
+ assert_eq!(&replaced[..4], VERSION_ZPUB);
+ }
+
+ #[test]
+ fn convert_version_roundtrip() {
+ let original = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj";
+ let converted = convert_version(original, &Version::Zpub).unwrap();
+ let back = convert_version(&converted, &Version::Xpub).unwrap();
+ assert_eq!(back, original);
+ }
+
+ #[test]
+ fn version_from_str_cases() {
+ assert_eq!(Version::from_str("xpub").unwrap(), Version::Xpub);
+ assert_eq!(Version::from_str("Zpub").unwrap(), Version::ZpubMultisig);
+ assert!(Version::from_str("unknown").is_err());
+ }
+
+ #[test]
+ fn replace_version_bytes_too_short_slice() {
+ let result = replace_version_bytes([0u8; 2], &Version::Xpub);
+ assert!(matches!(result, Err(BitcoinError::Base58Error(_))));
+ }
+
+ #[test]
+ fn convert_version_invalid_base58() {
+ let err = convert_version("invalid", &Version::Xpub);
+ assert!(matches!(err, Err(BitcoinError::Base58Error(_))));
+ }
}
diff --git a/rust/apps/bitcoin/src/multi_sig/address.rs b/rust/apps/bitcoin/src/multi_sig/address.rs
index 73eca98..af66bd5 100644
--- a/rust/apps/bitcoin/src/multi_sig/address.rs
+++ b/rust/apps/bitcoin/src/multi_sig/address.rs
@@ -203,7 +203,7 @@ mod tests {
#[test]
fn test_create_multi_sig_address_for_pubkeys() {
- let pubkey_str = vec![
+ let pubkey_str = [
"03a0c95fd48f1a251c744629e19ad154dfe1d7fb992d6955d62c417ae4ac333340",
"0361769c55b3035962fd3267da5cc4efa03cb400fe1971f5ec1c686d6b301ccd60",
"021d24a7eda6ccbff4616d9965c9bb2a7871ce048b0161b71e91be83671be514d5",
@@ -222,4 +222,63 @@ mod tests {
.unwrap();
assert_eq!(address, "A2nev5Fc7tFZ11oy1Ybz1kJRbebTWff8K6");
}
+
+ #[test]
+ fn test_create_multi_sig_address_with_sorted_keys_matches_unsorted() {
+ let pubkey_str = [
+ "0361769c55b3035962fd3267da5cc4efa03cb400fe1971f5ec1c686d6b301ccd60",
+ "021d24a7eda6ccbff4616d9965c9bb2a7871ce048b0161b71e91be83671be514d5",
+ "03a0c95fd48f1a251c744629e19ad154dfe1d7fb992d6955d62c417ae4ac333340",
+ ];
+ let pubkeys = pubkey_str
+ .iter()
+ .map(|s| PublicKey::from_slice(&hex::decode(s).unwrap()).unwrap())
+ .collect::<Vec<_>>();
+ let mut sorted_pubkeys = pubkeys.clone();
+ sorted_pubkeys.sort_by_key(|key| key.to_bytes());
+
+ let expected = create_multi_sig_address_for_pubkeys_with_sorting(
+ 2,
+ &sorted_pubkeys,
+ MultiSigFormat::P2wsh,
+ Network::Bitcoin,
+ false,
+ )
+ .unwrap();
+ let result = create_multi_sig_address_for_pubkeys_with_sorting(
+ 2,
+ &pubkeys,
+ MultiSigFormat::P2wsh,
+ Network::Bitcoin,
+ true,
+ )
+ .unwrap();
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_create_multi_sig_address_testnet_prefix() {
+ let pubkey_str = [
+ "03a0c95fd48f1a251c744629e19ad154dfe1d7fb992d6955d62c417ae4ac333340",
+ "0361769c55b3035962fd3267da5cc4efa03cb400fe1971f5ec1c686d6b301ccd60",
+ "021d24a7eda6ccbff4616d9965c9bb2a7871ce048b0161b71e91be83671be514d5",
+ ];
+ let pubkeys = pubkey_str
+ .iter()
+ .map(|s| PublicKey::from_slice(&hex::decode(s).unwrap()).unwrap())
+ .collect::<Vec<_>>();
+
+ let address = create_multi_sig_address_for_pubkeys_with_sorting(
+ 2,
+ &pubkeys,
+ MultiSigFormat::P2sh,
+ Network::BitcoinTestnet,
+ true,
+ )
+ .unwrap();
+ assert!(
+ address.starts_with('2'),
+ "unexpected testnet address prefix: {address}"
+ );
+ }
}
diff --git a/rust/apps/bitcoin/src/multi_sig/mod.rs b/rust/apps/bitcoin/src/multi_sig/mod.rs
index ca930c5..712873d 100644
--- a/rust/apps/bitcoin/src/multi_sig/mod.rs
+++ b/rust/apps/bitcoin/src/multi_sig/mod.rs
@@ -785,21 +785,18 @@ mod tests {
"#;
let result = extract_xpub_info_from_str(json_str, &MultiSigFormat::P2sh).unwrap();
- println!("result is {:?}", result);
assert_eq!("m/45'", result.path);
assert_eq!("EB16731F", result.xfp);
assert_eq!("xpub69cicR2MFe9QbMVTMHN882fGtXBKQV4g9gqWNZN7aEM9RASi3WmUzgPF9md8fLfUNuF4znQ8937VQrjG2bG8VgU7rjhUR8qCfBL9hJDQogL", result.xpub);
let result = extract_xpub_info_from_str(json_str, &MultiSigFormat::P2wshP2sh).unwrap();
- println!("result is {:?}", result);
assert_eq!("m/48'/0'/0'/1'", result.path);
assert_eq!("EB16731F", result.xfp);
assert_eq!("Ypub6jwcqK3XjbvvGXVFK6ghVrrqctFYJjFPh4ZiQ6ZFfMKxZSe5FWzZX3ib3THzRy2UKsesZwXmXVGsez8pyMuDmdbhnN55RcZbTLj7rFqViqo", result.xpub);
let result = extract_xpub_info_from_str(json_str, &MultiSigFormat::P2wsh).unwrap();
- println!("result is {:?}", result);
assert_eq!("m/48'/0'/0'/2'", result.path);
assert_eq!("EB16731F", result.xfp);
diff --git a/rust/apps/bitcoin/src/multi_sig/wallet.rs b/rust/apps/bitcoin/src/multi_sig/wallet.rs
index 2d57123..4db6c61 100644
--- a/rust/apps/bitcoin/src/multi_sig/wallet.rs
+++ b/rust/apps/bitcoin/src/multi_sig/wallet.rs
@@ -567,11 +567,15 @@ mod tests {
use alloc::string::ToString;
+ use super::{
+ calculate_multi_sig_verify_code, detect_network, is_valid_multi_sig_policy, is_valid_xfp,
+ verify_wallet_config, MultiSigWalletConfig, MultiSigXPubItem,
+ };
use crate::multi_sig::wallet::{
create_wallet, generate_config_data, is_valid_xyzpub, parse_bsms_wallet_config,
parse_wallet_config, strict_verify_wallet_config,
};
- use crate::multi_sig::{MultiSigXPubInfo, Network};
+ use crate::multi_sig::{MultiSigFormat, MultiSigXPubInfo, Network};
use hex;
use ur_registry::bytes::Bytes;
@@ -749,8 +753,17 @@ mod tests {
let seed = hex::decode("5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4").unwrap();
let config = parse_wallet_config(config, "73c5da0a").unwrap();
+ // Sanity-check derived xpub equals config entry
+ let derivation = "m/48'/0'/0'/1'".to_string();
+ let expected = super::get_extended_public_key_by_seed(&seed, &derivation)
+ .unwrap()
+ .to_string();
+ let config_first =
+ super::xyzpub::convert_version(&config.xpub_items[0].xpub, &super::Version::Xpub)
+ .unwrap();
+ assert_eq!(expected, config_first);
let result = strict_verify_wallet_config(&seed, &config, "73c5da0a");
- assert_eq!(true, result.is_ok());
+ assert!(result.is_ok());
}
#[test]
@@ -924,4 +937,92 @@ BIP39 4".as_bytes().to_vec());
assert_eq!(wallet.derivation_path, "m/48'/0'/0'/2'");
assert_eq!(wallet.extended_pubkey, "xpub6DkFAXWQ2dHxq2vatrt9qyA3bXYU4ToWQwCHbf5XB2mSTexcHZCeKS1VZYcPoBd5X8yVcbXFHJR9R8UCVpt82VX1VhR28mCyxUFL4r6KFrf");
}
+
+ #[test]
+ fn test_multisig_wallet_getters() {
+ let mut wallet = MultiSigWalletConfig::default();
+ wallet.network = Network::MainNet;
+ wallet.format = "P2WSH".to_string();
+ wallet.derivations.push("m/48'/0'/0'/2'".to_string());
+ let path = wallet.get_wallet_path().unwrap();
+ assert_eq!(path, super::MULTI_P2WSH_PATH);
+ assert_eq!(wallet.get_network_u32(), 0);
+
+ wallet.derivations = vec!["m/48'/0'/0'/2'".to_string()];
+ let derivation = wallet.get_derivation_by_index(5).unwrap();
+ assert_eq!(derivation, "48'/0'/0'/2'");
+ }
+
+ #[test]
+ fn test_get_wallet_path_invalid_format() {
+ let mut wallet = MultiSigWalletConfig::default();
+ wallet.format = "UNKNOWN".to_string();
+ wallet.network = Network::MainNet;
+ assert!(wallet.get_wallet_path().is_err());
+ }
+
+ #[test]
+ fn test_is_valid_multi_sig_policy() {
+ assert!(is_valid_multi_sig_policy(3, 2));
+ assert!(!is_valid_multi_sig_policy(1, 0));
+ }
+
+ #[test]
+ fn test_is_valid_xfp() {
+ assert!(is_valid_xfp("73C5DA0A"));
+ assert!(!is_valid_xfp("G3C5DA0A"));
+ assert!(!is_valid_xfp("73C5DA0"));
+ }
+
+ #[test]
+ fn test_detect_network_helper() {
+ assert_eq!(detect_network("xpub6F6iZVTmc3KMgAUkV9JRNaouxYYwChRswPN1ut7nTfecn6VPRYLXFgXar1gvPUX27QH1zaVECqVEUoA2qMULZu5TjyKrjcWcLTQ6LkhrZAj"), Network::MainNet);
+ assert_eq!(detect_network("tpubD9hphZzCi9u5Wcbtq3jQYTzbPv6igoaRWDuhxLUDv5VTffE3gEVovYaqwfVMCa6q8VMdwAcPpFgAdajgmLML6XgYrKBquyYEDQg1HnKm3wQ"), Network::TestNet);
+ }
+
+ #[test]
+ fn test_verify_wallet_config_errors() {
+ let mut wallet = MultiSigWalletConfig::default();
+ wallet.total = 2;
+ wallet.threshold = 2;
+ wallet.derivations.push("m/45'".to_string());
+ let err = verify_wallet_config(&wallet, "73C5DA0A").unwrap_err();
+ assert!(matches!(
+ err,
+ crate::BitcoinError::MultiSigWalletParseError(_)
+ ));
+
+ wallet.xpub_items.push(MultiSigXPubItem {
+ xfp: "C45358FA".to_string(),
+ xpub: "tpubD9hphZzCi9u5Wcbtq3jQYTzbPv6igoaRWDuhxLUDv5VTffE3gEVovYaqwfVMCa6q8VMdwAcPpFgAdajgmLML6XgYrKBquyYEDQg1HnKm3wQ"
+ .to_string(),
+ });
+ let err = verify_wallet_config(&wallet, "73C5DA0A").unwrap_err();
+ assert!(matches!(
+ err,
+ crate::BitcoinError::MultiSigWalletParseError(_)
+ ));
+ }
+
+ #[test]
+ fn test_calculate_multi_sig_verify_code() {
+ let xpubs = vec![
+ "xpub6F6iZVTmc3KMgAUkV9JRNaouxYYwChRswPN1ut7nTfecn6VPRYLXFgXar1gvPUX27QH1zaVECqVEUoA2qMULZu5TjyKrjcWcLTQ6LkhrZAj"
+ .to_string(),
+ "xpub6EiTGcKqBQy2uTat1QQPhYQWt8LGmZStNqKDoikedkB72sUqgF9fXLUYEyPthqLSb6VP4akUAsy19MV5LL8SvqdzvcABYUpKw45jA1KZMhm"
+ .to_string(),
+ "xpub6EWksRHwPbDmXWkjQeA6wbCmXZeDPXieMob9hhbtJjmrmk647bWkh7om5rk2eoeDKcKG6NmD8nT7UZAFxXQMjTnhENTwTEovQw3MDQ8jJ16"
+ .to_string(),
+ ];
+ let code = calculate_multi_sig_verify_code(
+ &xpubs,
+ 2,
+ 3,
+ MultiSigFormat::P2wsh,
+ &Network::MainNet,
+ Some("5271C071"),
+ )
+ .unwrap();
+ assert_eq!(code, "9aa1fcb7");
+ }
}
diff --git a/rust/apps/bitcoin/src/transactions/legacy/mod.rs b/rust/apps/bitcoin/src/transactions/legacy/mod.rs
index 6471b25..941d77e 100644
--- a/rust/apps/bitcoin/src/transactions/legacy/mod.rs
+++ b/rust/apps/bitcoin/src/transactions/legacy/mod.rs
@@ -35,12 +35,54 @@ pub fn sign_legacy_tx(tx_data: &mut TxData, seed: &[u8]) -> Result<Vec<u8>> {
#[cfg(test)]
mod tests {
+ use super::sign_legacy_tx;
+ use super::TxData;
+ use crate::errors::BitcoinError;
use crate::test::{prepare_parse_context, prepare_payload};
+ use crate::transactions::legacy::input::TxIn;
use crate::{check_raw_tx, sign_raw_tx};
+ use alloc::string::{String, ToString};
use app_utils::keystone;
+ use bitcoin::absolute::LockTime;
+ use bitcoin::transaction::Version;
+ use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn as BitcoinTxIn, Txid, Witness};
use core::str::FromStr;
use hex;
+ const TEST_SEED_HEX: &str = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4";
+
+ fn build_dummy_tx_data(script_type: &str, pubkey: &str) -> TxData {
+ let prev_txid_str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ let txid = Txid::from_str(prev_txid_str).unwrap();
+ let bitcoin_input = BitcoinTxIn {
+ previous_output: OutPoint { txid, vout: 0 },
+ script_sig: ScriptBuf::new(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+ witness: Witness::new(),
+ };
+
+ TxData {
+ inputs: vec![TxIn {
+ previous_output: prev_txid_str.to_string(),
+ vout: 0,
+ value: 1_000,
+ pubkey: pubkey.to_string(),
+ hd_path: "m/44'/0'/0'/0/0".to_string(),
+ }],
+ outputs: vec![],
+ script_type: script_type.to_string(),
+ network: "BTC_LEGACY".to_string(),
+ extended_pubkey: String::new(),
+ xfp: String::new(),
+ transaction: Transaction {
+ version: Version(2),
+ lock_time: LockTime::from_consensus(0),
+ input: vec![bitcoin_input],
+ output: vec![],
+ },
+ }
+ }
+
#[test]
fn test_sign_ltc_p2sh_transaction() {
// tube
@@ -252,4 +294,24 @@ mod tests {
assert!(check.is_ok());
}
}
+
+ #[test]
+ fn test_sign_legacy_tx_returns_error_for_unknown_script_type() {
+ let pubkey = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
+ let mut tx_data = build_dummy_tx_data("UNKNOWN", pubkey);
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let err = sign_legacy_tx(&mut tx_data, &seed).unwrap_err();
+ assert!(matches!(
+ err,
+ BitcoinError::UnsupportedScriptType(message) if message.contains("UNKNOWN")
+ ));
+ }
+
+ #[test]
+ fn test_sign_legacy_tx_returns_error_for_invalid_pubkey() {
+ let mut tx_data = build_dummy_tx_data("P2PKH", "zz");
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let err = sign_legacy_tx(&mut tx_data, &seed).unwrap_err();
+ assert_eq!(BitcoinError::InvalidInput, err);
+ }
}
diff --git a/rust/apps/bitcoin/src/transactions/legacy/tx_data.rs b/rust/apps/bitcoin/src/transactions/legacy/tx_data.rs
index cdebdd8..85736f8 100644
--- a/rust/apps/bitcoin/src/transactions/legacy/tx_data.rs
+++ b/rust/apps/bitcoin/src/transactions/legacy/tx_data.rs
@@ -323,6 +323,7 @@ impl TxData {
mod tests {
use super::*;
use crate::test::{prepare_parse_context, prepare_payload};
+ use crate::transactions::legacy::constants::{SIGHASH_ALL, SIGHASH_FORKID};
use crate::transactions::legacy::tx_data::TxOut;
use alloc::string::ToString;
@@ -389,6 +390,37 @@ mod tests {
);
}
+ #[test]
+ fn test_check_inputs_invalid_xfp() {
+ let hex = "1f8b08000000000000035d90cf6a935110c5632a18be4d633686ac62109442c8ccbdf3dd99bbd3c6d08d420537aee4fe99b1584b9a5a89b8f4415cb8f115dcf4010a7d83be806fe046c14fc58d3067318bc3f99d33e88f769f9d2dd755a78767ebf37559bf995ced0cfaa30103af568fc37276b1d334fbcf972f9fac0e1e2d5f8cee792cb91648f3e20acc095b99c79cfc5ccc2b39c31a939ff4a6d7971fbf7c870703f7b33fb8ba3bfc7c73f6e946f310414128040ce640a56a27884256384b0e2e7224c59c8422e5564939438ad55a32b39a6dd89b1c34fbe035ab1a86a2482187448c1ca16d63086c2495a97a0b46ea29b9e22b082791ecbb9824d9098dbfde99ed3e5d10dd5fc0df5bd01fc0ae4a85d6392c0a05d8aa7122c939f36fc45c09b85b8624f9a089c9aab49ab2380da5ade8e41f60a6d0fd41318ac51858adad013b5b04344dde77b50aa690108d0b1278f444d179d36e19d3f18fdbff03c29e6ff672c1cdf1db0da5f7f1f5e96673f2ee646b47af5a3a3edf1eb9181c7f3016e7797b3afc766bdc9bf5a61787bf001ef38242e5010000";
+ let pubkey_str = "xpub6Ch68rD9nNm8AReQe9VwV6Mi67okew4oiYoNpKAESTvmjoDn5dBUQ9A4oqdxc9VpveH1cxWeH237HLFSmC37gRVwxnL4KvFYxCQqfprRinW";
+ let payload = prepare_payload(hex);
+ let context = prepare_parse_context(pubkey_str);
+ let mut tx_data = TxData::from_payload(payload, &context).unwrap();
+ tx_data.xfp = "00000000".to_string();
+ let err = tx_data.check_inputs(&context).unwrap_err();
+ assert!(matches!(err, BitcoinError::InvalidParseContext(_)));
+ }
+
+ #[test]
+ fn test_check_inputs_without_my_inputs() {
+ let hex = "1f8b08000000000000035d90cf6a935110c5632a18be4d633686ac62109442c8ccbdf3dd99bbd3c6d08d420537aee4fe99b1584b9a5a89b8f4415cb8f115dcf4010a7d83be806fe046c14fc58d3067318bc3f99d33e88f769f9d2dd755a78767ebf37559bf995ced0cfaa30103af568fc37276b1d334fbcf972f9fac0e1e2d5f8cee792cb91648f3e20acc095b99c79cfc5ccc2b39c31a939ff4a6d7971fbf7c870703f7b33fb8ba3bfc7c73f6e946f310414128040ce640a56a27884256384b0e2e7224c59c8422e5564939438ad55a32b39a6dd89b1c34fbe035ab1a86a2482187448c1ca16d63086c2495a97a0b46ea29b9e22b082791ecbb9824d9098dbfde99ed3e5d10dd5fc0df5bd01fc0ae4a85d6392c0a05d8aa7122c939f36fc45c09b85b8624f9a089c9aab49ab2380da5ade8e41f60a6d0fd41318ac51858adad013b5b04344dde77b50aa690108d0b1278f444d179d36e19d3f18fdbff03c29e6ff672c1cdf1db0da5f7f1f5e96673f2ee646b47af5a3a3edf1eb9181c7f3016e7797b3afc766bdc9bf5a61787bf001ef38242e5010000";
+ let pubkey_str = "xpub6Ch68rD9nNm8AReQe9VwV6Mi67okew4oiYoNpKAESTvmjoDn5dBUQ9A4oqdxc9VpveH1cxWeH237HLFSmC37gRVwxnL4KvFYxCQqfprRinW";
+ let payload = prepare_payload(hex);
+ let context = prepare_parse_context(pubkey_str);
+ let mut tx_data = TxData::from_payload(payload, &context).unwrap();
+ tx_data.xfp = hex::encode(context.master_fingerprint).to_uppercase();
+ for input in tx_data.inputs.iter_mut() {
+ let mut wrong_pubkey = input.pubkey.clone();
+ let last_char = wrong_pubkey.pop().unwrap();
+ let replacement = if last_char == '0' { '1' } else { '0' };
+ wrong_pubkey.push(replacement);
+ input.pubkey = wrong_pubkey;
+ }
+ let err = tx_data.check_inputs(&context).unwrap_err();
+ assert_eq!(BitcoinError::NoMyInputs, err);
+ }
+
#[test]
fn test_p2pkh_signature_hash_bch() {
let hex = "1f8b08000000000000030d8d3b4e025114400336131a81ca580131c14c3299f7eefb77061a4b7507f77d2e062423cc88ba0a57a05bb07709d616962cc0d858183b2739d5494e4ed61d1e5e6ee7554ca38b6dd554a1ba397ee9b6363322a8880c274fdddec16c7e3e3c09c149ed131436c95048f0a94001bc501a95401b48713d7afddebffdb1d3eceab393bd0ffa1ff9e4b9d33bd3367a9242a1f4481645f049f3e0c8a055ca5be1109c16c210779c11a04c29044ac854d2013d3fdaff8e273306de27137d88643d80e64a30b4da4432dc32cf0d3ae6a5053292c008eddb138b828cd24942026ef2c1ba94725a72a9a6256b2959ce7af9e6966a5804ba5f08803a2d564b79dd967772d72c1f77f543b3d1eb7ae518aafed7cff81f4c55a87f34010000";
@@ -403,6 +435,16 @@ mod tests {
);
}
+ #[test]
+ fn test_sig_hash_type_for_bitcoin_cash() {
+ let hex = "1f8b08000000000000030d8d3b4e025114400336131a81ca580131c14c3299f7eefb77061a4b7507f77d2e062423cc88ba0a57a05bb07709d616962cc0d858183b2739d5494e4ed61d1e5e6ee7554ca38b6dd554a1ba397ee9b6363322a8880c274fdddec16c7e3e3c09c149ed131436c95048f0a94001bc501a95401b48713d7afddebffdb1d3eceab393bd0ffa1ff9e4b9d33bd3367a9242a1f4481645f049f3e0c8a055ca5be1109c16c210779c11a04c29044ac854d2013d3fdaff8e273306de27137d88643d80e64a30b4da4432dc32cf0d3ae6a5053292c008eddb138b828cd24942026ef2c1ba94725a72a9a6256b2959ce7af9e6966a5804ba5f08803a2d564b79dd967772d72c1f77f543b3d1eb7ae518aafed7cff81f4c55a87f34010000";
+ let pubkey_str = "xpub6ByHsPNSQXTWZ7PLESMY2FufyYWtLXagSUpMQq7Un96SiThZH2iJB1X7pwviH1WtKVeDP6K8d6xxFzzoaFzF3s8BKCZx8oEDdDkNnp4owAZ";
+ let payload = prepare_payload(hex);
+ let context = prepare_parse_context(pubkey_str);
+ let tx_data = TxData::from_payload(payload, &context).unwrap();
+ assert_eq!(SIGHASH_ALL | SIGHASH_FORKID, tx_data.sig_hash_type());
+ }
+
#[test]
fn test_p2wpkh_signature_hash() {
let hex = "1f8b0800000000000003658d3b4b42611c87f5d07070e9e4244e22412188effdb29517caa130921ae5ffbee73d8a17f498e6e553b4340611f8151a823e41d027696b288820ad31f80dcff27b1edf4b6f9f8d2bc3d0e51ae3e1646887fdec97e77b695f2259ab554525ffe6a576cacd4aebf4b059bfa8b5ce6b4797f5667a5771ee8cc3bc4811124566382d2ab05004ee2270cc1029c36c22f7f8f9bdfa40fb3e5979fe6b2678d8cadf275307c2027296600c587050a112064ba4003bc3a5c004bbc882309a6b4d210a29a20c21b329291552428264f6385546c49ad051a5b1a2c2ad554e6bc04681d09668ceb4e31b00bb4ea9b5433106045b4c8120c443a121f37e97ce072725c5f64ae877b8444981a50ac6e2b877153398ebee288e07d3c12ceab439eb4d661da20591cb482a42e56c14dcbc2433897ca250fd7bcdf56241898d05a1f162acd4a2ddebb6afbbedfe14f59642f0100d74a483dba72093fcd7a6b9e7c60f67e05bf594010000";
diff --git a/rust/apps/bitcoin/src/transactions/psbt/parsed_psbt.rs b/rust/apps/bitcoin/src/transactions/psbt/parsed_psbt.rs
index 8ff92e8..13b48c4 100644
--- a/rust/apps/bitcoin/src/transactions/psbt/parsed_psbt.rs
+++ b/rust/apps/bitcoin/src/transactions/psbt/parsed_psbt.rs
@@ -96,7 +96,29 @@ mod tests {
use super::*;
use crate::parsed_tx::TxParser;
+ use bitcoin::absolute::LockTime;
use bitcoin::psbt::Psbt;
+ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
+ use bitcoin::transaction::Version;
+ use bitcoin::Transaction;
+
+ fn dummy_secp_public_key() -> PublicKey {
+ let secp = Secp256k1::new();
+ let sk =
+ SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
+ .unwrap();
+ PublicKey::from_secret_key(&secp, &sk)
+ }
+
+ fn empty_psbt() -> Psbt {
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: LockTime::ZERO,
+ input: Vec::new(),
+ output: Vec::new(),
+ };
+ Psbt::from_unsigned_tx(tx).unwrap()
+ }
#[test]
fn test_parse_psbt() {
@@ -337,21 +359,113 @@ mod tests {
assert_eq!("Avalanche BTC", result.overview.network);
- println!("{:?}", result);
- let first_input = result.detail.from.get(0).unwrap();
+ let first_input = result.detail.from.first().unwrap();
assert_eq!(
"bc1q6kj04arn7nasy6vhtjqlg4fzwx6cjrewgdczdt",
first_input.address.clone().unwrap()
);
- assert_eq!(true, first_input.path.is_some());
+ assert!(first_input.path.is_some());
assert_eq!(47990, first_input.value);
- let first_output = result.detail.to.get(0).unwrap();
+ let first_output = result.detail.to.first().unwrap();
assert_eq!(
"bc1q8nur2k3xphnsqa5zxgjl7djtkj3ya0gf6rpqa9",
first_output.address
);
- assert_eq!(false, first_output.path.is_some());
+ assert!(first_output.path.is_none());
assert_eq!(48000, first_output.value);
}
+
+ #[test]
+ fn test_determine_network_from_xpub() {
+ let mut psbt = empty_psbt();
+ let xpub = Xpub::from_str("xpub6Bm9M1SxZdzL3TxdNV8897FgtTLBgehR1wVNnMyJ5VLRK5n3tFqXxrCVnVQj4zooN4eFSkf6Sma84reWc5ZCXMxPbLXQs3BcaBdTd4YQa3B").unwrap();
+ let fingerprint = Fingerprint::from_str("A1B2C3D4").unwrap();
+ let path = DerivationPath::from_str("m/84'/0'/0'").unwrap();
+ psbt.xpub.insert(xpub, (fingerprint, path));
+
+ let wrapped = WrappedPsbt { psbt };
+ let network = wrapped.determine_network().unwrap();
+ assert!(matches!(network, Network::Bitcoin));
+ }
+
+ #[test]
+ fn test_determine_network_from_coin_type() {
+ let mut psbt = empty_psbt();
+ let mut input = bitcoin::psbt::Input::default();
+ let pubkey = dummy_secp_public_key();
+ input.bip32_derivation.insert(
+ pubkey,
+ (
+ Fingerprint::from_str("73c5da0a").unwrap(),
+ DerivationPath::from_str("m/84'/145'/0'/0/0").unwrap(),
+ ),
+ );
+ psbt.inputs.push(input);
+
+ let wrapped = WrappedPsbt { psbt };
+ let network = wrapped.determine_network().unwrap();
+ assert!(matches!(network, Network::BitcoinCash));
+ }
+
+ #[test]
+ fn test_determine_network_unknown_coin_type() {
+ let mut psbt = empty_psbt();
+ let mut input = bitcoin::psbt::Input::default();
+ let pubkey = dummy_secp_public_key();
+ input.bip32_derivation.insert(
+ pubkey,
+ (
+ Fingerprint::from_str("73c5da0a").unwrap(),
+ DerivationPath::from_str("m/84'/999'/0'/0/0").unwrap(),
+ ),
+ );
+ psbt.inputs.push(input);
+
+ let wrapped = WrappedPsbt { psbt };
+ let err = wrapped.determine_network().unwrap_err();
+ assert!(matches!(
+ err,
+ BitcoinError::InvalidTransaction(message) if message.contains("unknown network 999")
+ ));
+ }
+
+ #[test]
+ fn test_determine_network_with_non_hardened_coin_type() {
+ let mut psbt = empty_psbt();
+ let mut input = bitcoin::psbt::Input::default();
+ let pubkey = dummy_secp_public_key();
+ input.bip32_derivation.insert(
+ pubkey,
+ (
+ Fingerprint::from_str("73c5da0a").unwrap(),
+ DerivationPath::from_str("m/84/1/0/0").unwrap(),
+ ),
+ );
+ psbt.inputs.push(input);
+
+ let wrapped = WrappedPsbt { psbt };
+ let err = wrapped.determine_network().unwrap_err();
+ assert!(matches!(
+ err,
+ BitcoinError::InvalidTransaction(message)
+ if message.contains("unsupported derivation path")
+ ));
+ }
+
+ #[test]
+ fn test_parse_without_context() {
+ let mut psbt = empty_psbt();
+ let xpub = Xpub::from_str("tpubDDfvzhdVV4unsoKt5aE6dcsNsfeWbTgmLZPi8LQDYU2xixrYemMfWJ3BaVneH3u7DBQePdTwhpybaKRU95pi6PMUtLPBJLVQRpzEnjfjZzX").unwrap();
+ let fingerprint = Fingerprint::from_str("73c5da0a").unwrap();
+ let path = DerivationPath::from_str("m/84'/1'/0'").unwrap();
+ psbt.xpub.insert(xpub, (fingerprint, path));
+
+ let wrapped = WrappedPsbt { psbt };
+ let err = wrapped.parse(None).unwrap_err();
+ assert!(matches!(
+ err,
+ BitcoinError::InvalidParseContext(message) if message == "empty context"
+ ));
+ }
}
diff --git a/rust/apps/bitcoin/src/transactions/psbt/wrapped_psbt.rs b/rust/apps/bitcoin/src/transactions/psbt/wrapped_psbt.rs
index ad55cf7..0a8bc06 100644
--- a/rust/apps/bitcoin/src/transactions/psbt/wrapped_psbt.rs
+++ b/rust/apps/bitcoin/src/transactions/psbt/wrapped_psbt.rs
@@ -1,4 +1,5 @@
use crate::errors::{BitcoinError, Result};
+use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use bitcoin::script::Instruction;
@@ -7,7 +8,6 @@ use itertools::Itertools;
use crate::addresses::address::Address;
use crate::network::{self, CustomNewNetwork};
use crate::transactions::parsed_tx::{ParseContext, ParsedInput, ParsedOutput, TxParser};
-use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::ops::Index;
@@ -387,6 +387,9 @@ impl WrappedPsbt {
}
}
}
+ if self.is_taproot_input(input) {
+ return self.get_taproot_sign_status(input);
+ }
//there might be a (x, 0) forms of sign status which we don't care;
(
input.partial_sigs.len() as u32,
@@ -416,6 +419,8 @@ impl WrappedPsbt {
}
}
Ok(true)
+ } else if self.is_taproot_input(input) {
+ Ok(!self.has_signed_taproot_input(input, context)?)
} else {
Ok(input.partial_sigs.is_empty())
}
@@ -802,6 +807,56 @@ impl WrappedPsbt {
None
}
+ fn has_signed_taproot_input(&self, input: &Input, context: &ParseContext) -> Result<bool> {
+ for (pk, (leaf_hashes, (fingerprint, _))) in input.tap_key_origins.iter() {
+ if *fingerprint != context.master_fingerprint {
+ continue;
+ }
+ if leaf_hashes.is_empty() {
+ if input.tap_key_sig.is_some() {
+ return Ok(true);
+ }
+ } else {
+ for leaf_hash in leaf_hashes {
+ if input.tap_script_sigs.contains_key(&(*pk, *leaf_hash)) {
+ return Ok(true);
+ }
+ }
+ }
+ }
+ Ok(false)
+ }
+
+ fn get_taproot_sign_status(&self, input: &Input) -> (u32, u32) {
+ let mut signed = 0u32;
+ // for key path spend, it requires only 1 signature.
+ let mut required = 1u32;
+
+ if input.tap_key_sig.is_some() {
+ signed += 1;
+ }
+
+ if !input.tap_script_sigs.is_empty() {
+ // every key generates a signature for each leaf script, see https://github.com/rust-bitcoin/rust-bitcoin/blob/master/bitcoin/src/psbt/mod.rs#L461
+ // but the actual requirements depends on the script content.
+ // we don't parse the taproot script content for now, so we use a max required value here.
+ let max_required = input
+ .tap_key_origins
+ .iter()
+ .fold(0, |acc, (_, (leaf_hashes, _))| {
+ acc + leaf_hashes.len() as u32
+ });
+ let mut unique_signers: BTreeSet<[u8; 32]> = BTreeSet::new();
+ for &(pk, _) in input.tap_script_sigs.keys() {
+ unique_signers.insert(pk.serialize());
+ }
+ signed += unique_signers.len() as u32;
+ // for script path spend, we don't know the required number, so we set it to 0xff.
+ required = max_required;
+ }
+ (signed, required)
+ }
+
fn is_taproot_input(&self, input: &Input) -> bool {
if let Some(witness_utxo) = &input.witness_utxo {
return witness_utxo.script_pubkey.is_p2tr();
@@ -854,11 +909,34 @@ mod tests {
use core::str::FromStr;
use crate::TxChecker;
+ use bitcoin::absolute::LockTime;
+ use bitcoin::ecdsa::Signature;
+ use bitcoin::psbt::Input;
+ use bitcoin::secp256k1::{Secp256k1, SecretKey};
+ use bitcoin::transaction::Version;
+ use bitcoin::{ScriptBuf, Transaction};
use either::Left;
use hex::{self, FromHex, ToHex};
use super::*;
+ fn empty_psbt() -> Psbt {
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: LockTime::ZERO,
+ input: Vec::new(),
+ output: Vec::new(),
+ };
+ Psbt::from_unsigned_tx(tx).unwrap()
+ }
+
+ fn dummy_pubkey() -> secp256k1::PublicKey {
+ let sk =
+ SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
+ .unwrap();
+ secp256k1::PublicKey::from_secret_key(&Secp256k1::new(), &sk)
+ }
+
#[test]
fn test_new_sign() {
{
@@ -1016,7 +1094,102 @@ mod tests {
verify_code: Some("12345678".to_string()),
multisig_wallet_config: None,
}));
- assert_eq!(true, reust.is_err());
+ assert!(reust.is_err());
}
}
+
+ #[test]
+ fn test_get_multi_sig_input_threshold_and_total() {
+ let mut pk1 = vec![0x03];
+ pk1.extend([0x11; 32]);
+ let mut pk2 = vec![0x03];
+ pk2.extend([0x22; 32]);
+ let mut pk3 = vec![0x03];
+ pk3.extend([0x33; 32]);
+ let pk1 = bitcoin::script::PushBytesBuf::try_from(pk1).unwrap();
+ let pk2 = bitcoin::script::PushBytesBuf::try_from(pk2).unwrap();
+ let pk3 = bitcoin::script::PushBytesBuf::try_from(pk3).unwrap();
+ let script = ScriptBuf::builder()
+ .push_opcode(bitcoin::opcodes::all::OP_PUSHNUM_2)
+ .push_slice(pk1.clone())
+ .push_slice(pk2.clone())
+ .push_slice(pk3.clone())
+ .push_opcode(bitcoin::opcodes::all::OP_PUSHNUM_3)
+ .push_opcode(bitcoin::opcodes::all::OP_CHECKMULTISIG)
+ .into_script();
+ let wrapper = WrappedPsbt { psbt: empty_psbt() };
+ let (threshold, total) = wrapper
+ .get_multi_sig_input_threshold_and_total(&script)
+ .unwrap();
+ assert_eq!(threshold, 2);
+ assert_eq!(total, 3);
+
+ let invalid_script = ScriptBuf::builder()
+ .push_opcode(bitcoin::opcodes::all::OP_RETURN)
+ .into_script();
+ let err = wrapper
+ .get_multi_sig_input_threshold_and_total(&invalid_script)
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ BitcoinError::MultiSigInputError(message) if message == "it's not a multi sig script"
+ ));
+ }
+
+ #[test]
+ fn test_judge_external_key() {
+ assert!(WrappedPsbt::judge_external_key(
+ "M/84'/0'/0'/0/15".to_string(),
+ "M/84'/0'/0'/".to_string()
+ ));
+ assert!(!WrappedPsbt::judge_external_key(
+ "M/84'/0'/0'/1/3".to_string(),
+ "M/84'/0'/0'/".to_string()
+ ));
+ }
+
+ #[test]
+ fn test_get_overall_sign_status_variants() {
+ let wrapper_empty = WrappedPsbt { psbt: empty_psbt() };
+ assert!(wrapper_empty.get_overall_sign_status().is_none());
+
+ let mut psbt_unsigned = empty_psbt();
+ let mut unsigned_input = Input::default();
+ unsigned_input.bip32_derivation.insert(
+ dummy_pubkey(),
+ (
+ Fingerprint::from_str("73c5da0a").unwrap(),
+ DerivationPath::from_str("m/84'/0'/0'/0/0").unwrap(),
+ ),
+ );
+ psbt_unsigned.inputs.push(unsigned_input);
+ let wrapper_unsigned = WrappedPsbt {
+ psbt: psbt_unsigned,
+ };
+ assert_eq!(
+ wrapper_unsigned.get_overall_sign_status(),
+ Some("Unsigned".to_string())
+ );
+
+ let mut psbt_signed = empty_psbt();
+ let mut signed_input = Input::default();
+ let key = dummy_pubkey();
+ signed_input.bip32_derivation.insert(
+ key,
+ (
+ Fingerprint::from_str("73c5da0a").unwrap(),
+ DerivationPath::from_str("m/84'/0'/0'/0/0").unwrap(),
+ ),
+ );
+ let signature = Signature::from_str("3045022100e2b9a7963bed429203bbd73e5ea000bfe58e3fc46ef8c1939e8cf8d1cf8460810220587ba791fc2a42445db70e2b3373493a19e6d5c47a2af0447d811ff479721b0001").unwrap();
+ signed_input
+ .partial_sigs
+ .insert(bitcoin::PublicKey::new(key), signature);
+ psbt_signed.inputs.push(signed_input);
+ let wrapper_signed = WrappedPsbt { psbt: psbt_signed };
+ assert_eq!(
+ wrapper_signed.get_overall_sign_status(),
+ Some("Completed".to_string())
+ );
+ }
}
diff --git a/rust/apps/zcash/src/pczt/parse.rs b/rust/apps/zcash/src/pczt/parse.rs
index dc15cc0..c2857fa 100644
--- a/rust/apps/zcash/src/pczt/parse.rs
+++ b/rust/apps/zcash/src/pczt/parse.rs
@@ -722,7 +722,7 @@ mod tests {
let value = u64::MAX as f64;
let result = format_zec_value(value);
assert!(result.ends_with(" ZEC"));
-
+
// Test max realistic ZEC value (21 million ZEC = 2.1e15 zatoshis)
let max_zec = 2_100_000_000_000_000_u64;
let result = format_zec_value(max_zec as f64);
@@ -897,17 +897,16 @@ mod tests {
fn test_format_zec_value_typical_transaction_amounts() {
// Test common transaction amounts
let test_cases = vec![
- (10_000_000u64, "0.1 ZEC"), // 0.1 ZEC
- (50_000_000u64, "0.5 ZEC"), // 0.5 ZEC
- (100_000_000u64, "1 ZEC"), // 1 ZEC
- (250_000_000u64, "2.5 ZEC"), // 2.5 ZEC
- (1_000_000_000u64, "10 ZEC"), // 10 ZEC
- (10_000_000_000u64, "100 ZEC"), // 100 ZEC
+ (10_000_000u64, "0.1 ZEC"), // 0.1 ZEC
+ (50_000_000u64, "0.5 ZEC"), // 0.5 ZEC
+ (100_000_000u64, "1 ZEC"), // 1 ZEC
+ (250_000_000u64, "2.5 ZEC"), // 2.5 ZEC
+ (1_000_000_000u64, "10 ZEC"), // 10 ZEC
+ (10_000_000_000u64, "100 ZEC"), // 100 ZEC
];
for (input, expected) in test_cases {
assert_eq!(format_zec_value(input as f64), expected);
}
}
-
}
diff --git a/rust/rust_c/src/zcash/mod.rs b/rust/rust_c/src/zcash/mod.rs
index bb0fff0..48cef70 100644
--- a/rust/rust_c/src/zcash/mod.rs
+++ b/rust/rust_c/src/zcash/mod.rs
@@ -8,7 +8,7 @@ use crate::common::{
ur::{UREncodeResult, FRAGMENT_MAX_LENGTH_DEFAULT},
utils::{convert_c_char, recover_c_char},
};
-use crate::{extract_array_mut, extract_array};
+use crate::{extract_array, extract_array_mut};
use crate::{extract_ptr_with_type, make_free_method};
use alloc::{boxed::Box, format, string::String, string::ToString};
use app_zcash::get_address;
diff --git a/src/ui/gui_chain/gui_btc.c b/src/ui/gui_chain/gui_btc.c
index 317a0df..f695ca4 100644
--- a/src/ui/gui_chain/gui_btc.c
+++ b/src/ui/gui_chain/gui_btc.c
@@ -16,7 +16,6 @@
#include "gui_multisig_transaction_signature_widgets.h"
#endif
-
#define CHECK_FREE_PARSE_RESULT(result) \
if (result != NULL) \
{ \
@@ -106,6 +105,10 @@ static int32_t GuiGetUtxoPubKeyAndHdPath(ViewType viewType, char **xPub, char **
}
#endif
+__attribute__((weak)) UREncodeResult *GuiGetSignPsbtBytesCodeData(void)
+{
+ return NULL;
+}
#ifdef BTC_ONLY
static UREncodeResult *GuiGetSignPsbtBytesCodeData(void)
{
@@ -123,11 +126,13 @@ static UREncodeResult *GuiGetSignPsbtBytesCodeData(void)
uint8_t mfp[4] = {0};
GetMasterFingerPrint(mfp);
uint8_t seed[64];
- int len = GetMnemonicType() == MNEMONIC_TYPE_BIP39 ? sizeof(seed) : GetCurrentAccountEntropyLen();
- GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
+ int len = GetCurrentAccountSeedLen();
+ int ret = GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
+ CHECK_ERRCODE_RETURN("GetAccountSeed", ret);
MultisigSignResult *result = btc_sign_multisig_psbt_bytes(g_psbtBytes, g_psbtBytesLen, seed, len, mfp, sizeof(mfp));
encodeResult = result->ur_result;
GuiMultisigTransactionSignatureSetSignStatus(result->sign_status, result->is_completed, result->psbt_hex, result->psbt_len);
+ memset_s(seed, sizeof(seed), 0, sizeof(seed));
free_MultisigSignResult(result);
}
CHECK_CHAIN_PRINT(encodeResult);
@@ -147,74 +152,95 @@ UREncodeResult *GuiGetBtcSignUrDataUnlimited(void)
return GetBtcSignDataDynamic(true);
}
+static UREncodeResult *BtcSignPsbt(void *data, uint8_t *seed, int len, uint8_t *mfp, bool unLimit) {
+ UREncodeResult *encodeResult = NULL;
+ if (GuiGetCurrentTransactionNeedSign()) {
+ if (unLimit) {
+ encodeResult = btc_sign_psbt_unlimited(data, seed, len, mfp, sizeof(mfp));
+ } else {
+ encodeResult = btc_sign_psbt(data, seed, len, mfp, sizeof(mfp));
+ }
+ }
+ return encodeResult;
+}
+
+static UREncodeResult *BtcSignPsbtMultisig(void *data, uint8_t *seed, int len, uint8_t *mfp) {
+#ifdef BTC_ONLY
+ UREncodeResult *encodeResult = NULL;
+ if (!GuiGetCurrentTransactionNeedSign()) {
+ MultisigSignResult *result = btc_export_multisig_psbt(data);
+ encodeResult = result->ur_result;
+ GuiMultisigTransactionSignatureSetSignStatus(result->sign_status, result->is_completed, result->psbt_hex, result->psbt_len);
+ free_MultisigSignResult(result);
+ } else {
+ encodeResult = btc_sign_multisig_psbt(data, seed, len, mfp, sizeof(mfp));
+ }
+ return encodeResult;
+#else
+ return NULL;
+#endif
+}
+
+static bool SupportSignPsbtFromSDCard(void) {
+#ifdef BTC_ONLY
+ return true;
+#else
+ return false;
+#endif
+}
+
+static bool SupportSignLegacyKeystoneTransactions(QRCodeType urType) {
+#ifdef WEB3_VERSION
+ return (urType == Bytes || urType == KeystoneSignRequest);
+#else
+ return false;
+#endif
+}
+
+static bool SupportSignPsbtExtend(QRCodeType urType) {
+#ifdef WEB3_VERSION
+ return (urType == CryptoPSBTExtend);
+#else
+ return false;
+#endif
+}
+
// The results here are released in the close qr timer species
static UREncodeResult *GetBtcSignDataDynamic(bool unLimit)
{
-#ifdef BTC_ONLY
- if (g_psbtBytes != NULL) {
+ if (SupportSignPsbtFromSDCard() && g_psbtBytes != NULL) {
return GuiGetSignPsbtBytesCodeData();
}
-#endif
bool enable = IsPreviousLockScreenEnable();
SetLockScreen(false);
enum QRCodeType urType = URTypeUnKnown;
-#ifndef BTC_ONLY
enum ViewType viewType = ViewTypeUnKnown;
-#endif
void *data = NULL;
if (g_isMulti) {
urType = g_urMultiResult->ur_type;
-#ifndef BTC_ONLY
viewType = g_urMultiResult->t;
-#endif
data = g_urMultiResult->data;
} else {
urType = g_urResult->ur_type;
-#ifndef BTC_ONLY
viewType = g_urResult->t;
-#endif
data = g_urResult->data;
}
UREncodeResult *encodeResult = NULL;
uint8_t mfp[4] = {0};
GetMasterFingerPrint(mfp);
uint8_t seed[64];
- int len = GetMnemonicType() == MNEMONIC_TYPE_BIP39 ? sizeof(seed) : GetCurrentAccountEntropyLen();
- GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
+ int len = GetCurrentAccountSeedLen();
+ int ret = GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
+ CHECK_ERRCODE_RETURN("GetAccountSeed", ret);
if (urType == CryptoPSBT) {
- if (!GuiGetCurrentTransactionNeedSign()) {
-#ifdef BTC_ONLY
- if (GuiGetCurrentTransactionType() == TRANSACTION_TYPE_BTC_MULTISIG) {
- MultisigSignResult *result = btc_export_multisig_psbt(data);
- encodeResult = result->ur_result;
- GuiMultisigTransactionSignatureSetSignStatus(result->sign_status, result->is_completed, result->psbt_hex, result->psbt_len);
- free_MultisigSignResult(result);
- }
-#endif
+ if (GuiGetCurrentTransactionType() == TRANSACTION_TYPE_BTC_MULTISIG) {
+ encodeResult = BtcSignPsbtMultisig(data, seed, len, mfp);
} else {
- uint8_t mfp[4] = {0};
- GetMasterFingerPrint(mfp);
-#ifdef BTC_ONLY
- if (GuiGetCurrentTransactionType() == TRANSACTION_TYPE_BTC_MULTISIG) {
- MultisigSignResult *result = btc_sign_multisig_psbt(data, seed, len, mfp, sizeof(mfp));
- encodeResult = result->ur_result;
- GuiMultisigTransactionSignatureSetSignStatus(result->sign_status, result->is_completed, result->psbt_hex, result->psbt_len);
- free_MultisigSignResult(result);
- } else {
- encodeResult = btc_sign_psbt(data, seed, len, mfp, sizeof(mfp));
- }
-#else
- if (unLimit) {
- encodeResult = btc_sign_psbt_unlimited(data, seed, len, mfp, sizeof(mfp));
- } else {
- encodeResult = btc_sign_psbt(data, seed, len, mfp, sizeof(mfp));
- }
-#endif
+ encodeResult = BtcSignPsbt(data, seed, len, mfp, unLimit);
}
}
-#ifndef BTC_ONLY
- else if (CHECK_UR_TYPE()) {
+ else if (SupportSignLegacyKeystoneTransactions(urType)) {
char *hdPath = NULL;
char *xPub = NULL;
if (0 != GuiGetUtxoPubKeyAndHdPath(viewType, &xPub, &hdPath)) {
@@ -222,22 +248,25 @@ static UREncodeResult *GetBtcSignDataDynamic(bool unLimit)
}
encodeResult = utxo_sign_keystone(data, urType, mfp, sizeof(mfp), xPub, SOFTWARE_VERSION, seed, len);
}
-#endif
else if (urType == BtcSignRequest) {
encodeResult = btc_sign_msg(data, seed, len, mfp, sizeof(mfp));
} else if (urType == SeedSignerMessage) {
encodeResult = sign_seed_signer_message(data, seed, len);
-#ifdef WEB3_VERSION
- } else if (urType == CryptoPSBTExtend) {
+ } else if (SupportSignPsbtExtend(urType)) {
encodeResult = utxo_sign_psbt_extend(data, seed, len, mfp, sizeof(mfp), unLimit);
-#endif
}
CHECK_CHAIN_PRINT(encodeResult);
+ memset_s(seed, sizeof(seed), 0, sizeof(seed));
ClearSecretCache();
SetLockScreen(enable);
return encodeResult;
}
+__attribute__((weak)) void *GuiGetParsedPsbtStrData(void)
+{
+ return NULL;
+}
+
#ifdef BTC_ONLY
static void *GuiGetParsedPsbtStrData(void)
{
@@ -299,34 +328,7 @@ static void *GuiGetParsedPsbtStrData(void)
}
#endif
-void *GuiGetParsedQrData(void)
-{
-#ifdef BTC_ONLY
- if (g_psbtBytes != NULL) {
- return GuiGetParsedPsbtStrData();
- }
-#endif
- enum QRCodeType urType = URTypeUnKnown;
-#ifndef BTC_ONLY
- enum ViewType viewType = ViewTypeUnKnown;
-#endif
- void *crypto = NULL;
- if (g_isMulti) {
- crypto = g_urMultiResult->data;
- urType = g_urMultiResult->ur_type;
-#ifndef BTC_ONLY
- viewType = g_urMultiResult->t;
-#endif
- } else {
- crypto = g_urResult->data;
- urType = g_urResult->ur_type;
-#ifndef BTC_ONLY
- viewType = g_urResult->t;
-#endif
- }
- uint8_t mfp[4] = {0};
- GetMasterFingerPrint(mfp);
- PtrT_CSliceFFI_ExtendedPublicKey public_keys = SRAM_MALLOC(sizeof(CSliceFFI_ExtendedPublicKey));
+static void PreparePublicKeys(PtrT_CSliceFFI_ExtendedPublicKey public_keys) {
#ifdef BTC_ONLY
ExtendedPublicKey keys[14];
public_keys->data = keys;
@@ -387,60 +389,91 @@ void *GuiGetParsedQrData(void)
keys[8].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BCH);
#endif
#endif
- do {
- if (urType == CryptoPSBT) {
+}
+
+static void *ParsePsbt(void *crypto, uint8_t *mfp, PtrT_CSliceFFI_ExtendedPublicKey public_keys) {
+ g_parseResult = NULL;
#ifdef BTC_ONLY
- char *wallet_config = NULL;
- if (GetCurrentWalletIndex() != SINGLE_WALLET) {
- MultiSigWalletItem_t *item = GetDefaultMultisigWallet();
- if (item != NULL) {
- wallet_config = SRAM_MALLOC(MAX_WALLET_CONFIG_LEN);
- memset_s(wallet_config, MAX_WALLET_CONFIG_LEN, '\0', MAX_WALLET_CONFIG_LEN);
- strncpy_s(wallet_config, MAX_WALLET_CONFIG_LEN, item->walletConfig, strnlen_s(item->walletConfig, MAX_WALLET_CONFIG_LEN));
- }
- }
- g_parseResult = btc_parse_psbt(crypto, mfp, sizeof(mfp), public_keys, wallet_config);
- GuiSetCurrentTransactionNeedSign(g_parseResult->data->overview->need_sign);
- SRAM_FREE(wallet_config);
+ char *wallet_config = NULL;
+ if (GetCurrentWalletIndex() != SINGLE_WALLET) {
+ MultiSigWalletItem_t *item = GetDefaultMultisigWallet();
+ if (item != NULL) {
+ wallet_config = SRAM_MALLOC(MAX_WALLET_CONFIG_LEN);
+ memset_s(wallet_config, MAX_WALLET_CONFIG_LEN, '\0', MAX_WALLET_CONFIG_LEN);
+ strncpy_s(wallet_config, MAX_WALLET_CONFIG_LEN, item->walletConfig, strnlen_s(item->walletConfig, MAX_WALLET_CONFIG_LEN));
+ }
+ }
+ g_parseResult = btc_parse_psbt(crypto, mfp, sizeof(mfp), public_keys, wallet_config);
+ GuiSetCurrentTransactionNeedSign(g_parseResult->data->overview->need_sign);
+ SRAM_FREE(wallet_config);
#else
- g_parseResult = btc_parse_psbt(crypto, mfp, sizeof(mfp), public_keys, NULL);
+ g_parseResult = btc_parse_psbt(crypto, mfp, sizeof(mfp), public_keys, NULL);
#endif
- CHECK_CHAIN_RETURN(g_parseResult);
- if (IsMultiSigTx(g_parseResult->data)) {
- GuiSetCurrentTransactionType(TRANSACTION_TYPE_BTC_MULTISIG);
- }
- SRAM_FREE(public_keys);
- return g_parseResult;
- }
-#ifndef BTC_ONLY
- else if (CHECK_UR_TYPE()) {
- char *hdPath = NULL;
- char *xPub = NULL;
- if (0 != GuiGetUtxoPubKeyAndHdPath(viewType, &xPub, &hdPath)) {
- return NULL;
- }
- g_parseResult = utxo_parse_keystone(crypto, urType, mfp, sizeof(mfp), xPub);
- CHECK_CHAIN_RETURN(g_parseResult);
- return g_parseResult;
+ return g_parseResult;
+}
+
+void *GuiGetParsedQrData(void)
+{
+ if (SupportSignPsbtFromSDCard() && g_psbtBytes != NULL) {
+ return GuiGetParsedPsbtStrData();
+ }
+ enum QRCodeType urType = URTypeUnKnown;
+ enum ViewType viewType = ViewTypeUnKnown;
+ void *crypto = NULL;
+ if (g_isMulti) {
+ crypto = g_urMultiResult->data;
+ urType = g_urMultiResult->ur_type;
+ viewType = g_urMultiResult->t;
+ } else {
+ crypto = g_urResult->data;
+ urType = g_urResult->ur_type;
+ viewType = g_urResult->t;
+ }
+ uint8_t mfp[4] = {0};
+ GetMasterFingerPrint(mfp);
+ PtrT_CSliceFFI_ExtendedPublicKey public_keys = SRAM_MALLOC(sizeof(CSliceFFI_ExtendedPublicKey));
+ PreparePublicKeys(public_keys);
+ if (urType == CryptoPSBT) {
+ g_parseResult = ParsePsbt(crypto, mfp, public_keys);
+ SRAM_FREE(public_keys);
+ CHECK_CHAIN_RETURN(g_parseResult);
+ if (IsMultiSigTx(g_parseResult->data)) {
+ GuiSetCurrentTransactionType(TRANSACTION_TYPE_BTC_MULTISIG);
}
-#endif
- else if (urType == BtcSignRequest) {
- g_parseMsgResult = btc_parse_msg(crypto, public_keys, mfp, sizeof(mfp));
- CHECK_CHAIN_RETURN(g_parseMsgResult);
- return g_parseMsgResult;
- } else if (urType == SeedSignerMessage) {
- g_parseMsgResult = parse_seed_signer_message(crypto, public_keys);
- CHECK_CHAIN_RETURN(g_parseMsgResult);
- return g_parseMsgResult;
-#ifdef WEB3_VERSION
- } else if (urType == CryptoPSBTExtend) {
- g_parseResult = utxo_parse_extend_psbt(crypto, public_keys, mfp, sizeof(mfp));
- CHECK_CHAIN_RETURN(g_parseResult);
- return g_parseResult;
-#endif
+ return g_parseResult;
+ }
+ else if (SupportSignLegacyKeystoneTransactions(urType)) {
+ char *hdPath = NULL;
+ char *xPub = NULL;
+ if (0 != GuiGetUtxoPubKeyAndHdPath(viewType, &xPub, &hdPath)) {
+ return NULL;
}
- } while (0);
- return g_parseResult;
+ g_parseResult = utxo_parse_keystone(crypto, urType, mfp, sizeof(mfp), xPub);
+ SRAM_FREE(public_keys);
+ CHECK_CHAIN_RETURN(g_parseResult);
+ return g_parseResult;
+ } else if (SupportSignPsbtExtend(urType)) {
+ g_parseResult = utxo_parse_extend_psbt(crypto, public_keys, mfp, sizeof(mfp));
+ SRAM_FREE(public_keys);
+ CHECK_CHAIN_RETURN(g_parseResult);
+ return g_parseResult;
+ } else if (urType == BtcSignRequest) {
+ g_parseMsgResult = btc_parse_msg(crypto, public_keys, mfp, sizeof(mfp));
+ SRAM_FREE(public_keys);
+ CHECK_CHAIN_RETURN(g_parseMsgResult);
+ return g_parseMsgResult;
+ } else if (urType == SeedSignerMessage) {
+ g_parseMsgResult = parse_seed_signer_message(crypto, public_keys);
+ SRAM_FREE(public_keys);
+ CHECK_CHAIN_RETURN(g_parseMsgResult);
+ return g_parseMsgResult;
+ }
+ return NULL;
+}
+
+__attribute__((weak)) PtrT_TransactionCheckResult GuiGetPsbtStrCheckResult(void)
+{
+ return NULL;
}
#ifdef BTC_ONLY
@@ -448,39 +481,7 @@ PtrT_TransactionCheckResult GuiGetPsbtStrCheckResult(void)
{
PtrT_TransactionCheckResult result = NULL;
PtrT_CSliceFFI_ExtendedPublicKey public_keys = SRAM_MALLOC(sizeof(CSliceFFI_ExtendedPublicKey));
- ExtendedPublicKey keys[14];
- public_keys->data = keys;
- public_keys->size = 14;
- keys[0].path = "m/84'/0'/0'";
- keys[0].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_NATIVE_SEGWIT);
- keys[1].path = "m/49'/0'/0'";
- keys[1].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC);
- keys[2].path = "m/44'/0'/0'";
- keys[2].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_LEGACY);
- keys[3].path = "m/86'/0'/0'";
- keys[3].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TAPROOT);
- keys[4].path = "m/84'/1'/0'";
- keys[4].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_NATIVE_SEGWIT_TEST);
- keys[5].path = "m/49'/1'/0'";
- keys[5].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TEST);
- keys[6].path = "m/44'/1'/0'";
- keys[6].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_LEGACY_TEST);
- keys[7].path = "m/86'/1'/0'";
- keys[7].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TAPROOT_TEST);
-
- keys[8].path = "m/45'";
- keys[8].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2SH);
- keys[9].path = "m/48'/0'/0'/1'";
- keys[9].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH_P2SH);
- keys[10].path = "m/48'/0'/0'/2'";
- keys[10].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH);
- keys[11].path = "m/45'";
- keys[11].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2SH_TEST);
- keys[12].path = "m/48'/1'/0'/1'";
- keys[12].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH_P2SH_TEST);
- keys[13].path = "m/48'/1'/0'/2'";
- keys[13].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH_TEST);
-
+ PreparePublicKeys(public_keys);
uint8_t mfp[4] = {0};
GetMasterFingerPrint(mfp);
@@ -501,7 +502,6 @@ PtrT_TransactionCheckResult GuiGetPsbtStrCheckResult(void)
strncpy_s(wallet_config, MAX_WALLET_CONFIG_LEN, item->walletConfig, strnlen_s(item->walletConfig, MAX_WALLET_CONFIG_LEN));
}
}
- printf("wallet_config = %s\n", wallet_config);
result = btc_check_psbt_bytes(g_psbtBytes, g_psbtBytesLen, mfp, sizeof(mfp), public_keys, verify_without_mfp, wallet_config);
if (result->error_code != 0 && strnlen_s(verify_without_mfp, MAX_VERIFY_CODE_LEN) == 0) {
@@ -517,130 +517,68 @@ PtrT_TransactionCheckResult GuiGetPsbtStrCheckResult(void)
}
#endif
+static PtrT_TransactionCheckResult CheckPsbt(void *crypto, uint8_t *mfp, PtrT_CSliceFFI_ExtendedPublicKey public_keys) {
+ PtrT_TransactionCheckResult result = NULL;
+#ifdef BTC_ONLY
+ char *verify_without_mfp = NULL;
+ char *verify_code = NULL;
+ char *wallet_config = NULL;
+ if (GetCurrentWalletIndex() != SINGLE_WALLET) {
+ MultiSigWalletItem_t *item = GetDefaultMultisigWallet();
+ if (item != NULL) {
+ verify_without_mfp = SRAM_MALLOC(MAX_VERIFY_CODE_LEN);
+ memset_s(verify_without_mfp, MAX_VERIFY_CODE_LEN, '\0', MAX_VERIFY_CODE_LEN);
+ strncpy_s(verify_without_mfp, MAX_VERIFY_CODE_LEN, item->verifyWithoutMfp, strnlen_s(item->verifyWithoutMfp, MAX_VERIFY_CODE_LEN));
+ verify_code = SRAM_MALLOC(MAX_VERIFY_CODE_LEN);
+ memset_s(verify_code, MAX_VERIFY_CODE_LEN, '\0', MAX_VERIFY_CODE_LEN);
+ strncpy_s(verify_code, MAX_VERIFY_CODE_LEN, item->verifyCode, strnlen_s(item->verifyCode, MAX_VERIFY_CODE_LEN));
+ wallet_config = SRAM_MALLOC(MAX_WALLET_CONFIG_LEN);
+ memset_s(wallet_config, MAX_WALLET_CONFIG_LEN, '\0', MAX_WALLET_CONFIG_LEN);
+ strncpy_s(wallet_config, MAX_WALLET_CONFIG_LEN, item->walletConfig, strnlen_s(item->walletConfig, MAX_WALLET_CONFIG_LEN));
+ }
+ }
+
+ result = btc_check_psbt(crypto, mfp, sizeof(mfp), public_keys, verify_without_mfp, wallet_config);
+ if (result->error_code != 0 && strnlen_s(verify_without_mfp, MAX_VERIFY_CODE_LEN) == 0) {
+ free_TransactionCheckResult(result);
+ result = btc_check_psbt(crypto, mfp, sizeof(mfp), public_keys, verify_code, wallet_config);
+ }
+ SRAM_FREE(verify_without_mfp);
+ SRAM_FREE(verify_code);
+ SRAM_FREE(wallet_config);
+#else
+ result = btc_check_psbt(crypto, mfp, sizeof(mfp), public_keys, NULL, NULL);
+#endif
+ return result;
+}
+
PtrT_TransactionCheckResult GuiGetPsbtCheckResult(void)
{
-#ifdef BTC_ONLY
- if (g_psbtBytes != NULL) {
+ if (SupportSignPsbtFromSDCard() && g_psbtBytes != NULL) {
return GuiGetPsbtStrCheckResult();
}
-#endif
PtrT_TransactionCheckResult result = NULL;
enum QRCodeType urType = URTypeUnKnown;
-#ifndef BTC_ONLY
enum ViewType viewType = ViewTypeUnKnown;
-#endif
void *crypto = NULL;
if (g_isMulti) {
crypto = g_urMultiResult->data;
urType = g_urMultiResult->ur_type;
-#ifndef BTC_ONLY
viewType = g_urMultiResult->t;
-#endif
} else {
crypto = g_urResult->data;
urType = g_urResult->ur_type;
-#ifndef BTC_ONLY
viewType = g_urResult->t;
-#endif
}
uint8_t mfp[4] = {0};
GetMasterFingerPrint(mfp);
if (urType == CryptoPSBT) {
PtrT_CSliceFFI_ExtendedPublicKey public_keys = SRAM_MALLOC(sizeof(CSliceFFI_ExtendedPublicKey));
-#ifdef BTC_ONLY
- ExtendedPublicKey keys[14];
- public_keys->data = keys;
- public_keys->size = 14;
- keys[0].path = "m/84'/0'/0'";
- keys[0].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_NATIVE_SEGWIT);
- keys[1].path = "m/49'/0'/0'";
- keys[1].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC);
- keys[2].path = "m/44'/0'/0'";
- keys[2].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_LEGACY);
- keys[3].path = "m/86'/0'/0'";
- keys[3].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TAPROOT);
- keys[4].path = "m/84'/1'/0'";
- keys[4].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_NATIVE_SEGWIT_TEST);
- keys[5].path = "m/49'/1'/0'";
- keys[5].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TEST);
- keys[6].path = "m/44'/1'/0'";
- keys[6].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_LEGACY_TEST);
- keys[7].path = "m/86'/1'/0'";
- keys[7].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TAPROOT_TEST);
-
- keys[8].path = "m/45'";
- keys[8].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2SH);
- keys[9].path = "m/48'/0'/0'/1'";
- keys[9].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH_P2SH);
- keys[10].path = "m/48'/0'/0'/2'";
- keys[10].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH);
- keys[11].path = "m/45'";
- keys[11].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2SH_TEST);
- keys[12].path = "m/48'/1'/0'/1'";
- keys[12].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH_P2SH_TEST);
- keys[13].path = "m/48'/1'/0'/2'";
- keys[13].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_MULTI_SIG_P2WSH_TEST);
-#else
- ExtendedPublicKey keys[9];
- public_keys->data = keys;
- public_keys->size = 4;
- keys[0].path = "m/84'/0'/0'";
- keys[0].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_NATIVE_SEGWIT);
- keys[1].path = "m/49'/0'/0'";
- keys[1].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC);
- keys[2].path = "m/44'/0'/0'";
- keys[2].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_LEGACY);
- keys[3].path = "m/86'/0'/0'";
- keys[3].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TAPROOT);
-#ifdef WEB3_VERSION
- public_keys->size = NUMBER_OF_ARRAYS(keys);
- keys[4].path = "m/44'/60'/0'";
- keys[4].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_AVAX_BIP44_STANDARD);
- keys[5].path = "m/44'/3'/0'";
- keys[5].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_DOGE);
- // ltc、dash、bch
- keys[6].path = "m/49'/2'/0'";
- keys[6].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_LTC);
- keys[7].path = "m/44'/5'/0'";
- keys[7].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_DASH);
- keys[8].path = "m/44'/145'/0'";
- keys[8].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BCH);
-#endif
-#endif
-#ifdef BTC_ONLY
- char *verify_without_mfp = NULL;
- char *verify_code = NULL;
- char *wallet_config = NULL;
- if (GetCurrentWalletIndex() != SINGLE_WALLET) {
- MultiSigWalletItem_t *item = GetDefaultMultisigWallet();
- if (item != NULL) {
- verify_without_mfp = SRAM_MALLOC(MAX_VERIFY_CODE_LEN);
- memset_s(verify_without_mfp, MAX_VERIFY_CODE_LEN, '\0', MAX_VERIFY_CODE_LEN);
- strncpy_s(verify_without_mfp, MAX_VERIFY_CODE_LEN, item->verifyWithoutMfp, strnlen_s(item->verifyWithoutMfp, MAX_VERIFY_CODE_LEN));
- verify_code = SRAM_MALLOC(MAX_VERIFY_CODE_LEN);
- memset_s(verify_code, MAX_VERIFY_CODE_LEN, '\0', MAX_VERIFY_CODE_LEN);
- strncpy_s(verify_code, MAX_VERIFY_CODE_LEN, item->verifyCode, strnlen_s(item->verifyCode, MAX_VERIFY_CODE_LEN));
- wallet_config = SRAM_MALLOC(MAX_WALLET_CONFIG_LEN);
- memset_s(wallet_config, MAX_WALLET_CONFIG_LEN, '\0', MAX_WALLET_CONFIG_LEN);
- strncpy_s(wallet_config, MAX_WALLET_CONFIG_LEN, item->walletConfig, strnlen_s(item->walletConfig, MAX_WALLET_CONFIG_LEN));
- }
- }
-
- result = btc_check_psbt(crypto, mfp, sizeof(mfp), public_keys, verify_without_mfp, wallet_config);
- if (result->error_code != 0 && strnlen_s(verify_without_mfp, MAX_VERIFY_CODE_LEN) == 0) {
- free_TransactionCheckResult(result);
- result = btc_check_psbt(crypto, mfp, sizeof(mfp), public_keys, verify_code, wallet_config);
- }
- SRAM_FREE(verify_without_mfp);
- SRAM_FREE(verify_code);
- SRAM_FREE(wallet_config);
-#else
- result = btc_check_psbt(crypto, mfp, sizeof(mfp), public_keys, NULL, NULL);
-#endif
+ PreparePublicKeys(public_keys);
+ result = CheckPsbt(crypto, mfp, public_keys);
SRAM_FREE(public_keys);
}
-#ifndef BTC_ONLY
- else if (CHECK_UR_TYPE()) {
+ else if (SupportSignLegacyKeystoneTransactions(urType)) {
char *hdPath = NULL;
char *xPub = NULL;
if (0 != GuiGetUtxoPubKeyAndHdPath(viewType, &xPub, &hdPath)) {
@@ -648,38 +586,15 @@ PtrT_TransactionCheckResult GuiGetPsbtCheckResult(void)
}
result = utxo_check_keystone(crypto, urType, mfp, sizeof(mfp), xPub);
}
-#endif
else if (urType == BtcSignRequest) {
result = btc_check_msg(crypto, mfp, sizeof(mfp));
} else if (urType == SeedSignerMessage) {
result = tx_check_pass();
-#ifdef WEB3_VERSION
- } else if (urType == CryptoPSBTExtend) {
+ } else if (SupportSignPsbtExtend(urType)) {
PtrT_CSliceFFI_ExtendedPublicKey public_keys = SRAM_MALLOC(sizeof(CSliceFFI_ExtendedPublicKey));
- ExtendedPublicKey keys[9];
- public_keys->data = keys;
- public_keys->size = NUMBER_OF_ARRAYS(keys);
- keys[0].path = "m/84'/0'/0'";
- keys[0].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_NATIVE_SEGWIT);
- keys[1].path = "m/49'/0'/0'";
- keys[1].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC);
- keys[2].path = "m/44'/0'/0'";
- keys[2].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_LEGACY);
- keys[3].path = "m/86'/0'/0'";
- keys[3].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BTC_TAPROOT);
- keys[4].path = "m/44'/60'/0'";
- keys[4].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_AVAX_BIP44_STANDARD);
- keys[5].path = "m/44'/3'/0'";
- keys[5].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_DOGE);
- // ltc、dash、bch
- keys[6].path = "m/49'/2'/0'";
- keys[6].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_LTC);
- keys[7].path = "m/44'/5'/0'";
- keys[7].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_DASH);
- keys[8].path = "m/44'/145'/0'";
- keys[8].xpub = GetCurrentAccountPublicKey(XPUB_TYPE_BCH);
+ PreparePublicKeys(public_keys);
result = utxo_check_psbt_extend(crypto, mfp, sizeof(mfp), public_keys, NULL, NULL);
-#endif
+ SRAM_FREE(public_keys);
}
return result;
}
Why this scored 29/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.