1
- // Copyright 2022-2024 Shift Crypto AG
1
+ // Copyright 2022-2025 Shift Crypto AG
2
2
//
3
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
4
// you may not use this file except in compliance with the License.
@@ -853,12 +853,18 @@ async fn _process(
853
853
} ;
854
854
}
855
855
856
- if tx_output. value == 0 {
856
+ let output_type = pb:: BtcOutputType :: try_from ( tx_output. r#type ) ?;
857
+
858
+ // We don't allow regular outputs to have 0 value.
859
+ // OP_RETURN outputs however we require to have 0 value.
860
+ if output_type == pb:: BtcOutputType :: OpReturn {
861
+ if tx_output. value != 0 {
862
+ return Err ( Error :: InvalidInput ) ;
863
+ }
864
+ } else if tx_output. value == 0 {
857
865
return Err ( Error :: InvalidInput ) ;
858
866
}
859
867
860
- let output_type = pb:: BtcOutputType :: try_from ( tx_output. r#type ) ?;
861
-
862
868
// Get payload. If the output is marked ours, we compute the payload from the keystore,
863
869
// otherwise it is provided in tx_output.payload.
864
870
let payload: common:: Payload = if tx_output. ours {
@@ -949,16 +955,21 @@ async fn _process(
949
955
if !is_change {
950
956
// Verify output if it is not a change output.
951
957
// Assemble address to display, get user confirmation.
952
- let address = if let Some ( sp) = tx_output. silent_payment . as_ref ( ) {
953
- sp. address . clone ( )
954
- } else {
955
- payload. address ( coin_params) ?
958
+ let address = || -> Result < String , Error > {
959
+ if let Some ( sp) = tx_output. silent_payment . as_ref ( ) {
960
+ Ok ( sp. address . clone ( ) )
961
+ } else {
962
+ Ok ( payload. address ( coin_params) ?)
963
+ }
956
964
} ;
957
965
958
966
if let Some ( output_payment_request_index) = tx_output. payment_request_index {
959
967
if output_payment_request_index != 0 {
960
968
return Err ( Error :: InvalidInput ) ;
961
969
}
970
+ if output_type == pb:: BtcOutputType :: OpReturn {
971
+ return Err ( Error :: InvalidInput ) ;
972
+ }
962
973
if payment_request_seen {
963
974
return Err ( Error :: InvalidInput ) ;
964
975
}
@@ -970,7 +981,7 @@ async fn _process(
970
981
coin_params,
971
982
& payment_request,
972
983
tx_output. value ,
973
- & address,
984
+ & address ( ) ? ,
974
985
)
975
986
. is_err ( )
976
987
{
@@ -979,6 +990,16 @@ async fn _process(
979
990
}
980
991
981
992
payment_request_seen = true ;
993
+ } else if output_type == pb:: BtcOutputType :: OpReturn {
994
+ // OP_RETURN value was validated to be 0 above, so we don't need to show the amount.
995
+ crate :: workflow:: verify_message:: verify (
996
+ hal,
997
+ "OP_RETURN" ,
998
+ "OP_RETURN" ,
999
+ & tx_output. payload ,
1000
+ false ,
1001
+ )
1002
+ . await ?;
982
1003
} else {
983
1004
// When sending coins back to the same account (non-change), or another account of
984
1005
// the same keystore (change or non-change), we show a prefix to let the user know.
@@ -1001,9 +1022,9 @@ async fn _process(
1001
1022
hal. ui ( )
1002
1023
. verify_recipient (
1003
1024
& ( if let Some ( prefix) = prefix {
1004
- format ! ( "{}: {}" , prefix, address)
1025
+ format ! ( "{}: {}" , prefix, address( ) ? )
1005
1026
} else {
1006
- address
1027
+ address ( ) ?
1007
1028
} ) ,
1008
1029
& format_amount ( coin_params, format_unit, tx_output. value ) ?,
1009
1030
)
@@ -3683,4 +3704,102 @@ mod tests {
3683
3704
]
3684
3705
) ;
3685
3706
}
3707
+
3708
+ #[ test]
3709
+ fn test_op_return ( ) {
3710
+ let transaction =
3711
+ alloc:: rc:: Rc :: new ( core:: cell:: RefCell :: new ( Transaction :: new ( pb:: BtcCoin :: Btc ) ) ) ;
3712
+
3713
+ // Attach OP_RETURN output
3714
+ {
3715
+ let mut tx = transaction. borrow_mut ( ) ;
3716
+ tx. outputs . push ( pb:: BtcSignOutputRequest {
3717
+ r#type : pb:: BtcOutputType :: OpReturn as _ ,
3718
+ value : 0 ,
3719
+ payload : b"hello world" . to_vec ( ) ,
3720
+ ..Default :: default ( )
3721
+ } ) ;
3722
+ }
3723
+
3724
+ mock_host_responder ( transaction. clone ( ) ) ;
3725
+ mock_unlocked ( ) ;
3726
+ bitbox02:: random:: fake_reset ( ) ;
3727
+ let init_request = transaction. borrow ( ) . init_request ( ) ;
3728
+
3729
+ let mut mock_hal = TestingHal :: new ( ) ;
3730
+ let result = block_on ( process ( & mut mock_hal, & init_request) ) ;
3731
+
3732
+ match result {
3733
+ Ok ( Response :: BtcSignNext ( next) ) => {
3734
+ assert ! ( next. has_signature) ;
3735
+ assert_eq ! (
3736
+ hex:: encode( next. signature) ,
3737
+ "f49c71b89ec3510ebebae9aff9f967ad9bb6cc0c4cddbdf851f97e47e9922646622459e522b0751fa246e49a8e48417344a5384a9f68c1c85cd03804b35e1e1e" ,
3738
+ ) ;
3739
+ }
3740
+ _ => panic ! ( "wrong result" ) ,
3741
+ }
3742
+
3743
+ assert ! ( mock_hal. ui. contains_confirm( "OP_RETURN" , "hello world" ) ) ;
3744
+ }
3745
+
3746
+ #[ test]
3747
+ fn test_op_return_nonascii ( ) {
3748
+ let transaction =
3749
+ alloc:: rc:: Rc :: new ( core:: cell:: RefCell :: new ( Transaction :: new ( pb:: BtcCoin :: Btc ) ) ) ;
3750
+
3751
+ // Attach OP_RETURN output
3752
+ {
3753
+ let mut tx = transaction. borrow_mut ( ) ;
3754
+ tx. outputs . push ( pb:: BtcSignOutputRequest {
3755
+ r#type : pb:: BtcOutputType :: OpReturn as _ ,
3756
+ value : 0 ,
3757
+ payload : vec ! [ 1 , 2 , 3 , 4 , 5 ] ,
3758
+ ..Default :: default ( )
3759
+ } ) ;
3760
+ }
3761
+
3762
+ mock_host_responder ( transaction. clone ( ) ) ;
3763
+ mock_unlocked ( ) ;
3764
+ bitbox02:: random:: fake_reset ( ) ;
3765
+ let init_request = transaction. borrow ( ) . init_request ( ) ;
3766
+
3767
+ let mut mock_hal = TestingHal :: new ( ) ;
3768
+ let result = block_on ( process ( & mut mock_hal, & init_request) ) ;
3769
+ assert ! ( result. is_ok( ) ) ;
3770
+
3771
+ assert ! (
3772
+ mock_hal
3773
+ . ui
3774
+ . contains_confirm( "OP_RETURN\n data (hex)" , "0102030405" )
3775
+ ) ;
3776
+ }
3777
+
3778
+ #[ test]
3779
+ fn test_op_return_fail_nonzero_value ( ) {
3780
+ let transaction =
3781
+ alloc:: rc:: Rc :: new ( core:: cell:: RefCell :: new ( Transaction :: new ( pb:: BtcCoin :: Btc ) ) ) ;
3782
+
3783
+ // Attach OP_RETURN output
3784
+ {
3785
+ let mut tx = transaction. borrow_mut ( ) ;
3786
+ tx. outputs . push ( pb:: BtcSignOutputRequest {
3787
+ r#type : pb:: BtcOutputType :: OpReturn as _ ,
3788
+ value : 100 ,
3789
+ payload : b"hello world" . to_vec ( ) ,
3790
+ ..Default :: default ( )
3791
+ } ) ;
3792
+ }
3793
+
3794
+ mock_host_responder ( transaction. clone ( ) ) ;
3795
+ mock_unlocked ( ) ;
3796
+ bitbox02:: random:: fake_reset ( ) ;
3797
+ let init_request = transaction. borrow ( ) . init_request ( ) ;
3798
+
3799
+ let mut mock_hal = TestingHal :: new ( ) ;
3800
+ assert_eq ! (
3801
+ block_on( process( & mut mock_hal, & init_request) ) ,
3802
+ Err ( Error :: InvalidInput )
3803
+ ) ;
3804
+ }
3686
3805
}
0 commit comments