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.
@@ -25,10 +25,11 @@ pub use pb::btc_sign_init_request::FormatUnit;
25
25
pub use pb:: { BtcCoin , BtcOutputType } ;
26
26
27
27
use super :: script_configs:: { ValidatedScriptConfig , ValidatedScriptConfigWithKeypath } ;
28
- use super :: { multisig, params:: Params , script } ;
28
+ use super :: { multisig, params:: Params } ;
29
29
30
30
use sha2:: { Digest , Sha256 } ;
31
31
32
+ use bitcoin:: ScriptBuf ;
32
33
use bitcoin:: bech32;
33
34
use bitcoin:: hashes:: Hash ;
34
35
@@ -210,7 +211,8 @@ impl Payload {
210
211
}
211
212
}
212
213
213
- /// Converts a payload to an address.
214
+ /// Converts a payload to an address. Returns an error for invalid input or if an address does
215
+ /// not exist for the output type (e.g. OP_RETURN).
214
216
pub fn address ( & self , params : & Params ) -> Result < String , ( ) > {
215
217
let payload = self . data . as_slice ( ) ;
216
218
match self . output_type {
@@ -251,54 +253,51 @@ impl Payload {
251
253
}
252
254
encode_segwit_addr ( params. bech32_hrp , 1 , payload)
253
255
}
256
+ BtcOutputType :: OpReturn => Err ( ( ) ) ,
254
257
}
255
258
}
256
259
257
- /// Computes the pkScript from a pubkey hash or script hash or pubkey, depending on the output type.
260
+ /// Computes the pkScript from a pubkey hash or script hash or pubkey, depending on the output
261
+ /// type.
258
262
pub fn pk_script ( & self , params : & Params ) -> Result < Vec < u8 > , Error > {
259
263
let payload = self . data . as_slice ( ) ;
260
- match self . output_type {
261
- BtcOutputType :: Unknown => Err ( Error :: InvalidInput ) ,
264
+ let script = match self . output_type {
265
+ BtcOutputType :: Unknown => return Err ( Error :: InvalidInput ) ,
262
266
BtcOutputType :: P2pkh => {
263
- if payload. len ( ) != HASH160_LEN {
264
- return Err ( Error :: Generic ) ;
265
- }
266
- let mut result = vec ! [ script:: OP_DUP , script:: OP_HASH160 ] ;
267
- script:: push_data ( & mut result, payload) ;
268
- result. extend_from_slice ( & [ script:: OP_EQUALVERIFY , script:: OP_CHECKSIG ] ) ;
269
- Ok ( result)
267
+ let pk_hash =
268
+ bitcoin:: PubkeyHash :: from_slice ( payload) . map_err ( |_| Error :: Generic ) ?;
269
+
270
+ ScriptBuf :: new_p2pkh ( & pk_hash)
270
271
}
271
272
BtcOutputType :: P2sh => {
272
- if payload. len ( ) != HASH160_LEN {
273
- return Err ( Error :: Generic ) ;
274
- }
275
- let mut result = vec ! [ script:: OP_HASH160 ] ;
276
- script:: push_data ( & mut result, payload) ;
277
- result. push ( script:: OP_EQUAL ) ;
278
- Ok ( result)
273
+ let script_hash =
274
+ bitcoin:: ScriptHash :: from_slice ( payload) . map_err ( |_| Error :: Generic ) ?;
275
+ ScriptBuf :: new_p2sh ( & script_hash)
279
276
}
280
- BtcOutputType :: P2wpkh | BtcOutputType :: P2wsh => {
281
- if ( self . output_type == BtcOutputType :: P2wpkh && payload. len ( ) != HASH160_LEN )
282
- || ( self . output_type == BtcOutputType :: P2wsh && payload. len ( ) != SHA256_LEN )
283
- {
284
- return Err ( Error :: Generic ) ;
285
- }
286
- let mut result = vec ! [ script:: OP_0 ] ;
287
- script:: push_data ( & mut result, payload) ;
288
- Ok ( result)
277
+ BtcOutputType :: P2wpkh => {
278
+ let wpkh = bitcoin:: WPubkeyHash :: from_slice ( payload) . map_err ( |_| Error :: Generic ) ?;
279
+ ScriptBuf :: new_p2wpkh ( & wpkh)
280
+ }
281
+ BtcOutputType :: P2wsh => {
282
+ let wsh = bitcoin:: WScriptHash :: from_slice ( payload) . map_err ( |_| Error :: Generic ) ?;
283
+ ScriptBuf :: new_p2wsh ( & wsh)
289
284
}
290
285
BtcOutputType :: P2tr => {
291
286
if !params. taproot_support {
292
287
return Err ( Error :: InvalidInput ) ;
293
288
}
294
- if payload. len ( ) != 32 {
295
- return Err ( Error :: Generic ) ;
296
- }
297
- let mut result = vec ! [ script:: OP_1 ] ;
298
- script:: push_data ( & mut result, payload) ;
299
- Ok ( result)
289
+ let tweaked = bitcoin:: key:: TweakedPublicKey :: dangerous_assume_tweaked (
290
+ bitcoin:: XOnlyPublicKey :: from_slice ( payload) . map_err ( |_| Error :: Generic ) ?,
291
+ ) ;
292
+ ScriptBuf :: new_p2tr_tweaked ( tweaked)
300
293
}
301
- }
294
+ BtcOutputType :: OpReturn => {
295
+ let pushbytes: & bitcoin:: script:: PushBytes =
296
+ payload. try_into ( ) . map_err ( |_| Error :: InvalidInput ) ?;
297
+ ScriptBuf :: new_op_return ( pushbytes)
298
+ }
299
+ } ;
300
+ Ok ( script. into_bytes ( ) )
302
301
}
303
302
}
304
303
@@ -622,4 +621,86 @@ mod tests {
622
621
b"\x25 \x0e \xc8 \x02 \xb6 \xd3 \xdb \x98 \x42 \xd1 \xbd \xbe \x0e \xe4 \x8d \x52 \xf9 \xa4 \xb4 \x6e \x60 \xcb \xbb \xab \x3b \xcc \x4e \xe9 \x15 \x73 \xfc \xe8 "
623
622
) ;
624
623
}
624
+
625
+ #[ test]
626
+ fn test_pkscript ( ) {
627
+ let params = super :: super :: params:: get ( pb:: BtcCoin :: Btc ) ;
628
+
629
+ let payload = Payload {
630
+ data : vec ! [ ] ,
631
+ output_type : BtcOutputType :: Unknown ,
632
+ } ;
633
+ assert_eq ! ( payload. pk_script( params) , Err ( Error :: InvalidInput ) ) ;
634
+
635
+ struct Test {
636
+ payload : & ' static str ,
637
+ output_type : BtcOutputType ,
638
+ expected_pkscript : & ' static str ,
639
+ }
640
+
641
+ let tests = [
642
+ Test {
643
+ payload : "669c6cb1883c50a1b10c34bd1693c1f34fe3d798" ,
644
+ output_type : BtcOutputType :: P2pkh ,
645
+ expected_pkscript : "76a914669c6cb1883c50a1b10c34bd1693c1f34fe3d79888ac" ,
646
+ } ,
647
+ Test {
648
+ payload : "b59e844a19063a882b3c34b64b941a8acdad74ee" ,
649
+ output_type : BtcOutputType :: P2sh ,
650
+ expected_pkscript : "a914b59e844a19063a882b3c34b64b941a8acdad74ee87" ,
651
+ } ,
652
+ Test {
653
+ payload : "b7cfb87a9806bb232e64f64e714785bd8366596b" ,
654
+ output_type : BtcOutputType :: P2wpkh ,
655
+ expected_pkscript : "0014b7cfb87a9806bb232e64f64e714785bd8366596b" ,
656
+ } ,
657
+ Test {
658
+ payload : "526e8e589b4bf1de80774986d972aed96ae70f17572d35fe89e61e9e88e2dd4a" ,
659
+ output_type : BtcOutputType :: P2wsh ,
660
+ expected_pkscript : "0020526e8e589b4bf1de80774986d972aed96ae70f17572d35fe89e61e9e88e2dd4a" ,
661
+ } ,
662
+ Test {
663
+ payload : "a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c" ,
664
+ output_type : BtcOutputType :: P2tr ,
665
+ expected_pkscript : "5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c" ,
666
+ } ,
667
+ Test {
668
+ payload : "aabbcc" ,
669
+ output_type : BtcOutputType :: OpReturn ,
670
+ expected_pkscript : "6a03aabbcc" ,
671
+ } ,
672
+ Test {
673
+ payload : "" ,
674
+ output_type : BtcOutputType :: OpReturn ,
675
+ expected_pkscript : "6a00" ,
676
+ } ,
677
+ Test {
678
+ // 80 byte payload
679
+ payload : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
680
+ output_type : BtcOutputType :: OpReturn ,
681
+ expected_pkscript : "6a4c50aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
682
+ } ,
683
+ ] ;
684
+
685
+ for test in tests {
686
+ // OK
687
+ let payload = Payload {
688
+ data : hex:: decode ( test. payload ) . unwrap ( ) ,
689
+ output_type : test. output_type ,
690
+ } ;
691
+ assert_eq ! (
692
+ hex:: encode( payload. pk_script( params) . unwrap( ) ) ,
693
+ test. expected_pkscript,
694
+ ) ;
695
+
696
+ // Payload of wrong size. Does not apply to OpReturn, almost any size is accepted.
697
+ if test. output_type != BtcOutputType :: OpReturn {
698
+ let payload = Payload {
699
+ data : hex:: decode ( & test. payload [ 2 ..] ) . unwrap ( ) ,
700
+ output_type : test. output_type ,
701
+ } ;
702
+ assert_eq ! ( payload. pk_script( params) , Err ( Error :: Generic ) ) ;
703
+ }
704
+ }
705
+ }
625
706
}
0 commit comments