@@ -717,8 +717,20 @@ async fn _process(
717717 let mut hasher_amounts = Sha256 :: new ( ) ;
718718 let mut hasher_scriptpubkeys = Sha256 :: new ( ) ;
719719
720- // Are all inputs taproot?
721- let taproot_only = validated_script_configs. iter ( ) . all ( is_taproot) ;
720+ let prev_txs = pb:: btc_sign_init_request:: PrevTxs :: try_from ( request. prev_txs ) ?;
721+
722+ // We will request to stream previous transactions if not all inputs are Taproot.
723+ let prevtxs_required: bool = match prev_txs {
724+ // For backwards compatibility for client's that don't provide the `prev_txs` field: handle
725+ // it like before `prev_txs` was introduced by inspecting the script configs and seeing if
726+ // all of them are taproot. This is not robust, as non-Taproot change outputs are included
727+ // there and falsely leads to previous transactions being required.
728+ pb:: btc_sign_init_request:: PrevTxs :: Default => {
729+ !validated_script_configs. iter ( ) . all ( is_taproot)
730+ }
731+ pb:: btc_sign_init_request:: PrevTxs :: Required => true ,
732+ pb:: btc_sign_init_request:: PrevTxs :: NotRequired => false ,
733+ } ;
722734
723735 let mut silent_payment = if request. contains_silent_payment_outputs {
724736 Some ( SilentPayment :: new ( SECP256K1 , coin. try_into ( ) ?) )
@@ -775,7 +787,7 @@ async fn _process(
775787 hasher_scriptpubkeys. update ( serialize_varint ( pk_script. len ( ) as u64 ) . as_slice ( ) ) ;
776788 hasher_scriptpubkeys. update ( pk_script. as_slice ( ) ) ;
777789
778- if !taproot_only {
790+ if prevtxs_required {
779791 handle_prevtx (
780792 input_index,
781793 & tx_input,
@@ -784,6 +796,8 @@ async fn _process(
784796 & mut next_response,
785797 )
786798 . await ?;
799+ } else if !is_taproot ( script_config_account) {
800+ return Err ( Error :: InvalidInput ) ;
787801 }
788802
789803 if let Some ( ref mut silent_payment) = silent_payment {
@@ -1572,6 +1586,7 @@ mod tests {
15721586 . outputs
15731587 . iter ( )
15741588 . any ( |output| output. silent_payment . is_some ( ) ) ,
1589+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
15751590 }
15761591 }
15771592
@@ -1595,6 +1610,7 @@ mod tests {
15951610 locktime : self . locktime ,
15961611 format_unit : FormatUnit :: Default as _ ,
15971612 contains_silent_payment_outputs : false ,
1613+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
15981614 }
15991615 }
16001616
@@ -1679,6 +1695,7 @@ mod tests {
16791695 locktime : 0 ,
16801696 format_unit : FormatUnit :: Default as _ ,
16811697 contains_silent_payment_outputs : false ,
1698+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
16821699 } ;
16831700
16841701 {
@@ -1871,6 +1888,7 @@ mod tests {
18711888 locktime: 0 ,
18721889 format_unit: FormatUnit :: Default as _,
18731890 contains_silent_payment_outputs: false ,
1891+ prev_txs: pb:: btc_sign_init_request:: PrevTxs :: Default as _,
18741892 }
18751893 ) ) ,
18761894 Err ( Error :: InvalidInput )
@@ -2147,6 +2165,57 @@ mod tests {
21472165 assert ! ( unsafe { !PREVTX_REQUESTED } ) ;
21482166 }
21492167
2168+ /// Test signing if all inputs are of type P2TR, but change is not of type P2TR.
2169+ /// Previous transactions are requested for backwards compatibility when
2170+ #[ test]
2171+ fn test_script_type_p2tr_different_change ( ) {
2172+ let transaction =
2173+ alloc:: rc:: Rc :: new ( core:: cell:: RefCell :: new ( Transaction :: new ( pb:: BtcCoin :: Btc ) ) ) ;
2174+ for input in transaction. borrow_mut ( ) . inputs . iter_mut ( ) {
2175+ input. input . keypath [ 0 ] = 86 + HARDENED ;
2176+ input. input . script_config_index = 1 ;
2177+ }
2178+
2179+ let tx = transaction. clone ( ) ;
2180+ // Check that previous transactions are not streamed, as all inputs are taproot.
2181+ static mut PREVTX_REQUESTED : bool = false ;
2182+ * crate :: hww:: MOCK_NEXT_REQUEST . 0 . borrow_mut ( ) =
2183+ Some ( Box :: new ( move |response : Response | {
2184+ let next = extract_next ( & response) ;
2185+ if NextType :: try_from ( next. r#type ) . unwrap ( ) == NextType :: PrevtxInit {
2186+ unsafe { PREVTX_REQUESTED = true }
2187+ }
2188+ Ok ( tx. borrow ( ) . make_host_request ( response) )
2189+ } ) ) ;
2190+
2191+ mock_unlocked ( ) ;
2192+ bitbox02:: random:: fake_reset ( ) ;
2193+ let mut init_request = transaction. borrow ( ) . init_request ( ) ;
2194+ init_request
2195+ . script_configs
2196+ . push ( pb:: BtcScriptConfigWithKeypath {
2197+ script_config : Some ( pb:: BtcScriptConfig {
2198+ config : Some ( pb:: btc_script_config:: Config :: SimpleType (
2199+ SimpleType :: P2tr as _ ,
2200+ ) ) ,
2201+ } ) ,
2202+ keypath : vec ! [ 86 + HARDENED , 0 + HARDENED , 10 + HARDENED ] ,
2203+ } ) ;
2204+
2205+ init_request. prev_txs = pb:: btc_sign_init_request:: PrevTxs :: NotRequired as _ ;
2206+ assert ! ( block_on( process( & mut TestingHal :: new( ) , & init_request) ) . is_ok( ) ) ;
2207+ assert ! ( unsafe { !PREVTX_REQUESTED } ) ;
2208+
2209+ // Also test compatibility mode - before the introduction of `prev_txs`, the previous
2210+ // transactions were wrongly requested in this case.
2211+ unsafe {
2212+ PREVTX_REQUESTED = false ;
2213+ }
2214+ init_request. prev_txs = pb:: btc_sign_init_request:: PrevTxs :: Default as _ ;
2215+ assert ! ( block_on( process( & mut TestingHal :: new( ) , & init_request) ) . is_ok( ) ) ;
2216+ assert ! ( unsafe { PREVTX_REQUESTED } ) ;
2217+ }
2218+
21502219 /// Test signing if with mixed inputs, one of them being taproot. Previous transactions of all
21512220 /// inputs should be streamed in this case.
21522221 #[ test]
@@ -2180,11 +2249,31 @@ mod tests {
21802249 } ) ,
21812250 keypath : vec ! [ 86 + HARDENED , 0 + HARDENED , 10 + HARDENED ] ,
21822251 } ) ;
2252+
2253+ // In compatibility mode, prevtxs are correctly requested.
2254+ init_request. prev_txs = pb:: btc_sign_init_request:: PrevTxs :: Default as _ ;
2255+ unsafe { PREVTX_REQUESTED = 0 } ;
21832256 assert ! ( block_on( process( & mut TestingHal :: new( ) , & init_request) ) . is_ok( ) ) ;
21842257 assert_eq ! (
21852258 unsafe { PREVTX_REQUESTED } ,
21862259 transaction. borrow( ) . inputs. len( ) as _
21872260 ) ;
2261+
2262+ // Same as when the client explicitly states it.
2263+ init_request. prev_txs = pb:: btc_sign_init_request:: PrevTxs :: Required as _ ;
2264+ unsafe { PREVTX_REQUESTED = 0 } ;
2265+ assert ! ( block_on( process( & mut TestingHal :: new( ) , & init_request) ) . is_ok( ) ) ;
2266+ assert_eq ! (
2267+ unsafe { PREVTX_REQUESTED } ,
2268+ transaction. borrow( ) . inputs. len( ) as _
2269+ ) ;
2270+
2271+ // Client can't lie about it.
2272+ init_request. prev_txs = pb:: btc_sign_init_request:: PrevTxs :: NotRequired as _ ;
2273+ assert_eq ! (
2274+ block_on( process( & mut TestingHal :: new( ) , & init_request) ) ,
2275+ Err ( Error :: InvalidInput )
2276+ ) ;
21882277 }
21892278
21902279 /// Test signing UTXOs with high keypath address indices. Even though we don't support verifying
@@ -2881,6 +2970,7 @@ mod tests {
28812970 locktime : tx. locktime ,
28822971 format_unit : FormatUnit :: Default as _ ,
28832972 contains_silent_payment_outputs : false ,
2973+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
28842974 }
28852975 } ;
28862976
@@ -2976,6 +3066,7 @@ mod tests {
29763066 locktime : tx. locktime ,
29773067 format_unit : FormatUnit :: Default as _ ,
29783068 contains_silent_payment_outputs : false ,
3069+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
29793070 }
29803071 } ;
29813072 assert_eq ! (
@@ -3047,6 +3138,7 @@ mod tests {
30473138 locktime : tx. locktime ,
30483139 format_unit : FormatUnit :: Default as _ ,
30493140 contains_silent_payment_outputs : false ,
3141+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
30503142 }
30513143 } ;
30523144 let result = block_on ( process ( & mut TestingHal :: new ( ) , & init_request) ) ;
@@ -3124,6 +3216,7 @@ mod tests {
31243216 locktime : tx. locktime ,
31253217 format_unit : FormatUnit :: Default as _ ,
31263218 contains_silent_payment_outputs : false ,
3219+ prev_txs : pb:: btc_sign_init_request:: PrevTxs :: Default as _ ,
31273220 }
31283221 } ;
31293222 let result = block_on ( process ( & mut TestingHal :: new ( ) , & init_request) ) ;
0 commit comments