From 387fa40d75cd7876ec1e95a30b89d734bc19c35d Mon Sep 17 00:00:00 2001 From: benma's agent Date: Tue, 21 Jul 2026 09:09:23 +0000 Subject: [PATCH] Test Bitcoin signing with shared vectors Consume the PSBT transaction vectors introduced by https://github.com/BitBoxSwiss/bitbox02-firmware/pull/2017 in simulator signing tests. The public PSBT API exercises conversion and the low-level signing protocol without a second raw fixture. Share stdout parsing and check versioned screens, errors, signature insertion, and final transactions. Teach PSBT signing to use output_script_configs for device-owned outputs from another account on firmware 9.22 and later, while preserving the older external-output behavior. --- src/btc.rs | 285 +- tests/data/btc-transaction-test-vectors.json | 4858 ++++++++++++++++++ tests/test_btc_psbt.rs | 1533 +++--- tests/test_simulator_stdout.rs | 179 + tests/util/mod.rs | 448 +- 5 files changed, 6204 insertions(+), 1099 deletions(-) create mode 100644 tests/data/btc-transaction-test-vectors.json create mode 100644 tests/test_simulator_stdout.rs diff --git a/src/btc.rs b/src/btc.rs index de0cfee..1e4ae2c 100644 --- a/src/btc.rs +++ b/src/btc.rs @@ -299,6 +299,9 @@ pub enum PsbtError { #[error("Invalid OP_RETURN script: {0}")] #[cfg_attr(feature = "wasm", assoc(js_code = "invalid-op-return"))] InvalidOpReturn(&'static str), + #[error("Account script configs must contain a BIP44 account keypath.")] + #[cfg_attr(feature = "wasm", assoc(js_code = "invalid-account-keypath"))] + InvalidAccountKeypath, } impl From for PsbtError { @@ -478,32 +481,27 @@ fn script_config_from_utxo( Err(PsbtError::UnknownOutputType) } +struct PsbtTransaction { + transaction: Transaction, + our_keys: Vec, + output_script_configs: Vec, + output_script_config_indices: Vec>, +} + impl Transaction { fn from_psbt( + firmware_version: &semver::Version, our_root_fingerprint: &[u8], psbt: &bitcoin::psbt::Psbt, force_script_config: Option, - ) -> Result<(Self, Vec), PsbtError> { - let mut script_configs: Vec = Vec::new(); - let mut is_script_config_forced = false; - if let Some(cfg) = force_script_config { - script_configs.push(cfg); - is_script_config_forced = true; - } + ) -> Result { + let is_script_config_forced = force_script_config.is_some(); + let mut script_configs = force_script_config.into_iter().collect::>(); + let mut output_script_configs: Vec = Vec::new(); let mut our_keys: Vec = Vec::new(); let mut inputs: Vec = Vec::new(); - let mut add_script_config = |script_config: pb::BtcScriptConfigWithKeypath| -> usize { - match script_configs.iter().position(|el| el == &script_config) { - Some(pos) => pos, - None => { - script_configs.push(script_config); - script_configs.len() - 1 - } - } - }; - for (input_index, (tx_input, psbt_input)) in psbt.unsigned_tx.input.iter().zip(&psbt.inputs).enumerate() { @@ -512,12 +510,15 @@ impl Transaction { let script_config_index = if is_script_config_forced { 0 } else { - add_script_config(script_config_from_utxo( - utxo, - our_key.keypath(), - psbt_input.redeem_script.as_ref(), - psbt_input.witness_script.as_ref(), - )?) + find_or_add_script_config( + &mut script_configs, + script_config_from_utxo( + utxo, + our_key.keypath(), + psbt_input.redeem_script.as_ref(), + psbt_input.witness_script.as_ref(), + )?, + ) }; inputs.push(TxInput { @@ -533,35 +534,58 @@ impl Transaction { } let mut outputs: Vec = Vec::new(); + let mut output_script_config_indices: Vec> = Vec::new(); for (tx_output, psbt_output) in psbt.unsigned_tx.output.iter().zip(&psbt.outputs) { let our_key = find_our_key(our_root_fingerprint, psbt_output); // Either change output or a non-change output owned by the BitBox. match our_key { Ok(our_key) => { - let script_config_index = if is_script_config_forced { - 0 + let script_config = if is_script_config_forced { + script_configs[0].clone() } else { - add_script_config(script_config_from_utxo( + script_config_from_utxo( tx_output, our_key.keypath(), psbt_output.redeem_script.as_ref(), psbt_output.witness_script.as_ref(), - )?) + )? }; - outputs.push(TxOutput::Internal(TxInternalOutput { - keypath: our_key.keypath(), - value: tx_output.value.to_sat(), - script_config_index: script_config_index as _, - })); + let same_account = is_same_account(&script_configs, &script_config)?; + // Firmware 9.22 added separate output configs for device-owned outputs that + // belong to another account. Older firmware must display them as external. + if same_account { + let script_config_index = + find_or_add_script_config(&mut script_configs, script_config); + outputs.push(TxOutput::Internal(TxInternalOutput { + keypath: our_key.keypath(), + value: tx_output.value.to_sat(), + script_config_index: script_config_index as _, + })); + output_script_config_indices.push(None); + } else if firmware_version >= &semver::Version::new(9, 22, 0) { + let output_script_config_index = + find_or_add_script_config(&mut output_script_configs, script_config); + outputs.push(TxOutput::Internal(TxInternalOutput { + keypath: our_key.keypath(), + value: tx_output.value.to_sat(), + // Ignored when output_script_config_index is present. + script_config_index: 0, + })); + output_script_config_indices.push(Some(output_script_config_index as _)); + } else { + outputs.push(TxOutput::External(tx_output.try_into()?)); + output_script_config_indices.push(None); + } } Err(_) => { outputs.push(TxOutput::External(tx_output.try_into()?)); + output_script_config_indices.push(None); } } } - Ok(( - Transaction { + Ok(PsbtTransaction { + transaction: Transaction { script_configs, version: psbt.unsigned_tx.version.0 as _, inputs, @@ -569,7 +593,9 @@ impl Transaction { locktime: psbt.unsigned_tx.lock_time.to_consensus_u32(), }, our_keys, - )) + output_script_configs, + output_script_config_indices, + }) } } @@ -676,6 +702,54 @@ fn is_schnorr(script_config: &pb::BtcScriptConfigWithKeypath) -> bool { is_taproot_simple(script_config) | is_taproot_policy(script_config) } +fn is_simple_script_config(script_config: &pb::BtcScriptConfigWithKeypath) -> bool { + matches!( + script_config.script_config.as_ref(), + Some(pb::BtcScriptConfig { + config: Some(pb::btc_script_config::Config::SimpleType(_)), + }) + ) +} + +fn is_same_account( + input_script_configs: &[pb::BtcScriptConfigWithKeypath], + output_script_config: &pb::BtcScriptConfigWithKeypath, +) -> Result { + for input_script_config in input_script_configs { + if is_simple_script_config(input_script_config) { + let input_account = input_script_config + .keypath + .get(2) + .ok_or(PsbtError::InvalidAccountKeypath)?; + let output_account = output_script_config + .keypath + .get(2) + .ok_or(PsbtError::InvalidAccountKeypath)?; + if input_account != output_account { + return Ok(false); + } + } else if input_script_config != output_script_config { + return Ok(false); + } + } + Ok(true) +} + +fn find_or_add_script_config( + script_configs: &mut Vec, + script_config: pb::BtcScriptConfigWithKeypath, +) -> usize { + if let Some(index) = script_configs + .iter() + .position(|existing| existing == &script_config) + { + index + } else { + script_configs.push(script_config); + script_configs.len() - 1 + } +} + impl PairedBitBox { /// Retrieves an xpub. For non-standard keypaths, a warning is displayed on the BitBox even if /// `display` is false. @@ -820,17 +894,32 @@ impl PairedBitBox { format_unit: pb::btc_sign_init_request::FormatUnit, ) -> Result>, Error> { let _api_call = self.begin_api_call().await; - self.btc_sign_inner(coin, transaction, format_unit).await + self.btc_sign_inner(coin, transaction, &[], &[], format_unit) + .await } async fn btc_sign_inner( &self, coin: pb::BtcCoin, transaction: &Transaction, + output_script_configs: &[pb::BtcScriptConfigWithKeypath], + output_script_config_indices: &[Option], format_unit: pb::btc_sign_init_request::FormatUnit, ) -> Result>, Error> { + if !output_script_config_indices.is_empty() + && output_script_config_indices.len() != transaction.outputs.len() + { + return Err(Error::BtcSign( + "output script config indices must match transaction outputs".into(), + )); + } self.validate_version(">=9.4.0")?; // anti-klepto since 9.4.0 - if transaction.script_configs.iter().any(is_taproot_simple) { + if transaction + .script_configs + .iter() + .chain(output_script_configs) + .any(is_taproot_simple) + { self.validate_version(">=9.10.0")?; // taproot since 9.10.0 } if transaction.outputs.iter().any(|output| { @@ -849,7 +938,7 @@ impl PairedBitBox { .get_next_response(Request::BtcSignInit(pb::BtcSignInitRequest { coin: coin as _, script_configs: transaction.script_configs.clone(), - output_script_configs: vec![], + output_script_configs: output_script_configs.to_vec(), version: transaction.version, num_inputs: transaction.inputs.len() as _, num_outputs: transaction.outputs.len() as _, @@ -979,7 +1068,12 @@ impl PairedBitBox { .await?; } pb::btc_sign_next_response::Type::Output => { - let tx_output: &TxOutput = &transaction.outputs[next_response.index as usize]; + let output_index = next_response.index as usize; + let tx_output: &TxOutput = &transaction.outputs[output_index]; + let output_script_config_index = output_script_config_indices + .get(output_index) + .copied() + .flatten(); let request: Request = match tx_output { TxOutput::Internal(output) => { Request::BtcSignOutput(pb::BtcSignOutputRequest { @@ -987,6 +1081,7 @@ impl PairedBitBox { value: output.value, keypath: output.keypath.to_vec(), script_config_index: output.script_config_index, + output_script_config_index, ..Default::default() }) } @@ -1034,11 +1129,25 @@ impl PairedBitBox { self.validate_version(">=9.15.0")?; let our_root_fingerprint = self.root_fingerprint_inner().await?; - let (transaction, our_keys) = - Transaction::from_psbt(&our_root_fingerprint, psbt, force_script_config)?; - let signatures = self.btc_sign_inner(coin, &transaction, format_unit).await?; - for (psbt_input, (signature, our_key)) in - psbt.inputs.iter_mut().zip(signatures.iter().zip(our_keys)) + let psbt_transaction = Transaction::from_psbt( + self.version(), + &our_root_fingerprint, + psbt, + force_script_config, + )?; + let signatures = self + .btc_sign_inner( + coin, + &psbt_transaction.transaction, + &psbt_transaction.output_script_configs, + &psbt_transaction.output_script_config_indices, + format_unit, + ) + .await?; + for (psbt_input, (signature, our_key)) in psbt + .inputs + .iter_mut() + .zip(signatures.iter().zip(psbt_transaction.our_keys)) { match our_key { OurKey::Segwit(pubkey, _) => { @@ -1201,7 +1310,6 @@ impl PairedBitBox { #[cfg(test)] mod tests { use super::*; - use crate::keypath::HARDENED; #[test] fn test_payload_from_pkscript() { @@ -1331,91 +1439,4 @@ mod tests { )) )); } - - // Test that a PSBT containing only p2wpkh inputs is converted correctly to a transaction to be - // signed by the BitBox. - #[test] - fn test_transaction_from_psbt_p2wpkh() { - use std::str::FromStr; - - // Based on mnemonic: - // route glue else try obey local kidney future teach unaware pulse exclude. - let psbt_str = "cHNidP8BAHECAAAAAfbXTun4YYxDroWyzRq3jDsWFVlsZ7HUzxiORY/iR4goAAAAAAD9////AuLCAAAAAAAAFgAUg3w5W0zt3AmxRmgA5Q6wZJUDRhUowwAAAAAAABYAFJjQqUoXDcwUEqfExu9pnaSn5XBct0ElAAABAR+ghgEAAAAAABYAFHn03igII+hp819N2Zlb5LnN8atRAQDfAQAAAAABAZ9EJlMJnXF5bFVrb1eFBYrEev3pg35WpvS3RlELsMMrAQAAAAD9////AqCGAQAAAAAAFgAUefTeKAgj6GnzX03ZmVvkuc3xq1EoRs4JAAAAABYAFKG2PzjYjknaA6lmXFqPaSgHwXX9AkgwRQIhAL0v0r3LisQ9KOlGzMhM/xYqUmrv2a5sORRlkX1fqDC8AiB9XqxSNEdb4mPnp7ylF1cAlbAZ7jMhgIxHUXylTww3bwEhA0AEOM0yYEpexPoKE3vT51uxZ+8hk9sOEfBFKOeo6oDDAAAAACIGAyNQfmAT/YLmZaxxfDwClmVNt2BkFnfQu/i8Uc/hHDUiGBKiwYlUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDnxFM7Qr9LvJwQDB9GozdTRIe3MYVuHOqT7dU2EuvHrIYEqLBiVQAAIABAACAAAAAgAEAAAAAAAAAAA=="; - - let expected_transaction = Transaction { - script_configs: vec![pb::BtcScriptConfigWithKeypath { - script_config: Some(pb::BtcScriptConfig { - config: Some(pb::btc_script_config::Config::SimpleType( - pb::btc_script_config::SimpleType::P2wpkh as _, - )), - }), - keypath: vec![84 + HARDENED, 1 + HARDENED, HARDENED], - }], - version: 2, - inputs: vec![TxInput { - prev_out_hash: vec![ - 246, 215, 78, 233, 248, 97, 140, 67, 174, 133, 178, 205, 26, 183, 140, 59, 22, - 21, 89, 108, 103, 177, 212, 207, 24, 142, 69, 143, 226, 71, 136, 40, - ], - prev_out_index: 0, - prev_out_value: 100000, - sequence: 4294967293, - keypath: "m/84'/1'/0'/0/0".try_into().unwrap(), - script_config_index: 0, - prev_tx: Some(PrevTx { - version: 1, - inputs: vec![PrevTxInput { - prev_out_hash: vec![ - 159, 68, 38, 83, 9, 157, 113, 121, 108, 85, 107, 111, 87, 133, 5, 138, - 196, 122, 253, 233, 131, 126, 86, 166, 244, 183, 70, 81, 11, 176, 195, - 43, - ], - prev_out_index: 1, - signature_script: vec![], - sequence: 4294967293, - }], - outputs: vec![ - PrevTxOutput { - value: 100000, - pubkey_script: vec![ - 0, 20, 121, 244, 222, 40, 8, 35, 232, 105, 243, 95, 77, 217, 153, - 91, 228, 185, 205, 241, 171, 81, - ], - }, - PrevTxOutput { - value: 164513320, - pubkey_script: vec![ - 0, 20, 161, 182, 63, 56, 216, 142, 73, 218, 3, 169, 102, 92, 90, - 143, 105, 40, 7, 193, 117, 253, - ], - }, - ], - locktime: 0, - }), - }], - outputs: vec![ - TxOutput::External(TxExternalOutput { - payload: Payload { - data: vec![ - 131, 124, 57, 91, 76, 237, 220, 9, 177, 70, 104, 0, 229, 14, 176, 100, - 149, 3, 70, 21, - ], - output_type: pb::BtcOutputType::P2wpkh, - }, - value: 49890, - }), - TxOutput::Internal(TxInternalOutput { - keypath: "m/84'/1'/0'/1/0".try_into().unwrap(), - value: 49960, - script_config_index: 0, - }), - ], - locktime: 2441655, - }; - let our_root_fingerprint = hex::decode("12a2c189").unwrap(); - let psbt = bitcoin::psbt::Psbt::from_str(psbt_str).unwrap(); - let (transaction, _our_keys) = - Transaction::from_psbt(&our_root_fingerprint, &psbt, None).unwrap(); - assert_eq!(transaction, expected_transaction); - } } diff --git a/tests/data/btc-transaction-test-vectors.json b/tests/data/btc-transaction-test-vectors.json new file mode 100644 index 0000000..43d1d50 --- /dev/null +++ b/tests/data/btc-transaction-test-vectors.json @@ -0,0 +1,4858 @@ +{ + "simulator_seed": "boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide", + "vectors": [ + { + "id": "taproot-key-spend", + "description": "Signs two BIP86 Taproot key-spend inputs and recognizes a BIP86 change output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100b20200000002f8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270000000000fffffffff8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270100000000ffffffff0200e1f50500000000225120cc9235442b0f4fdf2f3c39516207c389e08fed3ae6cfa90cdb820d791a9d6ba4002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001012b00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db42221169e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434319004c00739d56000080010000800000008000000000010000000117209e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434300010520c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f7852107c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f78519004c00739d56000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "taproot_key", + "pubkey": "9e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a40574343", + "sighash": "default" + } + ] + }, + { + "id": "mixed-spend", + "description": "Signs P2TR, native P2WPKH and nested P2SH-P2WPKH inputs from one previous transaction and recognizes BIP86 change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100db0200000003e3ba22f94fa7f97d19e4b6c31853ce54c220cf225952b1ba9082ba0c1ba316260000000000ffffffffe3ba22f94fa7f97d19e4b6c31853ce54c220cf225952b1ba9082ba0c1ba316260100000000ffffffffe3ba22f94fa7f97d19e4b6c31853ce54c220cf225952b1ba9082ba0c1ba316260200000000ffffffff0200e1f50500000000225120cc9235442b0f4fdf2f3c39516207c389e08fed3ae6cfa90cdb820d791a9d6ba4002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a2782094400e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc66870000000021166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a2782094400e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc668700000000220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a2782094400e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc668700000000010416001468a00bbd6de555d756e92815e523b789def6c0b022060329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0184c00739d310000800100008000000080000000000000000000010520c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f7852107c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f78519004c00739d56000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 TBTC", + "fee": "1.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 TBTC", + "fee": "1.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + }, + { + "input_index": 2, + "kind": "ecdsa", + "pubkey": "0329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0", + "sighash": "all" + } + ] + }, + { + "id": "op-return", + "description": "Signs a P2WPKH spend with a zero-value OP_RETURN output containing one printable data push and a P2WPKH change output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01006802000000012b769ad9a4c846c82a6005040bbbc22a05ce02e42a6e1787a8ba8e57977bb7080000000000ffffffff0240aeeb02000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e9500000000000000000d6a0b68656c6c6f20776f726c640000000000010052020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0180f0fa0200000000160014bfbb8f964b61fb712f04587acaebea9e103161bc0000000001011f80f0fa0200000000160014bfbb8f964b61fb712f04587acaebea9e103161bc220602e62431dfc2aa92e24d23118f8f078894ee118271ddc13715c5bd33d7d2ffe229184c00739d540000800100008000000080000000000500000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "OP_RETURN", + "body": "hello world", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.01000000 TBTC", + "fee": "0.01000000 TBTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02e62431dfc2aa92e24d23118f8f078894ee118271ddc13715c5bd33d7d2ffe229", + "sighash": "all" + } + ] + }, + { + "id": "multisig-p2wsh", + "description": "Signs the device branch of a registered 1-of-2 sortedmulti P2WSH input, recognizes descriptor-derived change, and finalizes the witness.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001189002448bf29a8299625a5e0bbba99c561bd2ea07c71bb55b72be77e00becd60000000000ffffffff02801d2c04000000002200208dd9dc6a4969db2b4ed0a01a078e16c92328ea56ba3854f7416138132d2e6e48002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002200208b7d9944e24b23ca0e5d02a1233611fbaf2faf95bfc17cd35d68a800309352ec000000000105475121024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed20921039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde52ae2206024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed2090c4707983700000000000000002206039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde1c4c00739d300000800100008000000080020000800000000000000000000101475121031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb2103d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c52ae2202031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb1c4c00739d300000800100008000000080020000800100000000000000220203d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c0c4707983701000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test wsh multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'", + "name": "test wsh multisig" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde", + "sighash": "all" + } + ] + }, + { + "id": "policy-wsh", + "description": "Signs the device branch of a registered BIP388 WSH or-policy and recognizes descriptor-derived change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff010089020000000117bb7376f1aed39ca76fac738a9eecfb490d3d8e0d9cb47a08b227dd04bb7abc0000000000ffffffff02801d2c0400000000220020593d9bd9f01f27f05e9a8ad4096961eb5f1bcd43a6fed6f2df9020fc82673560002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002200204bff256253c47cfb36eb7d58f4e6b97d484701191075ecde1d3332ecdf8111010000000001054821033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2ac7c21024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed209ac9b2206024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed2090c4707983700000000000000002206033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21c4c00739d3000008001000080000000800300008000000000000000000001014821028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aac7c2103d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074cac9b2202028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1c4c00739d300000800100008000000080030000800100000000000000220203d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c0c4707983701000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test wsh policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test wsh policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test wsh policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + } + ] + }, + "name": "test wsh policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "sighash": "all" + } + ] + }, + { + "id": "policy-tr-keyspend", + "description": "Signs the key-spend path of a registered BIP388 Taproot policy using only a witness UTXO and recognizes descriptor-derived change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001c0470cb5928e1e538404293a10b5c47e8d2482e27834ec4fb855d2c9e708a1480000000000ffffffff02801d2c04000000002251202cf99a9e98b0ee07ccb287502f76b2f856af59b726a746cd1ab303adc6749129002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120eb86719b281a383608399cab552dbd40368c66cd958f38bcb42347830e729ef021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21d004c00739d3000008001000080000000800300008000000000000000000117203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2000105208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a21078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1d004c00739d3000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*)", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n1 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr keyspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*)", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/1", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n1 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr keyspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*)", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/1", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*)", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test tr keyspend policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "sighash": "default" + } + ] + }, + { + "id": "policy-tr-scriptspend", + "description": "Signs the script path owned by the device in a registered BIP388 Taproot policy whose internal key belongs to another signer.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000014625d1da866927cc28dae6171aabba10fdb6abaee03829bc717fa57c05fc6b1c0000000000ffffffff02801d2c04000000002251206ea53e5447e4771e25f4e3ef3e3c5973e00ed2a4f7680507134a797b7df3c3cc002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120d0692388c73a40274236cad74af812db4d49ecf07b0838e644189693c4a1c87f2215c14f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed20923203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2acc021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b23d01c3aa2951885f3ddabebfba4eb4873dbdb2c41e287562d542cf8782e2a857071b4c00739d30000080010000800000008003000080000000000000000021164f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed2090d004707983700000000000000000117204f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed209011820c3aa2951885f3ddabebfba4eb4873dbdb2c41e287562d542cf8782e2a857071b00010520d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c01062500c022208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aac21078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a3d01bdda447b6076e3cc5aef2434e05b03e88a74dba86599553f3371fac3e86112b74c00739d3000008001000080000000800300008001000000000000002107d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c0d004707983701000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "keys": [ + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr scriptspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr scriptspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "keys": [ + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test tr scriptspend policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_script", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "leaf_hash": "c3aa2951885f3ddabebfba4eb4873dbdb2c41e287562d542cf8782e2a857071b", + "sighash": "default" + } + ] + }, + { + "id": "taproot-to-non-taproot-change", + "description": "Signs all-Taproot inputs with P2WPKH change, requiring previous transactions for the added non-Taproot output config.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100a60200000002f8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270000000000fffffffff8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270100000000ffffffff0200e1f505000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef140000000000010089020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0200e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db4220000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e00010089020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0200e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db4220000000001012b00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db42221169e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434319004c00739d56000080010000800000008000000000010000000117209e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434300220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "taproot_key", + "pubkey": "9e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a40574343", + "sighash": "default" + } + ] + }, + { + "id": "silent-payment", + "description": "Signs mixed inputs with BIP352 metadata and a device-generated silent-payment output.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100b9020000000314f5c987750e136e84be08a812ad8273b6597dbd76ded0806e714c183644cdc40000000000ffffffff14f5c987750e136e84be08a812ad8273b6597dbd76ded0806e714c183644cdc40100000000ffffffff14f5c987750e136e84be08a812ad8273b6597dbd76ded0806e714c183644cdc40200000000ffffffff0200e1f50500000000225120aaf8631d8fffabc65d283faa12aedc768bdc669c1ba6ff1166f356f13eefe089002d31010000000000000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a294300e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b00e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870000000001012b00e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a29432116cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff0952719004c00739d5600008000000080000000800000000000000000011720cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff095270001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a294300e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b00e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870000000001011f00e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b220602c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d54000080000000800000008000000000000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a294300e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b00e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870000000001012000e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870104160014d14ce318a324bc9eb4ff285d94ee02a297c0496f220603d47c62767f5463ffc008b9250fa6bb7eac11f2aff84804cf19d80ea46922bd73184c00739d31000080000000800000008000000000000000000001052067426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee5210767426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee519004c00739d56000080000000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "silent_payment_address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + } + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 BTC", + "fee": "1.80000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "sp1q qgst e7k9 hx0q ftg6 qmwl kqtw uy6c ycya vzmz j85c 6qdf hjdp djtd gqju exzk 6mur w56s uy3e 0rd2 cgqv ycxt tddw svgx e2us fpxu mr70 xc9p kqwv" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 BTC", + "fee": "1.80000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff09527", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "ecdsa", + "pubkey": "02c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048", + "sighash": "all" + }, + { + "input_index": 2, + "kind": "ecdsa", + "pubkey": "03d47c62767f5463ffc008b9250fa6bb7eac11f2aff84804cf19d80ea46922bd73", + "sighash": "all" + } + ], + "expected_generated_outputs": { + "1": "5120d826829cb603fc008e5ef99d0818f2126d3569c3ab8a6cd069f07a20e892bd59" + } + }, + { + "id": "send-self-same-account", + "description": "Covers ownership detection for the input account, suppression of change confirmation and version-specific account labels.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100720200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff0280f0fa020000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d310100000000160014fc6b321f780a2401be6884e5bf84520a27820944000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d310000800100008000000080010000000000000000220203a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.22.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox02: tb1ql34ny8mcpgjqr0ngsnjmlpzjpgncyz2ygh2gye" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.22.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (same account): tb1ql34ny8mcpgjqr0ngsnjmlpzjpgncyz2ygh2gye" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (same account): tb1q l34n y8mc pgjq r0ng snjm lpzj pgnc yz2y gh2g ye" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "send-self-different-account", + "description": "Covers ownership detection for another account and version-specific account labels.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100720200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff0280f0fa020000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d31010000000016001460f1b576c18bdbece2606cac70378f2974aed9ef000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d310000800100008000000080010000000000000000220203cee733ae6350507a6c63656d45de6dfc98a252a9f242116cfbe6c6ade2626b0c184c00739d540000800100008001000080000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.22.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1qvrcm2akp30d7ecnqdjk8qdu09962ak005rcp6j" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.22.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (account #2): tb1qvrcm2akp30d7ecnqdjk8qdu09962ak005rcp6j" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (account #2): tb1q vrcm 2akp 30d7 ecnq djk8 qdu0 9962 ak00 5rcp 6j" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "payment-request", + "description": "Covers signed SLIP-24 payment-request metadata, merchant and multiline memo screens, address suppression and the pre-v9.24 simulator merchant limitation.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100720200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff0280f0fa020000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d3101000000001600142d997091b1574170c30720dbba9d06a367d68351000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d31000080010000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "text", + "note": "TextMemo line1\nTextMemo line2" + } + ], + "signature": "3e67d38390b3d32938bd3e3c8f239b201efc278fedadf371bea9561c94da89ea450ad806eeae079983babd8158ac9c7c6a3c12e589435e7535a571d562f9f337" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "Test Merchant" + }, + { + "type": "confirm", + "title": "", + "body": "Memo from\n\nTest Merchant", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 1/2", + "body": "TextMemo line1", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 2/2", + "body": "TextMemo line2", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "payment-request-owned-output", + "description": "Covers version-specific handling of payment-request metadata attached to an output owned by this device, rejected since v9.26.3.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff02801d2c0400000000225120cc9235442b0f4fdf2f3c39516207c389e08fed3ae6cfa90cdb820d791a9d6ba4002d310100000000160014fc6b321f780a2401be6884e5bf84520a27820944000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e00010520c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f7852107c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f78519004c00739d560000800100008000000080010000000000000000220203a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "text", + "note": "TextMemo line1\nTextMemo line2" + } + ], + "signature": "4e7837fadb6a322439108c295906d50229f5fd79a1624957e12eeb171cf4ceed36b854ba948ed2630ec89b38aec462b06ed236ea32fa74ad9040dadf430712cd" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.3", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "Test Merchant" + }, + { + "type": "confirm", + "title": "", + "body": "Memo from\n\nTest Merchant", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 1/2", + "body": "TextMemo line1", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 2/2", + "body": "TextMemo line2", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.3", + "outcome": "invalid_input", + "screens": [] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "multiple-output-types-bitcoin-coin", + "description": "Displays P2PKH, P2SH, P2WPKH and P2WSH recipients, warns about two change outputs, and signs the transaction.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001c38bf8f182f907c18224bd5f9cbd843e118019c68c288f5045bc4941672d30380000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd20296490000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600140b664ee927e3e41c6f5acd135d5b31fca4ed8e4a640000000000000016001491d160f1749d830eec2b9402b0b1fcc5ccd52ab1000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f0000000001011f8057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f22060218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d184c00739d54000080000000800a000080000000000500000000000000002202024ea6cfaca7a9f7689293a9b67c77e0a3e5aa8b160b5cfa12474b52767b4b94cf184c00739d54000080000000800a000080010000000300000000220202b8564af1c586c4477df4c233987002147a77987163bdc0535d364569306a74b6184c00739d54000080000000800a000080010000001e00000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH" + }, + { + "type": "transaction_address", + "amount": "12.34567890 BTC", + "address": "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "0.05419010 BTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH" + }, + { + "type": "transaction_address", + "amount": "12.34567890 BTC", + "address": "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1q xven xven xven xven xven xven xven xven 2ymj t8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "0.05419010 BTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d", + "sighash": "all" + } + ] + }, + { + "id": "multiple-output-types-bitcoin-satoshi", + "description": "Displays P2PKH, P2SH, P2WPKH and P2WSH recipients, warns about two change outputs, and signs the transaction.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001c38bf8f182f907c18224bd5f9cbd843e118019c68c288f5045bc4941672d30380000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd20296490000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600140b664ee927e3e41c6f5acd135d5b31fca4ed8e4a640000000000000016001491d160f1749d830eec2b9402b0b1fcc5ccd52ab1000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f0000000001011f8057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f22060218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d184c00739d54000080000000800a000080000000000500000000000000002202024ea6cfaca7a9f7689293a9b67c77e0a3e5aa8b160b5cfa12474b52767b4b94cf184c00739d54000080000000800a000080010000000300000000220202b8564af1c586c4477df4c233987002147a77987163bdc0535d364569306a74b6184c00739d54000080000000800a000080010000001e00000000", + "options": { + "format_unit": "sat" + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "100000000 sat", + "address": "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH" + }, + { + "type": "transaction_address", + "amount": "1234567890 sat", + "address": "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ" + }, + { + "type": "transaction_address", + "amount": "6000 sat", + "address": "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8" + }, + { + "type": "transaction_address", + "amount": "7000 sat", + "address": "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "1339999900 sat", + "fee": "5419010 sat", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "100000000 sat", + "address": "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH" + }, + { + "type": "transaction_address", + "amount": "1234567890 sat", + "address": "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ" + }, + { + "type": "transaction_address", + "amount": "6000 sat", + "address": "bc1q xven xven xven xven xven xven xven xven 2ymj t8" + }, + { + "type": "transaction_address", + "amount": "7000 sat", + "address": "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "1339999900 sat", + "fee": "5419010 sat", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d", + "sighash": "all" + } + ] + }, + { + "id": "multiple-output-types-litecoin-coin", + "description": "Displays P2PKH, P2SH, P2WPKH and P2WSH recipients, warns about two change outputs, and signs the transaction.", + "coin": "ltc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001df6504f0b9af7c7d6795aa190cb900553f2b56690c26ed0d5b8439086dedb9bf0000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd20296490000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600146874a290ffa9444d417e413635eb17a610282aef640000000000000016001466d4383af71817173d87697435a67185d787eb0e000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014f3f3b2cac1d6582e72c41c7db73f4d1c8eb642500000000001011f8057ff7800000000160014f3f3b2cac1d6582e72c41c7db73f4d1c8eb64250220602bad6b8353f8ad1cc4cfc775b05ab9f834a1d132ac36e4cafdc43d8c2c99ebc46184c00739d54000080020000800a0000800000000005000000000000000022020394b3d8170d82ca3dab3feaa5846c5e659cf2f970fc535597aa96c921ed747bdd184c00739d54000080020000800a00008001000000030000000022020349e62be3b83b966bb79dfb50aac69ca112df3237b0ec5cbb069321d2dc363d84184c00739d54000080020000800a000080010000001e00000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 LTC", + "address": "LLnCCHbSzfwWquEdaS5TF2Yt7uz5Qb1SZ1" + }, + { + "type": "transaction_address", + "amount": "12.34567890 LTC", + "address": "MB1e6aUeL3Zj4s4H2ZqFBHaaHd7kvvzTco" + }, + { + "type": "transaction_address", + "amount": "0.00006000 LTC", + "address": "ltc1qxvenxvenxvenxvenxvenxvenxvenxvenwcpknh" + }, + { + "type": "transaction_address", + "amount": "0.00007000 LTC", + "address": "ltc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqwr7k5s" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 LTC", + "fee": "0.05419010 LTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 LTC", + "address": "LLnC CHbS zfwW quEd aS5T F2Yt 7uz5 Qb1S Z1" + }, + { + "type": "transaction_address", + "amount": "12.34567890 LTC", + "address": "MB1e 6aUe L3Zj 4s4H 2ZqF BHaa Hd7k vvzT co" + }, + { + "type": "transaction_address", + "amount": "0.00006000 LTC", + "address": "ltc1 qxve nxve nxve nxve nxve nxve nxve nxve nwcp knh" + }, + { + "type": "transaction_address", + "amount": "0.00007000 LTC", + "address": "ltc1 qg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z qwr7 k5s" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 LTC", + "fee": "0.05419010 LTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02bad6b8353f8ad1cc4cfc775b05ab9f834a1d132ac36e4cafdc43d8c2c99ebc46", + "sighash": "all" + } + ] + }, + { + "id": "high-fee-rounding", + "description": "Displays the 18.1% high-fee warning and requires the final longtouch on the warning instead of the fee screen.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001c38bf8f182f907c18224bd5f9cbd843e118019c68c288f5045bc4941672d30380000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd240aa3d0000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600140b664ee927e3e41c6f5acd135d5b31fca4ed8e4a640000000000000016001491d160f1749d830eec2b9402b0b1fcc5ccd52ab1000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f0000000001011f8057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f22060218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d184c00739d54000080000000800a000080000000000500000000000000002202024ea6cfaca7a9f7689293a9b67c77e0a3e5aa8b160b5cfa12474b52767b4b94cf184c00739d54000080000000800a000080010000000300000000220202b8564af1c586c4477df4c233987002147a77987163bdc0535d364569306a74b6184c00739d54000080000000800a000080010000001e00000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 18.1%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH" + }, + { + "type": "transaction_address", + "amount": "10.34567890 BTC", + "address": "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "2.05419010 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 18.1%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH" + }, + { + "type": "transaction_address", + "amount": "10.34567890 BTC", + "address": "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1q xven xven xven xven xven xven xven xven 2ymj t8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "2.05419010 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 18.1%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d", + "sighash": "all" + } + ] + }, + { + "id": "swap-payment-request", + "description": "Displays and signs a Bitcoin-to-Ethereum coin-purchase payment request.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff01007d0200000001013018e6e997b8e9b80d0244f11e3c68ba7107d8bb9a359a16a9a796d64e85550000000000ffffffff02801d2c04000000001600141133a71526b176aac7b2462bc5bff36abe68a18a002d310100000000225120da28dbf53b86ae59ae156af3745bb326f44ce309dda97345708c8d3dd45755a6000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b0000000001011f00e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b220602c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d5400008000000080000000800000000000000000002202034fc8251f3b2e939dccf75db7c9edf0e8cbc88ec662c0119e961e586ebebe3731184c00739d54000080000000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "coin_purchase", + "coin_type": 60, + "amount": "0.25 ETH", + "address": "0x416E88840Eb6353E49252Da2a2c140eA1f969D1a", + "address_keypath": "m/44'/60'/0'/0/0" + } + ], + "signature": "917d3514b2358406730f42952efe0a873255788314a9cf2906474c1e2cefd8cb0e35791ef8211463d37a007e3d9d3a0ec11736250b7e089156c53e5ebf40413d" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.0", + "outcome": "invalid_input", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "Test Merchant" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "Test Merchant" + }, + { + "type": "swap", + "title": "Swap", + "from": "0.20000000 BTC", + "to": "0.25 ETH" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048", + "sighash": "all" + } + ] + }, + { + "id": "swap-payment-request-unsupported-source", + "description": "Rejects a coin-purchase payment request whose source account is Bitcoin testnet.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "coin_purchase", + "coin_type": 60, + "amount": "0.25 ETH", + "address": "0x416E88840Eb6353E49252Da2a2c140eA1f969D1a", + "address_keypath": "m/44'/60'/0'/0/0" + } + ], + "signature": "c231191f200c3259f71a509dc644b49ab14332fdde4b63a12aaa19b744f188f872e7c60cc6a7e5823acb1f849659e16664fbf2680749bd2101b33cdb49bb8dce" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.0", + "outcome": "invalid_input", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "Test Merchant" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "p2wpkh-p2sh", + "description": "Signs a nested SegWit input and recognizes nested SegWit change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007e020000000118daf26c629c509a7ea9da4fa3a32393c330c526aa6466e2a2e2f880ee9cf50e0000000000ffffffff02801d2c040000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc66870000000001012000e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc6687010416001468a00bbd6de555d756e92815e523b789def6c0b022060329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0184c00739d31000080010000800000008000000000000000000001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d31000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0", + "sighash": "all" + } + ] + }, + { + "id": "high-input-address-index", + "description": "Spends an owned input at address index 100000 without treating the high spend path as a receive-address verification request.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d02000000014e52523f00352b5da9aca9fcb5dfb4a6c1e0cb8632718e187e0ffefaec0a5eb90000000000ffffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014c2e296c295380207dfdc535c8872c043d9bf52d30000000001011f00e1f50500000000160014c2e296c295380207dfdc535c8872c043d9bf52d3220603ce6f00e8e7302e323faf1f26ce2584307cb1bcef2a2b2140c6013c67bb9b991b184c00739d54000080010000800000008000000000a086010000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03ce6f00e8e7302e323faf1f26ce2584307cb1bcef2a2b2140c6013c67bb9b991b", + "sighash": "all" + } + ] + }, + { + "id": "locktime-zero", + "description": "Suppresses the locktime confirmation when locktime is zero even if the sequence signals RBF.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000fdffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "locktime-non-rbf", + "description": "Displays block locktime 10 and its non-rbf sequence semantics.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000feffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef140a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is not RBF", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is not RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is not RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "locktime-rbf", + "description": "Displays block locktime 10 and its rbf sequence semantics.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000fdffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef140a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is RBF", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "locktime-litecoin-non-rbf-sequence", + "description": "Displays a Litecoin block locktime without Bitcoin-specific RBF wording.", + "coin": "ltc", + "psbt": { + "transaction": "70736274ff01007102000000018660b164e26bff1622e4172600f4011bd824211a4ef4201d92c0b0fb290f9b730000000000feffffff02801d2c04000000001600146514c2779c7ee0f8e09ae4f2999c334e5254c70e002d310100000000160014751e76e8199196d454941c45d1b3a323f1433bd60a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f160000000001011f00e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f162206032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2184c00739d5400008002000080000000800000000000000000002202039537a5fa4fa947baa7b215407914d5fcb3709c554ff5750bf0ba931b3cda24ae184c00739d54000080020000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kgmn4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1 qw50 8d6q ejxt dg4y 5r3z arva ry0c 5xw7 kgmn 4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2", + "sighash": "all" + } + ] + }, + { + "id": "locktime-litecoin-rbf-sequence", + "description": "Displays a Litecoin block locktime without Bitcoin-specific RBF wording.", + "coin": "ltc", + "psbt": { + "transaction": "70736274ff01007102000000018660b164e26bff1622e4172600f4011bd824211a4ef4201d92c0b0fb290f9b730000000000fdffffff02801d2c04000000001600146514c2779c7ee0f8e09ae4f2999c334e5254c70e002d310100000000160014751e76e8199196d454941c45d1b3a323f1433bd60a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f160000000001011f00e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f162206032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2184c00739d5400008002000080000000800000000000000000002202039537a5fa4fa947baa7b215407914d5fcb3709c554ff5750bf0ba931b3cda24ae184c00739d54000080020000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kgmn4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1 qw50 8d6q ejxt dg4y 5r3z arva ry0c 5xw7 kgmn 4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2", + "sighash": "all" + } + ] + }, + { + "id": "p2tr-output-mainnet", + "description": "Displays and signs a mainnet P2TR recipient output from a native SegWit account.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff01007d0200000001013018e6e997b8e9b80d0244f11e3c68ba7107d8bb9a359a16a9a796d64e85550000000000ffffffff02801d2c04000000001600141133a71526b176aac7b2462bc5bff36abe68a18a002d310100000000225120da28dbf53b86ae59ae156af3745bb326f44ce309dda97345708c8d3dd45755a6000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b0000000001011f00e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b220602c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d5400008000000080000000800000000000000000002202034fc8251f3b2e939dccf75db7c9edf0e8cbc88ec662c0119e961e586ebebe3731184c00739d54000080000000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "bc1pmg5dhafms6h9nts4dtehgkanym6yeccfmk5hx3ts3jxnm4zh2knqv80ha5" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "bc1p mg5d hafm s6h9 nts4 dteh gkan ym6y eccf mk5h x3ts 3jxn m4zh 2knq v80h a5" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048", + "sighash": "all" + } + ] + }, + { + "id": "silent-payment-owned-output", + "description": "Covers version-specific handling of silent-payment metadata attached to an output owned by this device, rejected since v9.26.3.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff01007d0200000001a49f3e93836b9131d84d700f422b07ff6f6ffa379499b910c0f153e18f3dac3c0000000000ffffffff02801d2c0400000000225120aaf8631d8fffabc65d283faa12aedc768bdc669c1ba6ff1166f356f13eefe089002d310100000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a29430000000001012b00e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a29432116cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff0952719004c00739d5600008000000080000000800000000000000000011720cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff095270001052067426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee5210767426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee519004c00739d560000800000008000000080010000000000000000220202c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d540000800000008000000080000000000000000000", + "options": { + "outputs": { + "1": { + "silent_payment_address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + } + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.22.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "This BitBox02: sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.22.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "This BitBox (same account): sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "max_version_exclusive": "9.26.3", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "This BitBox (same account): sp1q qgst e7k9 hx0q ftg6 qmwl kqtw uy6c ycya vzmz j85c 6qdf hjdp djtd gqju exzk 6mur w56s uy3e 0rd2 cgqv ycxt tddw svgx e2us fpxu mr70 xc9p kqwv" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.3", + "outcome": "invalid_input", + "screens": [] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff09527", + "sighash": "default" + } + ] + }, + { + "id": "op-return-nonascii", + "description": "Displays a non-ASCII OP_RETURN payload as hexadecimal and signs the transaction.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff030000000000000000076a050102030405801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "OP_RETURN\ndata (hex)", + "body": "0102030405", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "OP_RETURN\ndata (hex)", + "body": "0102030405", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "op-return-nonzero-value", + "description": "Rejects a nonzero-value OP_RETURN output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100930200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff0364000000000000000d6a0b68656c6c6f20776f726c64801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "op-return-silent-payment", + "description": "Rejects silent-payment metadata on an OP_RETURN output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100930200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff0300000000000000000d6a0b68656c6c6f20776f726c64801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000", + "options": { + "outputs": { + "0": { + "silent_payment_address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + } + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "op-return-payment-request", + "description": "Rejects payment-request metadata on an OP_RETURN output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100930200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff0300000000000000000d6a0b68656c6c6f20776f726c64801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000", + "options": { + "outputs": { + "0": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 1, + "memos": [ + { + "type": "text", + "note": "TextMemo line1\nTextMemo line2" + } + ], + "signature": "93582744d5fab8b70cd2f1686bb7baf76e2797f095a83a6029f04c8c92d11bca4f274a71ef0ac79ce65b87241feb770823a1525d64f85e69104081d6ad397601" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "multisig-not-registered", + "description": "Rejects a valid 1-of-2 P2WSH multisig transaction when its account has not been registered.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff010089020000000131816c7584e8fb28ce853874eeb709394562c6e99eb9b6f3987d205dcc6933880000000000ffffffff02801d2c040000000022002049934b3c5c97bbd8e9e77ca525ddbad2addcf929f5dd3b83d2c017ab65e54382002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020276686da70f18aec8090484fc1a58b0808b2f6c7aa9f88191ec47166bd805c9e0000000001012b00e1f50500000000220020276686da70f18aec8090484fc1a58b0808b2f6c7aa9f88191ec47166bd805c9e0105475121031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad21039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde52ae2206031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad1c0dbd0cc33000008001000080000000800200008000000000000000002206039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde1c4c00739d30000080010000800000008002000080000000000000000000010147512102e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b21031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb52ae220202e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b1c0dbd0cc33000008001000080000000800200008001000000000000002202031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb1c4c00739d3000008001000080000000800200008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFX8u4cajvyZrQGikAfBcRt5EqVj4C9xB1HEQSZfN5weSUq4ULeMra6GXY8fM6hwgBX3WZ8TCP7nvMQu3hM4zvUHqWPhU9oq8HNV4ixboTz" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "multisig-p2wsh-p2sh", + "description": "Signs and finalizes a registered 1-of-2 nested P2SH-P2WSH multisig transaction.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007e02000000017a26adfe8f25b544220236a6516252b9eb746a620ffd210679634285fc890dd50000000000ffffffff02801d2c040000000017a914c88f04120fca1cbbd8e1a9dac997a5d99d8ca09d87002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f5050000000017a9143b3a20e6b4121c867e5a8e1bf1c3c85504b1ff13870000000001012000e1f5050000000017a9143b3a20e6b4121c867e5a8e1bf1c3c85504b1ff13870104220020cd9833d9c1ca210e8e5984f8fffa56f40770409811f431ea1c6daa0cd7cfba7f010547512102efb2f859bf983f02f6c17664426a9992985b3bdac83184b0162993d398f8ba0d2103f506dd0e67b1d3f035a6055961a3ac5510dff004069e9d025930338fd8e20c8852ae220602efb2f859bf983f02f6c17664426a9992985b3bdac83184b0162993d398f8ba0d1c0dbd0cc3300000800100008000000080010000800000000000000000220603f506dd0e67b1d3f035a6055961a3ac5510dff004069e9d025930338fd8e20c881c4c00739d3000008001000080000000800100008000000000000000000001002200209a8754cc38326f8daa604b0d1f4d803f6bd714cf428490ed6cb5407b05ea91cd01014751210235294f21973f87de509b16ef8f71bf9c353c0d450db814a428709f80a2a347f32103fd271ddb4968f17ea1aab754c4283c05e58ee5810868e845ad7d2b06ef30a87452ae22020235294f21973f87de509b16ef8f71bf9c353c0d450db814a428709f80a2a347f31c0dbd0cc3300000800100008000000080010000800100000000000000220203fd271ddb4968f17ea1aab754c4283c05e58ee5810868e845ad7d2b06ef30a8741c4c00739d3000008001000080000000800100008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDNQtJHZFjbd79dLGtC1yTcWn7WHPaCdpa8jXS8mU4y9tdYqzhrUVt3w6QR6vjx66nbUX4s2Hw5V5oer76XzKpRVMExC7zpLG", + "tpubDFX8u4cajvyZqUGS5AVgPb7GUpAJJ5nbkVMnw1m2ajrHaZmknbxuWDac2P2zyiB35UcgqwmVAeVzGEVV9qbji6tzYRCTiWwPGvtUULR4DLM" + ], + "our_xpub_index": 0, + "script_type": "p2wsh_p2sh" + }, + "keypath": "m/48'/1'/0'/1'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test sh-wsh multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test sh-wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test sh-wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDNQtJHZFjbd79dLGtC1yTcWn7WHPaCdpa8jXS8mU4y9tdYqzhrUVt3w6QR6vjx66nbUX4s2Hw5V5oer76XzKpRVMExC7zpLG", + "tpubDFX8u4cajvyZqUGS5AVgPb7GUpAJJ5nbkVMnw1m2ajrHaZmknbxuWDac2P2zyiB35UcgqwmVAeVzGEVV9qbji6tzYRCTiWwPGvtUULR4DLM" + ], + "our_xpub_index": 0, + "script_type": "p2wsh_p2sh" + }, + "keypath": "m/48'/1'/0'/1'", + "name": "test sh-wsh multisig" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03f506dd0e67b1d3f035a6055961a3ac5510dff004069e9d025930338fd8e20c88", + "sighash": "all" + } + ] + }, + { + "id": "multisig-large", + "description": "Adds the seventh signature to a registered 7-of-15 P2WSH multisig transaction whose PSBT already contains six cosigner signatures.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001cb44be13195cb0246f334d02fc36179e5c8d03e5bb812f97b039c5920413280f0000000000ffffffff02801d2c0400000000220020472cd252fa25c59c9e7f876cca3cbf4585826ff6f66b5e465feddc94cf50a5cc002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f505000000002200208d69e9431a924d7a5cd26f9cf85b2a3bc9d91dc06266f4a24b2ea13fdd1da1780000000001012b00e1f505000000002200208d69e9431a924d7a5cd26f9cf85b2a3bc9d91dc06266f4a24b2ea13fdd1da17822020266dc638844899b98e0d23a40deb72ec14b4842fbd3e70f65f1a955b6cf9f5e50483045022100bf0316ed68454f814fe83efb4ae013339d044b003336a1c68ed80bf9123d9fb2022045ba0d2a90aadfd3b062036e03ba1f6cf8c2aa2bd5d4c5bf1728d4648b5cd87801220202fdb3657418ffef1ed8f513b9e4d9a886ce9798a6b766e5da018417f981395feb483045022100aa4344593f229829440738816c3ed451191898a1761f1e1157a008e7ba5bfb68022030f17cd5f5006baded37e56ecb2480fd3a05b44aa88fe2f870b4974326a04d52012202031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad483045022100a62043f3af8c106c3ef3d0aad0f7c36940627fa06792e19b178e5cd39c4fe9850220793fa3990ed0fa526018e835aed5cab8206827ddbea30a86cf2421c9cc3fb686012202039446d050672e4829214199332a974a5ec1ec80ab0cd4dfc395e2e37a79af87f0473044022002958b416ce5a3c9722cdd07c45addbc679ec34d63bd138207583b258031300c02204d36fd84522cd7831fc06dbd5e611e7bccfc23098a752808a5a252ba05060070012202039b319f5be7dcd91450775a73dc9f163c20ad6eb08e611f220342d53fb0d70e204730440220630b0e820c0e2e7940929a3190b374a649c2e0f0aa78077806551aad57d20b7c02202a2448562899d865f43805441331533721827925b386841818df7b5e406989e101220203ca2121456edf5d624b9bed7d7f507e40b088b313d5773f19df45e71b96278421483045022100dc2b897a4ae2ae446fb7fe7f94836f1136c7ad3c5deb4cb4ccd1eccd224c3f7a02205e4506f6edd9981ba7bb7578454b4c4250de3070db29b74c20c21f5d88e599b2010105fd01025721020faf2667c643963b00041bea043d6fd2e91f3db842b8ccfddefd0f5615bfe9b4210266dc638844899b98e0d23a40deb72ec14b4842fbd3e70f65f1a955b6cf9f5e502102bcdd4e3e30bdb4219ef1836d0f573b81574639b189225039deaefb5bd75153152102e73cddb837ae9bcb40d3f6cf72e133d77505daf9fe03a5c359822a45332391a82102eb12fd1be52ba727e325bd880e8b0ac28769c3850301f3c333c98c0d420b65ec2102fdb3657418ffef1ed8f513b9e4d9a886ce9798a6b766e5da018417f981395feb21031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad2103440396891d7d6c9f623635e4c1a4f66edca6dca79c11debe0dc58a0f10f9079421034a0ad5543e4978b96f5a06959b281298e4079fe2fd71efaa0604a93fa9305891210353983ae233c60e81243818ffa3ce635d65d666238c1e7503e31e9f2186cc714521039446d050672e4829214199332a974a5ec1ec80ab0cd4dfc395e2e37a79af87f021039b319f5be7dcd91450775a73dc9f163c20ad6eb08e611f220342d53fb0d70e2021039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde2103bbc0345e402e1f8838e0ea1669fc79f452cfcc7136a3ff91f621833fbc154f6a2103ca2121456edf5d624b9bed7d7f507e40b088b313d5773f19df45e71b962784215fae2206020faf2667c643963b00041bea043d6fd2e91f3db842b8ccfddefd0f5615bfe9b41ce9324a6c30000080010000800000008002000080000000000000000022060266dc638844899b98e0d23a40deb72ec14b4842fbd3e70f65f1a955b6cf9f5e501c73bccd56300000800100008000000080020000800000000000000000220602bcdd4e3e30bdb4219ef1836d0f573b81574639b189225039deaefb5bd75153151cd0bee5ae300000800100008000000080020000800000000000000000220602e73cddb837ae9bcb40d3f6cf72e133d77505daf9fe03a5c359822a45332391a81cc8c72c6c300000800100008000000080020000800000000000000000220602eb12fd1be52ba727e325bd880e8b0ac28769c3850301f3c333c98c0d420b65ec1c0bbe8456300000800100008000000080020000800000000000000000220602fdb3657418ffef1ed8f513b9e4d9a886ce9798a6b766e5da018417f981395feb1cac9a9e9e3000008001000080000000800200008000000000000000002206031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad1c0dbd0cc3300000800100008000000080020000800000000000000000220603440396891d7d6c9f623635e4c1a4f66edca6dca79c11debe0dc58a0f10f907941c0ed45ef03000008001000080000000800200008000000000000000002206034a0ad5543e4978b96f5a06959b281298e4079fe2fd71efaa0604a93fa93058911c9e1cb09d30000080010000800000008002000080000000000000000022060353983ae233c60e81243818ffa3ce635d65d666238c1e7503e31e9f2186cc71451c7fb8ad413000008001000080000000800200008000000000000000002206039446d050672e4829214199332a974a5ec1ec80ab0cd4dfc395e2e37a79af87f01c054f2f713000008001000080000000800200008000000000000000002206039b319f5be7dcd91450775a73dc9f163c20ad6eb08e611f220342d53fb0d70e201c41559f4f3000008001000080000000800200008000000000000000002206039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde1c4c00739d300000800100008000000080020000800000000000000000220603bbc0345e402e1f8838e0ea1669fc79f452cfcc7136a3ff91f621833fbc154f6a1c5d3e97e0300000800100008000000080020000800000000000000000220603ca2121456edf5d624b9bed7d7f507e40b088b313d5773f19df45e71b962784211c1a34b3e5300000800100008000000080020000800000000000000000000101fd01025721021f7e9e9befaa3424398f347b5b2f78d5f893498f6f8ec488dd2f9f22ea18c7bc21029c20cd5acd30db6bf7aeaf7f83cc171adc87cba7498bd7e673304db6e0a86b5b2102b29e8ea9305c52a928175756d9c979db1768ce8643badface43e1c4661aa1fec2102d63ffb041633c0c721c9dd16fabdcd0a413a5d20786fd798e9ea5458a9d0145f2102e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b21031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb21031fd8d9ae7b5cdd43427ff940ee8b485d7b0e79e2cbb53c111f736db8309dd6cb2103206d7162cc432f2b79039f839a8eb4b9155b407b9178ebf46fc29de2c23965e421034434b5c28fe2b71f01328b3af87647d7729a9ad1d1d1646d3f0b87cd0dfd06b6210347504a67f3b4bfdef20beaa5790304ce34927e740929f2caddc032ae8d653a7921035c3445de6a048ab9e905c86bc6d481890088fa7d51309a439bfeb4af77fc7650210365a176ac65d10de83778956b560630f341a6f6b43584acc0271083cff36ace35210376b63bd235b30bc9042c7b500be79e7fbebbc4f6e723c8b798aea84769acb1ea21037d33decd1fee9418357a1e419813a898f7aa006b9a59ca241fb46ad29b774fcb2103c69f4d4950ed98135ff37e631731881dd01060db8dae4ccda2f8c0a796f5c4e85fae2202021f7e9e9befaa3424398f347b5b2f78d5f893498f6f8ec488dd2f9f22ea18c7bc1c0bbe84563000008001000080000000800200008001000000000000002202029c20cd5acd30db6bf7aeaf7f83cc171adc87cba7498bd7e673304db6e0a86b5b1c0ed45ef0300000800100008000000080020000800100000000000000220202b29e8ea9305c52a928175756d9c979db1768ce8643badface43e1c4661aa1fec1c1a34b3e5300000800100008000000080020000800100000000000000220202d63ffb041633c0c721c9dd16fabdcd0a413a5d20786fd798e9ea5458a9d0145f1c41559f4f300000800100008000000080020000800100000000000000220202e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b1c0dbd0cc33000008001000080000000800200008001000000000000002202031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb1c4c00739d3000008001000080000000800200008001000000000000002202031fd8d9ae7b5cdd43427ff940ee8b485d7b0e79e2cbb53c111f736db8309dd6cb1c73bccd56300000800100008000000080020000800100000000000000220203206d7162cc432f2b79039f839a8eb4b9155b407b9178ebf46fc29de2c23965e41c9e1cb09d3000008001000080000000800200008001000000000000002202034434b5c28fe2b71f01328b3af87647d7729a9ad1d1d1646d3f0b87cd0dfd06b61cac9a9e9e30000080010000800000008002000080010000000000000022020347504a67f3b4bfdef20beaa5790304ce34927e740929f2caddc032ae8d653a791cc8c72c6c3000008001000080000000800200008001000000000000002202035c3445de6a048ab9e905c86bc6d481890088fa7d51309a439bfeb4af77fc76501c7fb8ad4130000080010000800000008002000080010000000000000022020365a176ac65d10de83778956b560630f341a6f6b43584acc0271083cff36ace351c054f2f7130000080010000800000008002000080010000000000000022020376b63bd235b30bc9042c7b500be79e7fbebbc4f6e723c8b798aea84769acb1ea1c5d3e97e03000008001000080000000800200008001000000000000002202037d33decd1fee9418357a1e419813a898f7aa006b9a59ca241fb46ad29b774fcb1ce9324a6c300000800100008000000080020000800100000000000000220203c69f4d4950ed98135ff37e631731881dd01060db8dae4ccda2f8c0a796f5c4e81cd0bee5ae3000008001000080000000800200008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 7, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFX8u4cajvyZrQGikAfBcRt5EqVj4C9xB1HEQSZfN5weSUq4ULeMra6GXY8fM6hwgBX3WZ8TCP7nvMQu3hM4zvUHqWPhU9oq8HNV4ixboTz", + "tpubDFCqK5T7J3FUasUXA1xsminXkeaPSxe1id2K4KCEaszuTNKERymaiZHTnUcjGDf4b57XinAHauwSh1uDRmNVMtfhiStJWsf83MZLA1tC5qi", + "tpubDEKEr89i7dBFXrrpkK2KZQy9aMw6Dg8idr1ysbZJ94mWk6uaFAy4DmYMw7LMDSEaeSJdAcgshdLATcbr6T9hQoouRphfHC9aBQqiWAh8wRF", + "tpubDERfkonU8krEq7knZXFPFrS5bnbhsyXhV7wdM9agZuzD6YgM95LgBWKzAkPeZMseTWRj2PPcbw2kEjcrBctJEzLNUu2VLz1Fixehoc5z4aq", + "tpubDECMwLTAdWk5RcgKaaLKaP83R586xMFtBhH5osdzdF6VbEwFpQp3uiiaHrKGPnqmfDUKFtSQE5ForxoBYgJWEHA5B2T9i5GP5ZAuScWKXgH", + "tpubDEuxzYbnkbY2XHQZjzcDV2Vk4DH4oZwLefGso1jXfFsTyhVV5HxZvVUrSdGLc9AVBTNcCDZh5qw7RgPiibMfMVWtHpjzoErqJcvukzFSpbf", + "tpubDEccsjksu3ddEUnx5FkNADMy9YkLi3p6vQGpdgFDSypMfYZs7amRL94ucqyHGZrVDMrBc937ptj4SKihNiYJtnRgiZ742hpxfnAgynaQno5", + "tpubDF1U1F2s28Rv71bukRRTLUN3hiTgKDGjxGVrFAYXYo1qaWFNsUnA4MoyFtKoytQyLHfmE267HDo3VDgDUfk2hQwb3ary3aNojzkT1sTe5oi", + "tpubDF8LvKCNR3JMayQDTRRqBD3PLhVng5wk6bYvCpsrhe5Mffon2sSFrLBz9J3WCdLqsoeUjMiG1gqsiiM8RVNw6eSfqbRyFsSt2sZPd54GoVi", + "tpubDEgfQ9SqcnKyEkVmEEkx9AaQ3pZN1nSZQjZxtCcTVytAk62DggB6FiuH12cFeygFLgDPsYtBYDp8FCQDrdgZLrpp2cFhnsFur55kWid1DYg", + "tpubDEHvZYZJ5N3jGxHTJJ1LSWdoD3SUSSz7zUhUPLrTMC7SoEkthZVzfFz8qSYAYF1Rg6LjKTsBZMeNJivd4iDyjbQQ6JcyZe2eHB3WENiQECP", + "tpubDEjJyDs2yboyzkczJ5ADEmU9yJNqCgmpUPBqW5CVSPCxg8Q59ZwDqRCBwUrDmR29iY8BJGfBa5bq9MBhYpnqjaQvNQdFHqGX99W6ocPX63z", + "tpubDFFcidKjrkaTSz4R4s9Y5ijp3dzHkJ9XZ2JkLanjkUskBBbFvjxfQMeU5L52ANJtgpiMRyjK4WzJ6qkK22KQsScHbiQBEsstjVKLoxZynZm", + "tpubDF3ds3VKs6p4YjvSLY848WwW21fxiaYGdn5cZEUZ5CwKwMveavxSNA3GhJHqZkFkXa5DTeT1ZKjThtFY8KDfNAuvF95SDcXEoGXNq5bs9nT" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "7-of-15\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test large multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "7-of-15\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test large multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "7-of-15\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test large multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "multisig", + "threshold": 7, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFX8u4cajvyZrQGikAfBcRt5EqVj4C9xB1HEQSZfN5weSUq4ULeMra6GXY8fM6hwgBX3WZ8TCP7nvMQu3hM4zvUHqWPhU9oq8HNV4ixboTz", + "tpubDFCqK5T7J3FUasUXA1xsminXkeaPSxe1id2K4KCEaszuTNKERymaiZHTnUcjGDf4b57XinAHauwSh1uDRmNVMtfhiStJWsf83MZLA1tC5qi", + "tpubDEKEr89i7dBFXrrpkK2KZQy9aMw6Dg8idr1ysbZJ94mWk6uaFAy4DmYMw7LMDSEaeSJdAcgshdLATcbr6T9hQoouRphfHC9aBQqiWAh8wRF", + "tpubDERfkonU8krEq7knZXFPFrS5bnbhsyXhV7wdM9agZuzD6YgM95LgBWKzAkPeZMseTWRj2PPcbw2kEjcrBctJEzLNUu2VLz1Fixehoc5z4aq", + "tpubDECMwLTAdWk5RcgKaaLKaP83R586xMFtBhH5osdzdF6VbEwFpQp3uiiaHrKGPnqmfDUKFtSQE5ForxoBYgJWEHA5B2T9i5GP5ZAuScWKXgH", + "tpubDEuxzYbnkbY2XHQZjzcDV2Vk4DH4oZwLefGso1jXfFsTyhVV5HxZvVUrSdGLc9AVBTNcCDZh5qw7RgPiibMfMVWtHpjzoErqJcvukzFSpbf", + "tpubDEccsjksu3ddEUnx5FkNADMy9YkLi3p6vQGpdgFDSypMfYZs7amRL94ucqyHGZrVDMrBc937ptj4SKihNiYJtnRgiZ742hpxfnAgynaQno5", + "tpubDF1U1F2s28Rv71bukRRTLUN3hiTgKDGjxGVrFAYXYo1qaWFNsUnA4MoyFtKoytQyLHfmE267HDo3VDgDUfk2hQwb3ary3aNojzkT1sTe5oi", + "tpubDF8LvKCNR3JMayQDTRRqBD3PLhVng5wk6bYvCpsrhe5Mffon2sSFrLBz9J3WCdLqsoeUjMiG1gqsiiM8RVNw6eSfqbRyFsSt2sZPd54GoVi", + "tpubDEgfQ9SqcnKyEkVmEEkx9AaQ3pZN1nSZQjZxtCcTVytAk62DggB6FiuH12cFeygFLgDPsYtBYDp8FCQDrdgZLrpp2cFhnsFur55kWid1DYg", + "tpubDEHvZYZJ5N3jGxHTJJ1LSWdoD3SUSSz7zUhUPLrTMC7SoEkthZVzfFz8qSYAYF1Rg6LjKTsBZMeNJivd4iDyjbQQ6JcyZe2eHB3WENiQECP", + "tpubDEjJyDs2yboyzkczJ5ADEmU9yJNqCgmpUPBqW5CVSPCxg8Q59ZwDqRCBwUrDmR29iY8BJGfBa5bq9MBhYpnqjaQvNQdFHqGX99W6ocPX63z", + "tpubDFFcidKjrkaTSz4R4s9Y5ijp3dzHkJ9XZ2JkLanjkUskBBbFvjxfQMeU5L52ANJtgpiMRyjK4WzJ6qkK22KQsScHbiQBEsstjVKLoxZynZm", + "tpubDF3ds3VKs6p4YjvSLY848WwW21fxiaYGdn5cZEUZ5CwKwMveavxSNA3GhJHqZkFkXa5DTeT1ZKjThtFY8KDfNAuvF95SDcXEoGXNq5bs9nT" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'", + "name": "test large multisig" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde", + "sighash": "all" + } + ] + }, + { + "id": "policy-tr-keyspend-with-script-tree", + "description": "Signs a Taproot policy key path whose tweak commits to a script tree.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001481b25e956e13b122e0155ee1066ff2620345d94c51e6c2b7889e0daac298db00000000000ffffffff02801d2c0400000000225120916ffdd6fe3d0adf420c6d71702a944b8f22db3277d4b24721f2f1deb96e8a46002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120d06244555fe882d1190e968e20f053018fe20114014338e89149d314ef4352d92215c13a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2232034c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de2acc0211634c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de23d01d9c7b0c5dc86c4594427111e20854229a2bded47788886c3b3e5093c5d9d9f56c5e4fe1a30000080010000800000008003000080000000000000000021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21d004c00739d3000008001000080000000800300008000000000000000000117203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2011820d9c7b0c5dc86c4594427111e20854229a2bded47788886c3b3e5093c5d9d9f56000105208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a01062500c022203df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563bac21073df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563b3d016a4a33d1169c5d57685ea4668790c7ce35fc9af5f8002aea692c3296287f548fc5e4fe1a30000080010000800000008003000080010000000000000021078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1d004c00739d3000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/**,pk(@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr tweaked keyspend", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/**,pk(@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr tweaked keyspend", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/**,pk(@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/**,pk(@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test tr tweaked keyspend" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "sighash": "default" + } + ] + }, + { + "id": "policy-tr-unspendable-internal-key", + "description": "Signs a Taproot script path with a provably unspendable internal key and displays that property in the policy review.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001a8346bd79715029f26a2628a872d2299934cfc774bb3c4e52209ef404f07278f0000000000ffffffff02801d2c0400000000225120f191a4cb0a3d8f8b47251bc375159b8ff3aac672264cb0e0c9c5adeba37a09e4002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120c5340ec5fb5662a6ef7290f9dffedf9c727d503c2f69377cb4c8560e9f02e45b4114bc0eab168b76abf60a0a6f3c197ea0f9b7973e27ca1aba50daa09c62de813c68e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c08558405d9d7da26d848dd9562ab95b348ac6414e1bc28a2f8ccd839b7dd5b452abc513d099a0f0efcd011fdcc9b5f18b3c69cb9122391b751e274a67dc74b61757ab5d2215c01ab64872242fff67a9cc2429d50bd615532e55122665e448ae0e53a3f4a2ce594720bc0eab168b76abf60a0a6f3c197ea0f9b7973e27ca1aba50daa09c62de813c68ac203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2ba529cc021161ab64872242fff67a9cc2429d50bd615532e55122665e448ae0e53a3f4a2ce590d007c461e5d000000000000000021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b23d01e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c085584c00739d3000008001000080000000800300008000000000000000002116bc0eab168b76abf60a0a6f3c197ea0f9b7973e27ca1aba50daa09c62de813c683d01e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c0855808f56e293000008001000080000000800300008000000000000000000117201ab64872242fff67a9cc2429d50bd615532e55122665e448ae0e53a3f4a2ce59011820e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c0855800010520349092f96b1dd44a23e6a05986e7934bae5d6a87b9a050c1e13b38a0ae4e52c101064900c04620dbe4edc0edbcb5f212c9fa1d1bcbdb4b3f6e875a5ea52ac3cafed4057379302bac208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aba529c2107349092f96b1dd44a23e6a05986e7934bae5d6a87b9a050c1e13b38a0ae4e52c10d007c461e5d010000000000000021078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a3d014b40a0f212ea2f428474b933288cec2f2a50121ba995173e8d601dbb1b2d9a784c00739d3000008001000080000000800300008001000000000000002107dbe4edc0edbcb5f212c9fa1d1bcbdb4b3f6e875a5ea52ac3cafed4057379302b3d014b40a0f212ea2f428474b933288cec2f2a50121ba995173e8d601dbb1b2d9a7808f56e293000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8" + }, + { + "xpub": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test unspendable policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test unspendable policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8" + }, + { + "xpub": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test unspendable policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_script", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "leaf_hash": "e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c08558", + "sighash": "default" + } + ] + }, + { + "id": "policy-tr-unspendable-internal-key-complex", + "description": "Signs the satisfiable branch of a multi-leaf Taproot policy with a provably unspendable internal key, distinct multipaths and a relative-timelock sibling branch.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000017079ce1d2dffec9e935d2a74cd4a702f722e651939daeb27faf3953f14fa5c3a0000000000ffffffff02801d2c04000000002251203ad5b7757d2dc6cd5e5ec17a0603352d401de5d69fc48f9c6ec1a3ac2b8e194f002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120eaef7e275cce42763c22a441d0e5b254c794d51e8123f5562441346dac40848c4114eeab1c2d28079589eb9e55bbd86765ffce87514a1feca661ee3d88f0e04643c9b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a404eb58edfa3eec62bf0fccefc4e73dafb7e74009a81e22a24ea6f1dae95e558273eb0f851610819384de73f327e2ffe0d3751a8b785f9db8648d2bb074559eaef4215c159165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed3309e075b5976239a09b6b08d128ec95a42f328a625c26213f9568d7768d853f4974720eeab1c2d28079589eb9e55bbd86765ffce87514a1feca661ee3d88f0e04643c9ac203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2ba529cc04215c159165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed330b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a4920ca89423a6e0a0673b51bfd05212d8963c14a8db6cd3d7cba5dabaff97d4e3ed8ac201d7d773d42ed516b73555c715929badf8ddb135b25779b475157a49ed946af79ba519d52b2c021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b23d01b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a4c00739d300000800100008000000080030000800000000000000000211659165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed3300d007c461e5d00000000000000002116eeab1c2d28079589eb9e55bbd86765ffce87514a1feca661ee3d88f0e04643c93d01b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a4040280c30000080010000800000008002000080000000000000000001172059165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed3300118207754188d95aa29ae86dcf87406dfb5ed0e1d6da02a9ad5104b95f5f81abf79a4000105209ba26f33424bc7330bf3f683129516387fdc20f5baf09046ea9eaa2ed71516d401069401c0482006048aec624d548af178a98ec862e0efaa5c2fcf8fb07f5146f410918eb21eefac20c621b132507c49d0f974e71d89e3017fe4f4ca8aa7c73c291d7a4edd1a6399a6ba519d52b201c04620ddad6824617b6a1bd330aab5c263f039b7ebe04196792fe90d203752e9c37809ac208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aba529c21078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a3d01d3064571589c8c4e858bc4dd2efe190b4e2d9d3da7d96306eb9702e5401d2f264c00739d30000080010000800000008003000080010000000000000021079ba26f33424bc7330bf3f683129516387fdc20f5baf09046ea9eaa2ed71516d40d007c461e5d01000000000000002107ddad6824617b6a1bd330aab5c263f039b7ebe04196792fe90d203752e9c378093d01d3064571589c8c4e858bc4dd2efe190b4e2d9d3da7d96306eb9702e5401d2f264040280c3000008001000080000000800200008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm" + }, + { + "root_fingerprint": "4040280c", + "keypath": "m/48'/1'/0'/2'", + "xpub": "tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test complex unspendable", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "[4040280c/48'/1'/0'/2']tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test complex unspendable", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "[4040280c/48'/1'/0'/2']tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm" + }, + { + "root_fingerprint": "4040280c", + "keypath": "m/48'/1'/0'/2'", + "xpub": "tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test complex unspendable" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_script", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "leaf_hash": "b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a", + "sighash": "default" + } + ] + }, + { + "id": "policy-different-multipath-derivations", + "description": "Signs and finalizes a registered WSH policy whose two keys use different receive and change branches.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff010089020000000149aa85e2c639e52c4984cba528c60ef347f47f35917519ecba279514841928470000000000ffffffff02801d2c0400000000220020d0a6b11cf5a05594e6fad40948646011c6307c84906d318f348f5effaaa97b02002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020e267e95e105bd40e8fbf3bdda795da8bc943949f4accec407d45b469f514b4830000000001012b00e1f50500000000220020e267e95e105bd40e8fbf3bdda795da8bc943949f4accec407d45b469f514b483220203e7d88a65b1ca31ccc898193ab72acdce9b0c0076a9ba8f4ef06bd0ccd4d01d034730440220182d262d790be3d24e15dd8e74057bacf77f678ef0e82bdecce4386ab3252a7c02205651ad4a74158357b08c05bd88dec8c2522bb91013332f963374b45722c842a401010547522102b95c8bf277d3e7d7de1ad4fbb4921c5bd1cefeb3ebf38987bdd4688b9cfcbb722103e7d88a65b1ca31ccc898193ab72acdce9b0c0076a9ba8f4ef06bd0ccd4d01d0352ae220602b95c8bf277d3e7d7de1ad4fbb4921c5bd1cefeb3ebf38987bdd4688b9cfcbb721c4c00739d300000800100008000000080030000800a00000000000000220603e7d88a65b1ca31ccc898193ab72acdce9b0c0076a9ba8f4ef06bd0ccd4d01d031cc5e4fe1a300000800100008000000080030000801400000000000000000101475221034c130edc7e1574821af949c79d64052475e85f7f659cbbbd4b99c79bdf840dc92102627713c28bf52eb7012ac66a69b333fca5f9c8794fb5751ceb0cb50484546b3952ae220202627713c28bf52eb7012ac66a69b333fca5f9c8794fb5751ceb0cb50484546b391cc5e4fe1a3000008001000080000000800300008015000000000000002202034c130edc7e1574821af949c79d64052475e85f7f659cbbbd4b99c79bdf840dc91c4c00739d300000800100008000000080030000800b000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test multipath policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test multipath policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test multipath policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test multipath policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02b95c8bf277d3e7d7de1ad4fbb4921c5bd1cefeb3ebf38987bdd4688b9cfcbb72", + "sighash": "all" + } + ] + }, + { + "id": "policy-wrong-account-keypath", + "description": "Rejects a registered policy when the signing account keypath does not match the device key in the policy.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000011b6e58cffa2f032e498aa516a97410d53fc282bee867fb9653192aa223bd02de0000000000ffffffff02801d2c040000000022002032001963e09213e7387b90c3374944bdbbaf69f554060bb040626f0b428f1850002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0000000001012b00e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0105475221033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2210234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de252ae22060234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de21cc5e4fe1a3000008001000080000000800300008000000000000000002206033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21c4c00739d300000800100008000000080030000800000000000000000000101475221028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a21023df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563b52ae2202023df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563b1cc5e4fe1a3000008001000080000000800300008001000000000000002202028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1c4c00739d3000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/4'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "outcome": "invalid_input", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test policy account", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/**,@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test policy account" + } + ] + }, + { + "id": "policy-change-index-too-high", + "description": "Rejects a registered policy change output at address index 10000.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000011b6e58cffa2f032e498aa516a97410d53fc282bee867fb9653192aa223bd02de0000000000ffffffff02801d2c0400000000220020bd6a0b3bc28769ca2a7ef1aa12f7c052beb977fd61a1c55a9e5a3d6cecce0cbb002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0000000001012b00e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0105475221033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2210234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de252ae22060234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de21cc5e4fe1a3000008001000080000000800300008000000000000000002206033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21c4c00739d3000008001000080000000800300008000000000000000000001014752210252631b8aca4b08e95acacae7daf3bfdad38a8651821a1009008fdd04a90d17bc210275db5c17fe98b071caf5e724752d268263106509e2bc2d3fa468168702329e9452ae22020252631b8aca4b08e95acacae7daf3bfdad38a8651821a1009008fdd04a90d17bc1c4c00739d30000080010000800000008003000080010000001027000022020275db5c17fe98b071caf5e724752d268263106509e2bc2d3fa468168702329e941cc5e4fe1a3000008001000080000000800300008001000000102700000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "outcome": "invalid_input", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test policy account", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/**,@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test policy account" + } + ] + } + ] +} diff --git a/tests/test_btc_psbt.rs b/tests/test_btc_psbt.rs index d2af3d6..e15c527 100644 --- a/tests/test_btc_psbt.rs +++ b/tests/test_btc_psbt.rs @@ -9,19 +9,26 @@ compile_error!("Enable the tokio feature to run simulator tests"); mod util; -use util::test_initialized_simulators; +use std::collections::{BTreeMap, BTreeSet}; +use std::time::Duration; -use bitbox_api::{btc::Xpub, pb}; - -use bitcoin::bip32::DerivationPath; -use bitcoin::opcodes::all; +use bitbox_api::{pb, Keypath}; +use bitcoin::bip32::{Fingerprint, Xpub}; use bitcoin::psbt::Psbt; use bitcoin::secp256k1; -use bitcoin::{ - blockdata::script::Builder, transaction, Amount, OutPoint, ScriptBuf, Sequence, Transaction, - TxIn, TxOut, Witness, -}; +use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use miniscript::psbt::PsbtExt; +use semver::Version; +use serde::{de::IgnoredAny, Deserialize}; +use util::{ + test_initialized_simulators_with_stdout, SimulatorScreen, SimulatorStdout, + SimulatorStdoutCheckpoint, SimulatorStdoutSnapshot, +}; + +// Generated by bitbox02-firmware/src/rust/bitbox-test-vectors; do not edit the copy by hand. +const VECTOR_JSON: &str = include_str!("data/btc-transaction-test-vectors.json"); +const STDOUT_STABLE_FOR: Duration = Duration::from_millis(50); +const STDOUT_TIMEOUT: Duration = Duration::from_secs(5); // Checks that the psbt is fully signed and valid (all scripts execute correctly). // @@ -32,7 +39,7 @@ use miniscript::psbt::PsbtExt; // (https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-27.0.md?plain=1#L40-L53) // and will be replaced by libbitcoinkernel some day. fn verify_transaction(psbt: Psbt) { - let utxos: Vec = psbt + let utxos: Vec = psbt .iter_funding_utxos() .map(|utxo| utxo.unwrap()) .cloned() @@ -65,1004 +72,632 @@ fn verify_transaction(psbt: Psbt) { } } -// Test signing; all inputs are BIP86 Taproot keyspends. -#[tokio::test] -async fn test_btc_psbt_taproot_key_spend() { - test_initialized_simulators(async |bitbox| { - let secp = secp256k1::Secp256k1::new(); - - let fingerprint = util::simulator_xprv().fingerprint(&secp); - - let change_path: DerivationPath = "m/86'/1'/0'/1/0".parse().unwrap(); - let change_xpub = util::simulator_xpub_at(&secp, &change_path); - - let input0_path: DerivationPath = "m/86'/1'/0'/0/0".parse().unwrap(); - let input0_xpub = util::simulator_xpub_at(&secp, &input0_path); - - let input1_path: DerivationPath = "m/86'/1'/0'/0/1".parse().unwrap(); - let input1_xpub = util::simulator_xpub_at(&secp, &input1_path); - - // A previous tx which creates some UTXOs we can reference later. - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: - "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2tr(&secp, input0_xpub.to_x_only_pub(), None), - }, - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2tr(&secp, input1_xpub.to_x_only_pub(), None), - }, - ], - }; - - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }, - TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 1, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }, - ], - output: vec![ - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2tr(&secp, change_xpub.to_x_only_pub(), None), - }, - TxOut { - value: Amount::from_sat(20_000_000), - script_pubkey: ScriptBuf::new_p2tr( - &secp, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - "e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576" - .parse() - .unwrap(), - None, - ), - }, - ], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - // Add input and change infos. - psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone()); - psbt.inputs[0].tap_internal_key = Some(input0_xpub.to_x_only_pub()); - psbt.inputs[0].tap_key_origins.insert( - input0_xpub.to_x_only_pub(), - (vec![], (fingerprint, input0_path.clone())), - ); - psbt.inputs[1].witness_utxo = Some(prev_tx.output[1].clone()); - psbt.inputs[1].tap_internal_key = Some(input1_xpub.to_x_only_pub()); - psbt.inputs[1].tap_key_origins.insert( - input1_xpub.to_x_only_pub(), - (vec![], (fingerprint, input1_path.clone())), - ); - - psbt.outputs[0].tap_internal_key = Some(change_xpub.to_x_only_pub()); - psbt.outputs[0].tap_key_origins.insert( - change_xpub.to_x_only_pub(), - (vec![], (fingerprint, change_path.clone())), - ); - - // Sign. - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - None, - pb::btc_sign_init_request::FormatUnit::Default, - ) - .await - .unwrap(); - - // Finalize, add witnesses. - psbt.finalize_mut(&secp).unwrap(); - - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }) - .await +#[derive(Deserialize)] +struct TestVectorFile { + vectors: Vec, } -// Test signing; mixed input types (p2wpkh, p2wpkh-p2sh, p2tr) -#[tokio::test] -async fn test_btc_psbt_mixed_spend() { - test_initialized_simulators(async |bitbox| { - let secp = secp256k1::Secp256k1::new(); - - let fingerprint = util::simulator_xprv().fingerprint(&secp); - - let change_path: DerivationPath = "m/86'/1'/0'/1/0".parse().unwrap(); - let change_xpub = util::simulator_xpub_at(&secp, &change_path); - - let input0_path: DerivationPath = "m/86'/1'/0'/0/0".parse().unwrap(); - let input0_xpub = util::simulator_xpub_at(&secp, &input0_path); - - let input1_path: DerivationPath = "m/84'/1'/0'/0/0".parse().unwrap(); - let input1_xpub = util::simulator_xpub_at(&secp, &input1_path); - - let input2_path: DerivationPath = "m/49'/1'/0'/0/0".parse().unwrap(); - let input2_xpub = util::simulator_xpub_at(&secp, &input2_path); - - let input2_redeemscript = ScriptBuf::new_p2wpkh(&input2_xpub.to_pub().wpubkey_hash()); - - // A previous tx which creates some UTXOs we can reference later. - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: - "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2tr(&secp, input0_xpub.to_x_only_pub(), None), - }, - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2wpkh(&input1_xpub.to_pub().wpubkey_hash()), - }, - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2sh(&input2_redeemscript.clone().into()), - }, - ], - }; - - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }, - TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 1, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }, - TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 2, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }, - ], - output: vec![ - TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: ScriptBuf::new_p2tr(&secp, change_xpub.to_x_only_pub(), None), - }, - TxOut { - value: Amount::from_sat(20_000_000), - script_pubkey: ScriptBuf::new_p2tr( - &secp, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - "e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576" - .parse() - .unwrap(), - None, - ), - }, - ], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - // Add input and change infos. - psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone()); - psbt.inputs[0].tap_internal_key = Some(input0_xpub.to_x_only_pub()); - psbt.inputs[0].tap_key_origins.insert( - input0_xpub.to_x_only_pub(), - (vec![], (fingerprint, input0_path.clone())), - ); - - psbt.inputs[1].non_witness_utxo = Some(prev_tx.clone()); - psbt.inputs[1] - .bip32_derivation - .insert(input1_xpub.to_pub().0, (fingerprint, input1_path.clone())); +#[derive(Deserialize)] +struct TestVector { + id: String, + description: String, + coin: Coin, + psbt: PsbtFixture, + expectations: Vec, + #[serde(default)] + registrations: Vec, + #[serde(default)] + expected_signatures: BTreeSet, +} - psbt.inputs[2].non_witness_utxo = Some(prev_tx.clone()); - psbt.inputs[2].redeem_script = Some(input2_redeemscript.clone()); - psbt.inputs[2] - .bip32_derivation - .insert(input2_xpub.to_pub().0, (fingerprint, input2_path.clone())); +impl TestVector { + fn context(&self) -> String { + format!("{} ({})", self.id, self.description) + } +} - psbt.outputs[0].tap_internal_key = Some(change_xpub.to_x_only_pub()); - psbt.outputs[0].tap_key_origins.insert( - change_xpub.to_x_only_pub(), - (vec![], (fingerprint, change_path.clone())), - ); +#[derive(Deserialize)] +struct PsbtFixture { + transaction: String, + #[serde(default)] + options: PsbtSignOptions, +} - // Sign. - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - None, - pb::btc_sign_init_request::FormatUnit::Default, - ) - .await - .unwrap(); +#[derive(Clone, Copy, Deserialize)] +#[serde(rename_all = "snake_case")] +enum Coin { + Btc, + Tbtc, + Ltc, +} - // Finalize, add witnesses. - psbt.finalize_mut(&secp).unwrap(); +#[derive(Deserialize)] +struct VersionExpectation { + min_version: Option, + max_version_exclusive: Option, + outcome: Outcome, + unsupported_version: Option, + screens: Vec, +} - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }) - .await +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ExpectedScreen { + Confirm { + title: String, + body: String, + }, + TransactionAddress { + amount: String, + address: String, + }, + TransactionFee { + amount: String, + fee: String, + }, + Status { + title: String, + body: String, + }, + Swap { + title: String, + from: String, + to: String, + }, } -#[tokio::test] -async fn test_btc_psbt_op_return() { - test_initialized_simulators(async |bitbox| { - if !semver::VersionReq::parse(">=9.24.0") - .unwrap() - .matches(bitbox.version()) - { - // OP_RETURN outputs are supported from firmware v9.24.0. - return; +impl ExpectedScreen { + // `longtouch` is intentionally omitted: released simulators do not report it. + fn observable(&self) -> SimulatorScreen { + match self { + Self::Confirm { title, body } => SimulatorScreen::Confirm { + title: title.clone(), + body: body.clone(), + }, + Self::TransactionAddress { amount, address } => SimulatorScreen::TransactionAddress { + amount: amount.clone(), + address: address.clone(), + }, + Self::TransactionFee { amount, fee } => SimulatorScreen::TransactionFee { + amount: amount.clone(), + fee: fee.clone(), + }, + Self::Status { title, body } => SimulatorScreen::Status { + title: title.clone(), + body: body.clone(), + }, + Self::Swap { title, from, to } => SimulatorScreen::Swap { + title: title.clone(), + from: from.clone(), + to: to.clone(), + }, } + } +} - let secp = secp256k1::Secp256k1::new(); - let fingerprint = util::simulator_xprv().fingerprint(&secp); - - let input_path: DerivationPath = "m/84'/1'/0'/0/5".parse().unwrap(); - let change_path: DerivationPath = "m/84'/1'/0'/1/0".parse().unwrap(); - - let input_pub = util::simulator_xpub_at(&secp, &input_path).to_pub(); - let change_pub = util::simulator_xpub_at(&secp, &change_path).to_pub(); - - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: - "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![TxOut { - value: Amount::from_sat(50_000_000), - script_pubkey: ScriptBuf::new_p2wpkh(&input_pub.wpubkey_hash()), - }], - }; - - let op_return_data = b"hello world"; - let op_return_script = Builder::new() - .push_opcode(all::OP_RETURN) - .push_slice(op_return_data) - .into_script(); - - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(49_000_000), - script_pubkey: ScriptBuf::new_p2wpkh(&change_pub.wpubkey_hash()), - }, - TxOut { - value: Amount::from_sat(0), - script_pubkey: op_return_script.clone(), - }, - ], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone()); - psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone()); - psbt.inputs[0] - .bip32_derivation - .insert(input_pub.0, (fingerprint, input_path.clone())); - - psbt.outputs[0] - .bip32_derivation - .insert(change_pub.0, (fingerprint, change_path.clone())); +#[derive(Clone, Copy, Deserialize)] +#[serde(rename_all = "snake_case")] +enum Outcome { + Success, + Unsupported, + InvalidInput, +} - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - None, - pb::btc_sign_init_request::FormatUnit::Default, - ) - .await - .unwrap(); +#[derive(Default, Deserialize)] +struct PsbtSignOptions { + force_script_config: Option, + #[serde(default)] + outputs: BTreeMap, + #[serde(default)] + payment_requests: Vec, + #[serde(default)] + format_unit: FormatUnit, +} - psbt.finalize_mut(&secp).unwrap(); +#[derive(Clone, Copy, Default, Deserialize)] +#[serde(rename_all = "snake_case")] +enum FormatUnit { + #[default] + Default, + Sat, +} - let final_tx = psbt.clone().extract_tx_unchecked_fee_rate(); - assert_eq!(final_tx.output.len(), 2); - assert_eq!(final_tx.output[1].value, Amount::from_sat(0)); - assert_eq!(final_tx.output[1].script_pubkey, op_return_script); +#[derive(Deserialize)] +struct ScriptConfigWithKeypath { + script_config: ScriptConfig, + keypath: String, +} - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }) - .await +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ScriptConfig { + Simple { + script_type: SimpleType, + }, + Multisig { + threshold: u32, + xpubs: Vec, + our_xpub_index: u32, + script_type: MultisigScriptType, + }, + Policy { + policy: String, + keys: Vec, + }, } -#[tokio::test] -async fn test_btc_psbt_multisig_p2wsh() { - test_initialized_simulators(async |bitbox| { - let secp = secp256k1::Secp256k1::new(); +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +enum SimpleType { + P2wpkh, + P2wpkhP2sh, + P2tr, +} - let coin = pb::BtcCoin::Tbtc; +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +enum MultisigScriptType { + P2wsh, + P2wshP2sh, +} - let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp); +#[derive(Deserialize)] +struct KeyOriginInfo { + root_fingerprint: Option, + keypath: Option, + xpub: String, +} - let threshold: u32 = 1; - let keypath_account: DerivationPath = "m/48'/1'/0'/2'".parse().unwrap(); +#[derive(Deserialize)] +struct Registration { + script_config: ScriptConfig, + keypath: Option, + name: String, +} - let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account); - let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap(); +#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd)] +struct SignatureSlot { + input_index: usize, + kind: SignatureKind, + pubkey: Option, + leaf_hash: Option, + sighash: Sighash, +} - // We use the miniscript library to build a multipath descriptor including key origin so we can - // easily derive the receive/change descriptor, pubkey scripts, populate the PSBT input key - // infos and convert the sigs to final witnesses. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd)] +#[serde(rename_all = "snake_case")] +enum SignatureKind { + Ecdsa, + TaprootKey, + TaprootScript, +} - let multi_descriptor: miniscript::Descriptor = format!( - "wsh(sortedmulti({},[{}/48'/1'/0'/2']{}/<0;1>/*,{}/<0;1>/*))", - threshold, our_root_fingerprint, our_xpub, some_xpub - ) - .parse::>() - .unwrap(); - assert!(multi_descriptor.sanity_check().is_ok()); +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd)] +#[serde(rename_all = "snake_case")] +enum Sighash { + All, + Default, +} - let [descriptor_receive, descriptor_change] = multi_descriptor - .into_single_descriptors() - .unwrap() - .try_into() - .unwrap(); - // Derive /0/0 (first receive) and /1/0 (first change) descriptors. - let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap(); - let change_descriptor = descriptor_change.at_derivation_index(0).unwrap(); - let multisig_config = bitbox_api::btc::make_script_config_multisig( - threshold, - &[our_xpub, some_xpub], - 0, - pb::btc_script_config::multisig::ScriptType::P2wsh, - ); +fn expectation_for<'a>( + expectations: &'a [VersionExpectation], + version: &Version, +) -> &'a VersionExpectation { + expectations + .iter() + .find(|expectation| { + let after_min = expectation + .min_version + .as_deref() + .is_none_or(|min| version >= &Version::parse(min).unwrap()); + let before_max = expectation + .max_version_exclusive + .as_deref() + .is_none_or(|max| version < &Version::parse(max).unwrap()); + after_min && before_max + }) + .unwrap_or_else(|| panic!("no expectation matches firmware {version}")) +} - // Register policy if not already registered. This must be done before any receive address is - // created or any transaction is signed. - let is_registered = bitbox - .btc_is_script_config_registered(coin, &multisig_config, None) - .await - .unwrap(); +fn convert_keypath(path: &str) -> Keypath { + Keypath::try_from(path).unwrap() +} - if !is_registered { - bitbox - .btc_register_script_config( - coin, - &multisig_config, - Some(&(&keypath_account).into()), - pb::btc_register_script_config_request::XPubType::AutoXpubTpub, - Some("test wsh multisig"), - ) - .await - .unwrap(); +fn convert_script_config(config: &ScriptConfig) -> pb::BtcScriptConfig { + match config { + ScriptConfig::Simple { script_type } => { + bitbox_api::btc::make_script_config_simple(match script_type { + SimpleType::P2wpkh => pb::btc_script_config::SimpleType::P2wpkh, + SimpleType::P2wpkhP2sh => pb::btc_script_config::SimpleType::P2wpkhP2sh, + SimpleType::P2tr => pb::btc_script_config::SimpleType::P2tr, + }) } - - // A previous tx which creates some UTXOs we can reference later. - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: input_descriptor.script_pubkey(), - }], - }; - - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(70_000_000), - script_pubkey: change_descriptor.script_pubkey(), - }, - TxOut { - value: Amount::from_sat(20_000_000), - script_pubkey: ScriptBuf::new_p2tr( - &secp, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - "e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576" - .parse() - .unwrap(), - None, - ), + ScriptConfig::Multisig { + threshold, + xpubs, + our_xpub_index, + script_type, + } => { + let xpubs = xpubs + .iter() + .map(|xpub| xpub.parse::().unwrap()) + .collect::>(); + bitbox_api::btc::make_script_config_multisig( + *threshold, + &xpubs, + *our_xpub_index, + match script_type { + MultisigScriptType::P2wsh => pb::btc_script_config::multisig::ScriptType::P2wsh, + MultisigScriptType::P2wshP2sh => { + pb::btc_script_config::multisig::ScriptType::P2wshP2sh + } }, - ], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - // Add input and change infos. - psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone()); - // These add the input/output bip32_derivation entries / key infos. - psbt.update_input_with_descriptor(0, &input_descriptor) - .unwrap(); - psbt.update_output_with_descriptor(0, &change_descriptor) - .unwrap(); - - // Sign. - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - Some(pb::BtcScriptConfigWithKeypath { - script_config: Some(multisig_config), - keypath: keypath_account.to_u32_vec(), - }), - pb::btc_sign_init_request::FormatUnit::Default, ) - .await - .unwrap(); - - // Finalize, add witnesses. - psbt.finalize_mut(&secp).unwrap(); - - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }).await + } + ScriptConfig::Policy { policy, keys } => { + let keys = keys + .iter() + .map(|key| bitbox_api::btc::KeyOriginInfo { + root_fingerprint: key + .root_fingerprint + .as_deref() + .map(|fingerprint| fingerprint.parse::().unwrap()), + keypath: key.keypath.as_deref().map(convert_keypath), + xpub: key.xpub.parse().unwrap(), + }) + .collect::>(); + bitbox_api::btc::make_script_config_policy(policy, &keys) + } + } } -#[tokio::test] -async fn test_btc_psbt_policy_wsh() { - test_initialized_simulators(async |bitbox| { - let secp = secp256k1::Secp256k1::new(); - - let coin = pb::BtcCoin::Tbtc; - // Policy string following BIP-388 syntax, input to the BitBox. - let policy = "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))"; - - let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp); - - let keypath_account: DerivationPath = "m/48'/1'/0'/3'".parse().unwrap(); - - let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account); - let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap(); - - // We use the miniscript library to build a multipath descriptor including key origin so we can - // easily derive the receive/change descriptor, pubkey scripts, populate the PSBT input key - // infos and convert the sigs to final witnesses. - - let multi_descriptor: miniscript::Descriptor = policy - .replace( - "@0", - &format!("[{}/48'/1'/0'/3']{}", our_root_fingerprint, our_xpub), - ) - .replace("@1", &some_xpub.to_string()) - .parse::>() - .unwrap(); - assert!(multi_descriptor.sanity_check().is_ok()); +fn convert_script_config_with_keypath( + config: &ScriptConfigWithKeypath, +) -> pb::BtcScriptConfigWithKeypath { + pb::BtcScriptConfigWithKeypath { + script_config: Some(convert_script_config(&config.script_config)), + keypath: convert_keypath(&config.keypath).to_vec(), + } +} - let [descriptor_receive, descriptor_change] = multi_descriptor - .into_single_descriptors() - .unwrap() - .try_into() - .unwrap(); - // Derive /0/0 (first receive) and /1/0 (first change) descriptors. - let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap(); - let change_descriptor = descriptor_change.at_derivation_index(0).unwrap(); +fn skip_reason(vector: &TestVector) -> Option<&'static str> { + if !vector.psbt.options.outputs.is_empty() || !vector.psbt.options.payment_requests.is_empty() { + return Some("btc_sign_psbt does not expose per-output options or payment requests"); + } + None +} - let keys = &[ - // Our key: root fingerprint and keypath are required. - bitbox_api::btc::KeyOriginInfo { - root_fingerprint: Some(our_root_fingerprint), - keypath: Some((&keypath_account).into()), - xpub: our_xpub, - }, - // Foreign key: root fingerprint and keypath are optional. - bitbox_api::btc::KeyOriginInfo { - root_fingerprint: None, - keypath: None, - xpub: some_xpub, - }, - ]; - let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys); +fn ecdsa_sighash(sighash: EcdsaSighashType) -> Sighash { + match sighash { + EcdsaSighashType::All => Sighash::All, + other => panic!("unsupported ECDSA sighash {other:?}"), + } +} - // Register policy if not already registered. This must be done before any receive address is - // created or any transaction is signed. - let is_registered = bitbox - .btc_is_script_config_registered(coin, &policy_config, None) - .await - .unwrap(); +fn taproot_sighash(sighash: TapSighashType) -> Sighash { + match sighash { + TapSighashType::All => Sighash::All, + TapSighashType::Default => Sighash::Default, + other => panic!("unsupported Taproot sighash {other:?}"), + } +} - if !is_registered { - bitbox - .btc_register_script_config( - coin, - &policy_config, - None, - pb::btc_register_script_config_request::XPubType::AutoXpubTpub, - Some("test wsh policy"), - ) - .await - .unwrap(); +fn signature_slots(psbt: &Psbt) -> BTreeSet { + let mut slots = BTreeSet::new(); + for (input_index, input) in psbt.inputs.iter().enumerate() { + for (pubkey, signature) in &input.partial_sigs { + slots.insert(SignatureSlot { + input_index, + kind: SignatureKind::Ecdsa, + pubkey: Some(pubkey.to_string()), + leaf_hash: None, + sighash: ecdsa_sighash(signature.sighash_type), + }); } + if let Some(signature) = &input.tap_key_sig { + slots.insert(SignatureSlot { + input_index, + kind: SignatureKind::TaprootKey, + pubkey: input.tap_internal_key.map(|pubkey| pubkey.to_string()), + leaf_hash: None, + sighash: taproot_sighash(signature.sighash_type), + }); + } + for ((pubkey, leaf_hash), signature) in &input.tap_script_sigs { + slots.insert(SignatureSlot { + input_index, + kind: SignatureKind::TaprootScript, + pubkey: Some(pubkey.to_string()), + leaf_hash: Some(leaf_hash.to_string()), + sighash: taproot_sighash(signature.sighash_type), + }); + } + } + slots +} - // A previous tx which creates some UTXOs we can reference later. - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: input_descriptor.script_pubkey(), - }], - }; +fn assert_signature_insertions( + vector: &TestVector, + before: &BTreeSet, + after: &BTreeSet, +) { + assert!( + before.is_subset(after), + "{}: signing removed signature slots: before={before:?}, after={after:?}", + vector.context() + ); + let inserted = after.difference(before).cloned().collect::>(); + assert_eq!( + inserted, + vector.expected_signatures, + "{}: inserted signature slots differ", + vector.context() + ); +} - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(70_000_000), - script_pubkey: change_descriptor.script_pubkey(), - }, - TxOut { - value: Amount::from_sat(20_000_000), - script_pubkey: ScriptBuf::new_p2tr( - &secp, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - "e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576" - .parse() - .unwrap(), - None, - ), - }, - ], - }; +fn wait_for_stdout( + stdout: &SimulatorStdout, + checkpoint: SimulatorStdoutCheckpoint, +) -> SimulatorStdoutSnapshot { + stdout + .wait_until_stable(checkpoint, STDOUT_STABLE_FOR, STDOUT_TIMEOUT) + .unwrap_or_else(|err| panic!("waiting for simulator stdout failed: {err:?}")) +} - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); +fn wait_for_terminal_stdout( + stdout: &SimulatorStdout, + checkpoint: SimulatorStdoutCheckpoint, +) -> SimulatorStdoutSnapshot { + stdout + .wait_for_terminal_screen(checkpoint, STDOUT_TIMEOUT) + .unwrap_or_else(|err| panic!("waiting for terminal simulator screen failed: {err:?}")) +} - // Add input and change infos. - psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone()); - // These add the input/output bip32_derivation entries / key infos. - psbt.update_input_with_descriptor(0, &input_descriptor) - .unwrap(); - psbt.update_output_with_descriptor(0, &change_descriptor) - .unwrap(); +fn assert_screens( + vector: &TestVector, + device_version: &Version, + expected: &[ExpectedScreen], + snapshot: &SimulatorStdoutSnapshot, +) { + let actual = snapshot + .screens() + .unwrap_or_else(|err| panic!("{}: {err}\n{}", vector.context(), snapshot.raw())); + if std::env::var("UPDATE_BTC_VECTOR_SCREENS").as_deref() == Ok("1") { + println!( + "screens for {} on firmware {}: {}", + vector.id, + device_version, + serde_json::to_string(&actual).unwrap() + ); + return; + } + let expected: Vec<_> = expected.iter().map(ExpectedScreen::observable).collect(); + assert_eq!( + actual, + expected, + "{}: simulator screens differ\n{}", + vector.context(), + snapshot.raw() + ); +} - // Sign. - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - Some(pb::BtcScriptConfigWithKeypath { - script_config: Some(policy_config), - keypath: keypath_account.to_u32_vec(), - }), - pb::btc_sign_init_request::FormatUnit::Default, +fn assert_unsupported_error( + vector: &TestVector, + device_version: &Version, + expectation: &VersionExpectation, + error: &bitbox_api::error::Error, +) { + let minimum = expectation + .unsupported_version + .as_deref() + .unwrap_or_else(|| { + panic!( + "{}: unsupported outcome has no minimum version", + vector.context() ) - .await - .unwrap(); - - // Finalize, add witnesses. - psbt.finalize_mut(&secp).unwrap(); + }); + let minimum_version = Version::parse(minimum).unwrap(); + assert!( + device_version < &minimum_version, + "{}: unsupported outcome selected for firmware {} at or above {}", + vector.context(), + device_version, + minimum + ); + + match error { + bitbox_api::error::Error::Version(requirement) => assert_eq!( + *requirement, + format!(">={minimum}"), + "{}: wrong client-side firmware requirement", + vector.context() + ), + bitbox_api::error::Error::BitBox( + bitbox_api::error::BitBoxError::InvalidInput | bitbox_api::error::BitBoxError::Disabled, + ) => {} + other => panic!( + "{}: unexpected error for unsupported firmware: {other:?}", + vector.context() + ), + } +} - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }).await +fn assert_invalid_input_error(vector: &TestVector, error: &bitbox_api::error::Error) { + assert!( + matches!( + error, + bitbox_api::error::Error::BitBox(bitbox_api::error::BitBoxError::InvalidInput) + ), + "{}: expected invalid input, got {error:?}", + vector.context() + ); } -#[tokio::test] -async fn test_btc_psbt_policy_tr_keyspend() { - test_initialized_simulators(async |bitbox| { - if !semver::VersionReq::parse(">=9.21.0") - .unwrap() - .matches(bitbox.version()) +async fn register_script_configs( + bitbox: &bitbox_api::PairedBitBox, + coin: pb::BtcCoin, + vector: &TestVector, +) -> Result<(), bitbox_api::error::Error> { + for registration in &vector.registrations { + let config = convert_script_config(®istration.script_config); + let keypath = registration.keypath.as_deref().map(convert_keypath); + if !bitbox + .btc_is_script_config_registered(coin, &config, keypath.as_ref()) + .await? { - // Taproot policies were added in v9.21.0. - return; - } - let secp = secp256k1::Secp256k1::new(); - - let coin = pb::BtcCoin::Tbtc; - // Policy string following BIP-388 syntax, input to the BitBox. - let policy = "tr(@0/<0;1>/*)"; - - let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp); - - let keypath_account: DerivationPath = "m/48'/1'/0'/3'".parse().unwrap(); - - let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account); - - // We use the miniscript library to build a multipath descriptor including key origin so we can - // easily derive the receive/change descriptor, pubkey scripts, populate the PSBT input key - // infos and convert the sigs to final witnesses. - - let multi_descriptor: miniscript::Descriptor = policy - .replace( - "@0", - &format!("[{}/48'/1'/0'/3']{}", our_root_fingerprint, our_xpub), - ) - .parse::>() - .unwrap(); - assert!(multi_descriptor.sanity_check().is_ok()); - - let [descriptor_receive, descriptor_change] = multi_descriptor - .into_single_descriptors() - .unwrap() - .try_into() - .unwrap(); - // Derive /0/0 (first receive) and /1/0 (first change) descriptors. - let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap(); - let change_descriptor = descriptor_change.at_derivation_index(0).unwrap(); - - let keys = &[ - // Our key: root fingerprint and keypath are required. - bitbox_api::btc::KeyOriginInfo { - root_fingerprint: Some(our_root_fingerprint), - keypath: Some((&keypath_account).into()), - xpub: our_xpub, - }, - ]; - let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys); - - // Register policy if not already registered. This must be done before any receive address is - // created or any transaction is signed. - let is_registered = bitbox - .btc_is_script_config_registered(coin, &policy_config, None) - .await - .unwrap(); - - if !is_registered { bitbox .btc_register_script_config( coin, - &policy_config, - None, + &config, + keypath.as_ref(), pb::btc_register_script_config_request::XPubType::AutoXpubTpub, - Some("test tr keyspend policy"), + Some(®istration.name), ) - .await - .unwrap(); + .await?; } + } + Ok(()) +} - // A previous tx which creates some UTXOs we can reference later. - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: - "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: input_descriptor.script_pubkey(), - }], - }; - - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(70_000_000), - script_pubkey: change_descriptor.script_pubkey(), - }, - TxOut { - value: Amount::from_sat(20_000_000), - script_pubkey: ScriptBuf::new_p2tr( - &secp, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - "e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576" - .parse() - .unwrap(), - None, - ), - }, - ], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - // Add input and change infos. - psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone()); - // These add the input/output bip32_derivation entries / key infos. - psbt.update_input_with_descriptor(0, &input_descriptor) - .unwrap(); - psbt.update_output_with_descriptor(0, &change_descriptor) - .unwrap(); - - // Sign. - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - Some(pb::BtcScriptConfigWithKeypath { - script_config: Some(policy_config), - keypath: keypath_account.to_u32_vec(), - }), - pb::btc_sign_init_request::FormatUnit::Default, - ) - .await - .unwrap(); - - // Finalize, add witnesses. - psbt.finalize_mut(&secp).unwrap(); +fn convert_coin(coin: Coin) -> pb::BtcCoin { + match coin { + Coin::Btc => pb::BtcCoin::Btc, + Coin::Tbtc => pb::BtcCoin::Tbtc, + Coin::Ltc => pb::BtcCoin::Ltc, + } +} - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }) - .await +fn convert_format_unit(format_unit: FormatUnit) -> pb::btc_sign_init_request::FormatUnit { + match format_unit { + FormatUnit::Default => pb::btc_sign_init_request::FormatUnit::Default, + FormatUnit::Sat => pb::btc_sign_init_request::FormatUnit::Sat, + } } -#[tokio::test] -async fn test_btc_psbt_policy_tr_scriptspend() { - test_initialized_simulators(async |bitbox| { - if !semver::VersionReq::parse(">=9.21.0") - .unwrap() - .matches(bitbox.version()) - { - // Taproot policies were added in v9.21.0. - return; +async fn run_vector( + bitbox: &bitbox_api::PairedBitBox, + stdout: &SimulatorStdout, + vector: &TestVector, +) { + let expectation = expectation_for(&vector.expectations, bitbox.version()); + + let psbt_bytes = hex::decode(&vector.psbt.transaction) + .unwrap_or_else(|err| panic!("{}: {err}", vector.context())); + let mut psbt = Psbt::deserialize(&psbt_bytes) + .unwrap_or_else(|err| panic!("{}: invalid PSBT: {err}", vector.context())); + + let before = signature_slots(&psbt); + let coin = convert_coin(vector.coin); + + let setup_checkpoint = stdout.checkpoint(); + let setup_result = register_script_configs(bitbox, coin, vector).await; + wait_for_stdout(stdout, setup_checkpoint); + if let Err(error) = setup_result { + match expectation.outcome { + Outcome::Unsupported => { + assert_unsupported_error(vector, bitbox.version(), expectation, &error) + } + Outcome::InvalidInput => assert_invalid_input_error(vector, &error), + Outcome::Success => panic!( + "{}: registering script config failed: {error:?}", + vector.context() + ), } - let secp = secp256k1::Secp256k1::new(); - - let coin = pb::BtcCoin::Tbtc; - // Policy string following BIP-388 syntax, input to the BitBox. - let policy = "tr(@0/<0;1>/*,pk(@1/<0;1>/*))"; - - let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp); - let keypath_account: DerivationPath = "m/48'/1'/0'/3'".parse().unwrap(); - - let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account); - let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap(); - - // We use the miniscript library to build a multipath descriptor including key origin so we can - // easily derive the receive/change descriptor, pubkey scripts, populate the PSBT input key - // infos and convert the sigs to final witnesses. - - let multi_descriptor: miniscript::Descriptor = policy - .replace( - "@1", - &format!("[{}/48'/1'/0'/3']{}", our_root_fingerprint, our_xpub), - ) - .replace("@0", &some_xpub.to_string()) - .parse::>() - .unwrap(); - assert!(multi_descriptor.sanity_check().is_ok()); - - let [descriptor_receive, descriptor_change] = multi_descriptor - .into_single_descriptors() - .unwrap() - .try_into() - .unwrap(); - // Derive /0/0 (first receive) and /1/0 (first change) descriptors. - let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap(); - let change_descriptor = descriptor_change.at_derivation_index(0).unwrap(); - - let keys = &[ - // Foreign key: root fingerprint and keypath are optional. - bitbox_api::btc::KeyOriginInfo { - root_fingerprint: None, - keypath: None, - xpub: some_xpub, - }, - // Our key: root fingerprint and keypath are required. - bitbox_api::btc::KeyOriginInfo { - root_fingerprint: Some(our_root_fingerprint), - keypath: Some((&keypath_account).into()), - xpub: our_xpub, - }, - ]; - let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys); - - // Register policy if not already registered. This must be done before any receive address is - // created or any transaction is signed. - let is_registered = bitbox - .btc_is_script_config_registered(coin, &policy_config, None) - .await - .unwrap(); + // Registration output is setup noise, never part of the transaction screen expectation. + let transaction_checkpoint = stdout.checkpoint(); + let transaction_output = wait_for_stdout(stdout, transaction_checkpoint); + assert_screens( + vector, + bitbox.version(), + &expectation.screens, + &transaction_output, + ); + assert_eq!( + signature_slots(&psbt), + before, + "{}: failed setup changed the PSBT", + vector.context() + ); + return; + } - if !is_registered { - bitbox - .btc_register_script_config( - coin, - &policy_config, - None, - pb::btc_register_script_config_request::XPubType::AutoXpubTpub, - Some("test tr scriptspend policy"), - ) - .await - .unwrap(); + let force_script_config = vector + .psbt + .options + .force_script_config + .as_ref() + .map(convert_script_config_with_keypath); + let format_unit = convert_format_unit(vector.psbt.options.format_unit); + + let transaction_checkpoint = stdout.checkpoint(); + let sign_result = bitbox + .btc_sign_psbt(coin, &mut psbt, force_script_config, format_unit) + .await; + let transaction_output = match expectation.outcome { + Outcome::Success => wait_for_terminal_stdout(stdout, transaction_checkpoint), + Outcome::Unsupported | Outcome::InvalidInput => { + wait_for_stdout(stdout, transaction_checkpoint) } + }; + assert_screens( + vector, + bitbox.version(), + &expectation.screens, + &transaction_output, + ); + + match expectation.outcome { + Outcome::Success => { + sign_result + .unwrap_or_else(|err| panic!("{}: signing failed: {err:?}", vector.context())); + let after = signature_slots(&psbt); + assert_signature_insertions(vector, &before, &after); + + let secp = secp256k1::Secp256k1::new(); + psbt.finalize_mut(&secp) + .unwrap_or_else(|err| panic!("{}: finalization failed: {err:?}", vector.context())); + verify_transaction(psbt); + } + Outcome::Unsupported => { + let error = sign_result.unwrap_err(); + assert_unsupported_error(vector, bitbox.version(), expectation, &error); + assert_eq!( + signature_slots(&psbt), + before, + "{}: unsupported signing changed the PSBT", + vector.context() + ); + } + Outcome::InvalidInput => { + let error = sign_result.unwrap_err(); + assert_invalid_input_error(vector, &error); + assert_eq!( + signature_slots(&psbt), + before, + "{}: failed signing changed the PSBT", + vector.context() + ); + } + } +} - // A previous tx which creates some UTXOs we can reference later. - let prev_tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: "3131313131313131313131313131313131313131313131313131313131313131:0" - .parse() - .unwrap(), - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![TxOut { - value: Amount::from_sat(100_000_000), - script_pubkey: input_descriptor.script_pubkey(), - }], - }; - - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { - txid: prev_tx.compute_txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0xFFFFFFFF), - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: Amount::from_sat(70_000_000), - script_pubkey: change_descriptor.script_pubkey(), - }, - TxOut { - value: Amount::from_sat(20_000_000), - script_pubkey: ScriptBuf::new_p2tr( - &secp, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - "e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576" - .parse() - .unwrap(), - None, - ), - }, - ], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - // Add input and change infos. - psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone()); - // These add the input/output bip32_derivation entries / key infos. - psbt.update_input_with_descriptor(0, &input_descriptor) - .unwrap(); - psbt.update_output_with_descriptor(0, &change_descriptor) - .unwrap(); - // Sign. - bitbox - .btc_sign_psbt( - pb::BtcCoin::Tbtc, - &mut psbt, - Some(pb::BtcScriptConfigWithKeypath { - script_config: Some(policy_config), - keypath: keypath_account.to_u32_vec(), - }), - pb::btc_sign_init_request::FormatUnit::Default, - ) - .await - .unwrap(); - - // Finalize, add witnesses. - psbt.finalize_mut(&secp).unwrap(); - - // Verify the signed tx, including that all sigs/witnesses are correct. - verify_transaction(psbt); - }).await +#[tokio::test] +async fn test_btc_transaction_vectors() { + let file: TestVectorFile = serde_json::from_str(VECTOR_JSON).unwrap(); + + test_initialized_simulators_with_stdout(async |bitbox, stdout| { + // Keep restore/pairing stdout out of the first vector checkpoint. + let startup_checkpoint = stdout.checkpoint(); + wait_for_stdout(stdout, startup_checkpoint); + for vector in &file.vectors { + println!("\t\tVector: {}", vector.context()); + if let Some(reason) = skip_reason(vector) { + println!("\t\tSkipping: {reason}"); + } else { + run_vector(bitbox, stdout, vector).await; + } + } + }) + .await; } diff --git a/tests/test_simulator_stdout.rs b/tests/test_simulator_stdout.rs new file mode 100644 index 0000000..bc5c260 --- /dev/null +++ b/tests/test_simulator_stdout.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(feature = "simulator")] +// Simulator support is available only on linux/amd64. +#![cfg(all(target_os = "linux", target_arch = "x86_64"))] + +mod util; + +use std::time::Duration; + +use util::{parse_simulator_screens, SimulatorScreen, SimulatorStdout, SimulatorStdoutWaitError}; + +#[test] +fn parses_structured_simulator_screens() { + let output = r#"USB setup success +CONFIRM SCREEN START +TITLE: High fee +BODY: The fee is 50.0% +the send amount. +Proceed? +CONFIRM SCREEN END +CONFIRM TRANSACTION ADDRESS SCREEN START +AMOUNT: 0.20000000 TBTC +ADDRESS: tb1p abcd efgh +CONFIRM TRANSACTION ADDRESS SCREEN END +CONFIRM TRANSACTION FEE SCREEN START +AMOUNT: 0.30000000 TBTC +FEE: 0.10000000 TBTC +CONFIRM TRANSACTION FEE SCREEN END +STATUS SCREEN START +TITLE: Transaction +confirmed +and complete +STATUS SCREEN END +CONFIRM SWAP SCREEN START +TITLE: Confirm swap +FROM: 1 BTC +on Bitcoin +TO: 10 ETH +on Ethereum +CONFIRM SWAP SCREEN END +"#; + + assert_eq!( + parse_simulator_screens(output).unwrap(), + vec![ + SimulatorScreen::Confirm { + title: "High fee".into(), + body: "The fee is 50.0%\nthe send amount.\nProceed?".into(), + }, + SimulatorScreen::TransactionAddress { + amount: "0.20000000 TBTC".into(), + address: "tb1p abcd efgh".into(), + }, + SimulatorScreen::TransactionFee { + amount: "0.30000000 TBTC".into(), + fee: "0.10000000 TBTC".into(), + }, + SimulatorScreen::Status { + title: "Transaction".into(), + body: "confirmed\nand complete".into(), + }, + SimulatorScreen::Swap { + title: "Confirm swap".into(), + from: "1 BTC\non Bitcoin".into(), + to: "10 ETH\non Ethereum".into(), + }, + ] + ); +} + +#[test] +fn screen_json_is_tagged_and_roundtrips() { + let screen = SimulatorScreen::Confirm { + title: String::new(), + body: "Show policy\ndetails?".into(), + }; + let value = serde_json::to_value(&screen).unwrap(); + + assert_eq!( + value, + serde_json::json!({ + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + }) + ); + assert_eq!( + serde_json::from_value::(value.clone()).unwrap(), + screen + ); + let mut unknown_field = value; + unknown_field + .as_object_mut() + .unwrap() + .insert("unknown".into(), true.into()); + assert!(serde_json::from_value::(unknown_field).is_err()); +} + +#[test] +fn reports_incomplete_and_invalid_screen_blocks() { + let incomplete = + parse_simulator_screens("CONFIRM SCREEN START\nTITLE: Warning\nBODY: Continue?\n") + .unwrap_err(); + assert_eq!(incomplete.line, 1); + assert_eq!(incomplete.message, "missing CONFIRM SCREEN END"); + + let missing_body = + parse_simulator_screens("CONFIRM SCREEN START\nTITLE: Warning\nCONFIRM SCREEN END\n") + .unwrap_err(); + assert_eq!(missing_body.line, 1); + assert_eq!(missing_body.message, "missing BODY field"); + + for invalid in [ + "CONFIRM SWAP SCREEN START\nFROM: 1 BTC\nTITLE: Swap\nTO: 20 ETH\nCONFIRM SWAP SCREEN END\n", + "CONFIRM SCREEN START\nunexpected\nTITLE: Title\nBODY: Body\nCONFIRM SCREEN END\n", + "CONFIRM SCREEN START\nTITLE: Outer\nBODY: Body\nSTATUS SCREEN START\nTITLE: Inner\nSTATUS SCREEN END\nCONFIRM SCREEN END\n", + "CONFIRM SCREEN START\nTITLE: Title\nBODY: Body\nSTATUS SCREEN END\nCONFIRM SCREEN END\n", + "FUTURE SCREEN START\nTITLE: Future\nFUTURE SCREEN END\n", + "FUTURE SCREEN END\n", + ] { + assert!( + parse_simulator_screens(invalid).is_err(), + "accepted malformed simulator output: {invalid}" + ); + } +} + +#[test] +fn checkpoints_isolate_snapshots_and_wait_for_stability() { + let stdout = SimulatorStdout::new(); + stdout.record_line("before checkpoint".into()); + let checkpoint = stdout.checkpoint(); + stdout.record_line("first".into()); + stdout.record_line("second".into()); + + let snapshot = stdout + .wait_until_stable(checkpoint, Duration::from_millis(5), Duration::from_secs(1)) + .unwrap(); + assert_eq!(snapshot.lines(), &["first", "second"]); + assert_eq!(snapshot.raw(), "first\nsecond\n"); + assert_eq!(stdout.snapshot(checkpoint), snapshot); +} + +#[test] +fn waits_for_a_complete_terminal_screen() { + let stdout = SimulatorStdout::new(); + let checkpoint = stdout.checkpoint(); + let writer = stdout.clone(); + let thread = std::thread::spawn(move || { + for line in [ + "STATUS SCREEN START", + "TITLE: Transaction", + "confirmed", + "STATUS SCREEN END", + ] { + writer.record_line(line.into()); + } + }); + + let snapshot = stdout + .wait_for_terminal_screen(checkpoint, Duration::from_secs(1)) + .unwrap(); + thread.join().unwrap(); + assert_eq!( + snapshot.screens().unwrap(), + vec![SimulatorScreen::Status { + title: "Transaction".into(), + body: "confirmed".into(), + }] + ); + + let no_terminal = SimulatorStdout::new(); + let checkpoint = no_terminal.checkpoint(); + assert!(matches!( + no_terminal.wait_for_terminal_screen(checkpoint, Duration::ZERO), + Err(SimulatorStdoutWaitError::Timeout(_)) + )); +} diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 0ff667c..9511034 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -12,6 +12,9 @@ use std::io::BufRead; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::process::{Child, Command}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::JoinHandle; +use std::time::{Duration, Instant}; use tokio::fs::{self, File}; use tokio::io::{self, AsyncReadExt}; @@ -36,45 +39,433 @@ struct Simulator { sha256: String, } -struct Server(Child); +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)] +pub enum SimulatorScreen { + Confirm { + title: String, + body: String, + }, + TransactionAddress { + amount: String, + address: String, + }, + TransactionFee { + amount: String, + fee: String, + }, + Status { + title: String, + body: String, + }, + Swap { + title: String, + from: String, + to: String, + }, +} + +impl SimulatorScreen { + pub fn is_terminal(&self) -> bool { + matches!(self, Self::Status { .. }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SimulatorScreenParseError { + pub line: usize, + pub message: String, +} + +impl std::fmt::Display for SimulatorScreenParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "simulator stdout line {}: {}", self.line, self.message) + } +} + +impl std::error::Error for SimulatorScreenParseError {} + +fn parse_error(line: usize, message: impl Into) -> SimulatorScreenParseError { + SimulatorScreenParseError { + line, + message: message.into(), + } +} + +fn parse_screen_fields( + lines: &[&str], + block_line: usize, + fields: &[&str], +) -> Result, SimulatorScreenParseError> { + let mut result = Vec::with_capacity(fields.len()); + let mut line_index = 0; + + for (field_index, field) in fields.iter().enumerate() { + let Some(line) = lines.get(line_index) else { + return Err(parse_error(block_line, format!("missing {field} field"))); + }; + let prefix = format!("{field}: "); + let Some(value) = line.strip_prefix(&prefix) else { + return Err(parse_error( + block_line, + format!("expected {prefix:?}, got {line:?}"), + )); + }; + + let mut value_lines = vec![value]; + line_index += 1; + if let Some(next_field) = fields.get(field_index + 1) { + let next_prefix = format!("{next_field}: "); + while line_index < lines.len() && !lines[line_index].starts_with(&next_prefix) { + value_lines.push(lines[line_index]); + line_index += 1; + } + } else { + value_lines.extend_from_slice(&lines[line_index..]); + line_index = lines.len(); + } + result.push(value_lines.join("\n")); + } + + Ok(result) +} + +fn parse_screen_block( + start: &str, + lines: &[&str], + block_line: usize, +) -> Result { + match start { + "CONFIRM SCREEN START" => { + let fields = parse_screen_fields(lines, block_line, &["TITLE", "BODY"])?; + Ok(SimulatorScreen::Confirm { + title: fields[0].clone(), + body: fields[1].clone(), + }) + } + "CONFIRM TRANSACTION ADDRESS SCREEN START" => { + let fields = parse_screen_fields(lines, block_line, &["AMOUNT", "ADDRESS"])?; + Ok(SimulatorScreen::TransactionAddress { + amount: fields[0].clone(), + address: fields[1].clone(), + }) + } + "CONFIRM TRANSACTION FEE SCREEN START" => { + let fields = parse_screen_fields(lines, block_line, &["AMOUNT", "FEE"])?; + Ok(SimulatorScreen::TransactionFee { + amount: fields[0].clone(), + fee: fields[1].clone(), + }) + } + "STATUS SCREEN START" => { + let Some(title) = lines.first().and_then(|line| line.strip_prefix("TITLE: ")) else { + return Err(parse_error(block_line, "missing TITLE field")); + }; + let title = title.to_owned(); + let body = lines[1..].join("\n"); + Ok(SimulatorScreen::Status { title, body }) + } + "CONFIRM SWAP SCREEN START" => { + let fields = parse_screen_fields(lines, block_line, &["TITLE", "FROM", "TO"])?; + Ok(SimulatorScreen::Swap { + title: fields[0].clone(), + from: fields[1].clone(), + to: fields[2].clone(), + }) + } + _ => unreachable!(), + } +} + +fn screen_end_marker(start: &str) -> Option<&'static str> { + match start { + "CONFIRM SCREEN START" => Some("CONFIRM SCREEN END"), + "CONFIRM TRANSACTION ADDRESS SCREEN START" => { + Some("CONFIRM TRANSACTION ADDRESS SCREEN END") + } + "CONFIRM TRANSACTION FEE SCREEN START" => Some("CONFIRM TRANSACTION FEE SCREEN END"), + "STATUS SCREEN START" => Some("STATUS SCREEN END"), + "CONFIRM SWAP SCREEN START" => Some("CONFIRM SWAP SCREEN END"), + _ => None, + } +} + +fn is_screen_marker(line: &str) -> bool { + line.ends_with(" SCREEN START") || line.ends_with(" SCREEN END") +} + +pub fn parse_simulator_screens( + output: &str, +) -> Result, SimulatorScreenParseError> { + let lines: Vec<&str> = output.lines().collect(); + let mut result = Vec::new(); + let mut index = 0; + + while index < lines.len() { + let start = lines[index]; + let Some(end_marker) = screen_end_marker(start) else { + if is_screen_marker(start) { + return Err(parse_error( + index + 1, + format!("unknown screen marker {start:?}"), + )); + } + index += 1; + continue; + }; + let mut end = index + 1; + while end < lines.len() && lines[end] != end_marker { + if screen_end_marker(lines[end]).is_some() { + return Err(parse_error( + end + 1, + format!( + "screen starting on line {} contains a nested screen", + index + 1 + ), + )); + } + if is_screen_marker(lines[end]) { + return Err(parse_error( + end + 1, + format!( + "screen starting on line {} has unexpected marker {:?}", + index + 1, + lines[end] + ), + )); + } + end += 1; + } + if end == lines.len() { + return Err(parse_error(index + 1, format!("missing {end_marker}"))); + } + result.push(parse_screen_block( + start, + &lines[index + 1..end], + index + 1, + )?); + index = end + 1; + } + + Ok(result) +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SimulatorStdoutCheckpoint(usize); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SimulatorStdoutSnapshot { + lines: Vec, +} + +impl SimulatorStdoutSnapshot { + pub fn lines(&self) -> &[String] { + &self.lines + } + + pub fn raw(&self) -> String { + if self.lines.is_empty() { + String::new() + } else { + format!("{}\n", self.lines.join("\n")) + } + } + + pub fn screens(&self) -> Result, SimulatorScreenParseError> { + parse_simulator_screens(&self.raw()) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SimulatorStdoutWaitError { + Timeout(SimulatorStdoutSnapshot), + Closed(SimulatorStdoutSnapshot), +} + +struct SimulatorStdoutState { + lines: Vec, + last_update: Instant, + closed: bool, +} + +struct SimulatorStdoutShared { + state: Mutex, + changed: Condvar, +} + +#[derive(Clone)] +pub struct SimulatorStdout { + shared: Arc, +} + +impl SimulatorStdout { + pub(crate) fn new() -> Self { + Self { + shared: Arc::new(SimulatorStdoutShared { + state: Mutex::new(SimulatorStdoutState { + lines: Vec::new(), + last_update: Instant::now(), + closed: false, + }), + changed: Condvar::new(), + }), + } + } + + pub(crate) fn record_line(&self, line: String) { + let mut state = self.shared.state.lock().unwrap(); + state.lines.push(line); + state.last_update = Instant::now(); + self.shared.changed.notify_all(); + } + + fn mark_closed(&self) { + let mut state = self.shared.state.lock().unwrap(); + state.closed = true; + self.shared.changed.notify_all(); + } + + pub fn checkpoint(&self) -> SimulatorStdoutCheckpoint { + SimulatorStdoutCheckpoint(self.shared.state.lock().unwrap().lines.len()) + } + + fn snapshot_locked( + &self, + state: &SimulatorStdoutState, + checkpoint: SimulatorStdoutCheckpoint, + ) -> SimulatorStdoutSnapshot { + assert!(checkpoint.0 <= state.lines.len()); + SimulatorStdoutSnapshot { + lines: state.lines[checkpoint.0..].to_vec(), + } + } + + pub fn snapshot(&self, checkpoint: SimulatorStdoutCheckpoint) -> SimulatorStdoutSnapshot { + let state = self.shared.state.lock().unwrap(); + self.snapshot_locked(&state, checkpoint) + } + + pub fn wait_until_stable( + &self, + checkpoint: SimulatorStdoutCheckpoint, + stable_for: Duration, + timeout: Duration, + ) -> Result { + let started = Instant::now(); + let deadline = started + timeout; + let mut state = self.shared.state.lock().unwrap(); + + loop { + let now = Instant::now(); + let last_update = if state.lines.len() > checkpoint.0 { + state.last_update + } else { + started + }; + let stable_elapsed = now.saturating_duration_since(last_update); + if stable_elapsed >= stable_for || state.closed { + return Ok(self.snapshot_locked(&state, checkpoint)); + } + if now >= deadline { + return Err(SimulatorStdoutWaitError::Timeout( + self.snapshot_locked(&state, checkpoint), + )); + } + + let wait_for = std::cmp::min(stable_for - stable_elapsed, deadline - now); + (state, _) = self.shared.changed.wait_timeout(state, wait_for).unwrap(); + } + } + + pub fn wait_for_terminal_screen( + &self, + checkpoint: SimulatorStdoutCheckpoint, + timeout: Duration, + ) -> Result { + let deadline = Instant::now() + timeout; + let mut state = self.shared.state.lock().unwrap(); + + loop { + let snapshot = self.snapshot_locked(&state, checkpoint); + if snapshot + .screens() + .is_ok_and(|screens| screens.last().is_some_and(SimulatorScreen::is_terminal)) + { + return Ok(snapshot); + } + if state.closed { + return Err(SimulatorStdoutWaitError::Closed(snapshot)); + } + + let now = Instant::now(); + if now >= deadline { + return Err(SimulatorStdoutWaitError::Timeout(snapshot)); + } + (state, _) = self + .shared + .changed + .wait_timeout(state, deadline - now) + .unwrap(); + } + } +} + +struct Server { + child: Child, + stdout: SimulatorStdout, + stdout_thread: Option>, +} impl Server { fn launch(filename: &str) -> Self { - //let mut command = Command::new(filename); - let mut command = Command::new("stdbuf"); command .arg("-oL") // Line buffering for stdout .arg(filename) .stdout(std::process::Stdio::piped()); - command.stdout(std::process::Stdio::piped()); // Capture stdout - let mut child = command.spawn().expect("failed to start server"); - - // Take stdout handle from child let stdout = child.stdout.take().unwrap(); + let captured_stdout = SimulatorStdout::new(); + let thread_stdout = captured_stdout.clone(); // Spawn a thread to process the output, so we can print it indented for clarity. - std::thread::spawn(move || { + let stdout_thread = std::thread::spawn(move || { let reader = std::io::BufReader::new(stdout); for line in reader.lines() { match line { - Ok(line) => println!("\t\t{line}"), + Ok(line) => { + thread_stdout.record_line(line.clone()); + if std::env::var("UPDATE_BTC_VECTOR_SCREENS").as_deref() != Ok("1") { + println!("\t\t{line}"); + } + } Err(e) => eprintln!("Error reading line: {e}"), } } + thread_stdout.mark_closed(); }); - Self(child) + Self { + child, + stdout: captured_stdout, + stdout_thread: Some(stdout_thread), + } + } + + fn stdout(&self) -> SimulatorStdout { + self.stdout.clone() } } // Kill server on drop. impl Drop for Server { fn drop(&mut self) { - self.0.kill().unwrap(); - self.0.wait().unwrap(); + let _ = self.child.kill(); + let _ = self.child.wait(); + if let Some(stdout_thread) = self.stdout_thread.take() { + let _ = stdout_thread.join(); + } } } @@ -160,8 +551,8 @@ async fn download_simulators() -> Result, ()> { } /// Tests on an initialized device, which is not yet seeded. -pub async fn test_simulators_after_pairing( - run: impl AsyncFn(&bitbox_api::PairedBitBox), +pub async fn test_simulators_after_pairing_with_stdout( + run: impl AsyncFn(&bitbox_api::PairedBitBox, &SimulatorStdout), ) { let simulator_filenames = if let Some(simulator_filename) = option_env!("SIMULATOR") { vec![simulator_filename.into()] @@ -171,7 +562,8 @@ pub async fn test_simulators_after_pairing( for simulator_filename in simulator_filenames { println!(); println!("\tSimulator tests using {simulator_filename}"); - let _server = Server::launch(&simulator_filename); + let server = Server::launch(&simulator_filename); + let stdout = server.stdout(); let noise_config = Box::new(bitbox_api::NoiseConfigNoCache {}); let bitbox = bitbox_api::BitBox::::from_simulator( None, @@ -181,18 +573,38 @@ pub async fn test_simulators_after_pairing( .unwrap(); let pairing_bitbox = bitbox.unlock_and_pair().await.unwrap(); let paired_bitbox = pairing_bitbox.wait_confirm().await.unwrap(); - run(&paired_bitbox).await; + run(&paired_bitbox, &stdout).await; } } +/// Tests on an initialized device, which is not yet seeded. +pub async fn test_simulators_after_pairing( + run: impl AsyncFn(&bitbox_api::PairedBitBox), +) { + test_simulators_after_pairing_with_stdout(async |paired_bitbox, _stdout| { + run(paired_bitbox).await; + }) + .await +} + /// Tests on an initialized/seeded device. /// The simulator is initialized with the following mnemonic: /// boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide +pub async fn test_initialized_simulators_with_stdout( + run: impl AsyncFn(&bitbox_api::PairedBitBox, &SimulatorStdout), +) { + test_simulators_after_pairing_with_stdout(async |paired_bitbox, stdout| { + assert!(paired_bitbox.restore_from_mnemonic().await.is_ok()); + run(paired_bitbox, stdout).await; + }) + .await +} + +/// Tests on an initialized/seeded device. pub async fn test_initialized_simulators( run: impl AsyncFn(&bitbox_api::PairedBitBox), ) { - test_simulators_after_pairing(async |paired_bitbox| { - assert!(paired_bitbox.restore_from_mnemonic().await.is_ok()); + test_initialized_simulators_with_stdout(async |paired_bitbox, _stdout| { run(paired_bitbox).await; }) .await