@@ -71,12 +71,106 @@ impl AddressFormat {
7171 }
7272}
7373
74+ pub struct OutputScriptSupport {
75+ pub segwit : bool ,
76+ pub taproot : bool ,
77+ }
78+
79+ impl OutputScriptSupport {
80+ pub ( crate ) fn assert_legacy ( & self ) -> Result < ( ) > {
81+ // all coins support legacy scripts
82+ Ok ( ( ) )
83+ }
84+
85+ pub ( crate ) fn assert_segwit ( & self ) -> Result < ( ) > {
86+ if !self . segwit {
87+ return Err ( AddressError :: UnsupportedScriptType (
88+ "Network does not support segwit" . to_string ( ) ,
89+ ) ) ;
90+ }
91+ Ok ( ( ) )
92+ }
93+
94+ pub ( crate ) fn assert_taproot ( & self ) -> Result < ( ) > {
95+ if !self . taproot {
96+ return Err ( AddressError :: UnsupportedScriptType (
97+ "Network does not support taproot" . to_string ( ) ,
98+ ) ) ;
99+ }
100+ Ok ( ( ) )
101+ }
102+
103+ pub fn assert_support ( & self , script : & Script ) -> Result < ( ) > {
104+ match script. witness_version ( ) {
105+ None => {
106+ // all coins support legacy scripts
107+ }
108+ Some ( WitnessVersion :: V0 ) => {
109+ self . assert_segwit ( ) ?;
110+ }
111+ Some ( WitnessVersion :: V1 ) => {
112+ self . assert_taproot ( ) ?;
113+ }
114+ _ => {
115+ return Err ( AddressError :: UnsupportedScriptType (
116+ "Unsupported witness version" . to_string ( ) ,
117+ ) ) ;
118+ }
119+ }
120+ Ok ( ( ) )
121+ }
122+ }
123+
124+ impl Network {
125+ pub fn output_script_support ( & self ) -> OutputScriptSupport {
126+ // SegWit support:
127+ // Bitcoin: SegWit activated August 24, 2017 at block 481,824
128+ // - Consensus rules: https://github.com/bitcoin/bitcoin/blob/v28.0/src/consensus/tx_verify.cpp
129+ // - Witness validation: https://github.com/bitcoin/bitcoin/blob/v28.0/src/script/interpreter.cpp
130+ // - BIP141 (SegWit): https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
131+ // - BIP143 (Signature verification): https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
132+ // - BIP144 (P2P changes): https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
133+ //
134+ // Litecoin: SegWit activated May 10, 2017 at block 1,201,536
135+ // - Consensus implementation: https://github.com/litecoin-project/litecoin/blob/v0.21.4/src/consensus/tx_verify.cpp
136+ // - Script interpreter: https://github.com/litecoin-project/litecoin/blob/v0.21.4/src/script/interpreter.cpp
137+ //
138+ // Bitcoin Gold: Launched with SegWit support in October 2017
139+ // - Implementation: https://github.com/BTCGPU/BTCGPU/blob/v0.17.3/src/consensus/tx_verify.cpp
140+ let segwit = matches ! (
141+ self . mainnet( ) ,
142+ Network :: Bitcoin | Network :: Litecoin | Network :: BitcoinGold
143+ ) ;
144+
145+ // Taproot support:
146+ // Bitcoin: Taproot activated November 14, 2021 at block 709,632
147+ // - Taproot validation: https://github.com/bitcoin/bitcoin/blob/v28.0/src/script/interpreter.cpp
148+ // (see VerifyWitnessProgram, WITNESS_V1_TAPROOT)
149+ // - Schnorr signature verification: https://github.com/bitcoin/bitcoin/blob/v28.0/src/pubkey.cpp
150+ // (see XOnlyPubKey::VerifySchnorr)
151+ // - Deployment params: https://github.com/bitcoin/bitcoin/blob/v28.0/src/kernel/chainparams.cpp
152+ // (see Consensus::DeploymentPos::DEPLOYMENT_TAPROOT)
153+ // - BIP340 (Schnorr signatures): https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
154+ // - BIP341 (Taproot): https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
155+ // - BIP342 (Tapscript): https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki
156+ //
157+ // Litecoin: has apparent taproot support, but we have not enabled it in this library yet.
158+ // - https://github.com/litecoin-project/litecoin/blob/v0.21.4/src/chainparams.cpp#L89-L92
159+ // - https://github.com/litecoin-project/litecoin/blob/v0.21.4/src/script/interpreter.h#L129-L131
160+ let taproot = segwit && matches ! ( self . mainnet( ) , Network :: Bitcoin ) ;
161+
162+ OutputScriptSupport { segwit, taproot }
163+ }
164+ }
165+
74166/// Get codec for encoding an address for a given network and script type.
75167fn get_encode_codec (
76168 network : Network ,
77169 script : & Script ,
78170 format : AddressFormat ,
79171) -> Result < & ' static dyn AddressCodec > {
172+ network. output_script_support ( ) . assert_support ( script) ?;
173+
80174 let is_witness = script. is_p2wpkh ( ) || script. is_p2wsh ( ) || script. is_p2tr ( ) ;
81175 let is_legacy = script. is_p2pkh ( ) || script. is_p2sh ( ) ;
82176
@@ -208,6 +302,7 @@ pub fn from_output_script_with_coin_and_format(
208302 from_output_script_with_network_and_format ( script, network, format)
209303}
210304
305+ use miniscript:: bitcoin:: WitnessVersion ;
211306// WASM bindings
212307use wasm_bindgen:: prelude:: * ;
213308
@@ -476,4 +571,234 @@ mod tests {
476571 . to_string( )
477572 . contains( "Unknown address format" ) ) ;
478573 }
574+
575+ #[ test]
576+ fn test_output_script_support_assert_legacy ( ) {
577+ // Legacy should always succeed regardless of support flags
578+ let support_none = OutputScriptSupport {
579+ segwit : false ,
580+ taproot : false ,
581+ } ;
582+ assert ! ( support_none. assert_legacy( ) . is_ok( ) ) ;
583+
584+ let support_all = OutputScriptSupport {
585+ segwit : true ,
586+ taproot : true ,
587+ } ;
588+ assert ! ( support_all. assert_legacy( ) . is_ok( ) ) ;
589+ }
590+
591+ #[ test]
592+ fn test_output_script_support_assert_segwit ( ) {
593+ // Should succeed when segwit is supported
594+ let support_segwit = OutputScriptSupport {
595+ segwit : true ,
596+ taproot : false ,
597+ } ;
598+ assert ! ( support_segwit. assert_segwit( ) . is_ok( ) ) ;
599+
600+ // Should fail when segwit is not supported
601+ let no_support = OutputScriptSupport {
602+ segwit : false ,
603+ taproot : false ,
604+ } ;
605+ let result = no_support. assert_segwit ( ) ;
606+ assert ! ( result. is_err( ) ) ;
607+ assert ! ( result
608+ . unwrap_err( )
609+ . to_string( )
610+ . contains( "Network does not support segwit" ) ) ;
611+ }
612+
613+ #[ test]
614+ fn test_output_script_support_assert_taproot ( ) {
615+ // Should succeed when taproot is supported
616+ let support_taproot = OutputScriptSupport {
617+ segwit : true ,
618+ taproot : true ,
619+ } ;
620+ assert ! ( support_taproot. assert_taproot( ) . is_ok( ) ) ;
621+
622+ // Should fail when taproot is not supported
623+ let no_support = OutputScriptSupport {
624+ segwit : true ,
625+ taproot : false ,
626+ } ;
627+ let result = no_support. assert_taproot ( ) ;
628+ assert ! ( result. is_err( ) ) ;
629+ assert ! ( result
630+ . unwrap_err( )
631+ . to_string( )
632+ . contains( "Network does not support taproot" ) ) ;
633+ }
634+
635+ #[ test]
636+ fn test_output_script_support_assert_support_legacy ( ) {
637+ // Test with legacy P2PKH script
638+ let hash = hex:: decode ( "62e907b15cbf27d5425399ebf6f0fb50ebb88f18" ) . unwrap ( ) ;
639+ let pubkey_hash = PubkeyHash :: from_byte_array ( hash. try_into ( ) . unwrap ( ) ) ;
640+ let p2pkh_script = ScriptBuf :: new_p2pkh ( & pubkey_hash) ;
641+
642+ // Legacy scripts should work even without segwit/taproot support
643+ let no_support = OutputScriptSupport {
644+ segwit : false ,
645+ taproot : false ,
646+ } ;
647+ assert ! ( no_support. assert_support( & p2pkh_script) . is_ok( ) ) ;
648+
649+ // Test with legacy P2SH script
650+ let hash2 = hex:: decode ( "89abcdef89abcdef89abcdef89abcdef89abcdef" ) . unwrap ( ) ;
651+ let script_hash = crate :: bitcoin:: ScriptHash :: from_byte_array ( hash2. try_into ( ) . unwrap ( ) ) ;
652+ let p2sh_script = ScriptBuf :: new_p2sh ( & script_hash) ;
653+ assert ! ( no_support. assert_support( & p2sh_script) . is_ok( ) ) ;
654+ }
655+
656+ #[ test]
657+ fn test_output_script_support_assert_support_segwit ( ) {
658+ // Test with P2WPKH script (witness v0)
659+ let hash = hex:: decode ( "751e76e8199196d454941c45d1b3a323f1433bd6" ) . unwrap ( ) ;
660+ let wpkh = crate :: bitcoin:: WPubkeyHash :: from_byte_array ( hash. try_into ( ) . unwrap ( ) ) ;
661+ let p2wpkh_script = ScriptBuf :: new_p2wpkh ( & wpkh) ;
662+
663+ // Should succeed with segwit support
664+ let support_segwit = OutputScriptSupport {
665+ segwit : true ,
666+ taproot : false ,
667+ } ;
668+ assert ! ( support_segwit. assert_support( & p2wpkh_script) . is_ok( ) ) ;
669+
670+ // Should fail without segwit support
671+ let no_support = OutputScriptSupport {
672+ segwit : false ,
673+ taproot : false ,
674+ } ;
675+ let result = no_support. assert_support ( & p2wpkh_script) ;
676+ assert ! ( result. is_err( ) ) ;
677+ assert ! ( result
678+ . unwrap_err( )
679+ . to_string( )
680+ . contains( "Network does not support segwit" ) ) ;
681+
682+ // Test with P2WSH script (witness v0)
683+ let wsh = crate :: bitcoin:: WScriptHash :: from_byte_array (
684+ hex:: decode ( "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45" )
685+ . unwrap ( )
686+ . try_into ( )
687+ . unwrap ( ) ,
688+ ) ;
689+ let p2wsh_script = ScriptBuf :: new_p2wsh ( & wsh) ;
690+ assert ! ( support_segwit. assert_support( & p2wsh_script) . is_ok( ) ) ;
691+ assert ! ( no_support. assert_support( & p2wsh_script) . is_err( ) ) ;
692+ }
693+
694+ #[ test]
695+ fn test_output_script_support_assert_support_taproot ( ) {
696+ // Test with P2TR script (witness v1)
697+ use crate :: bitcoin:: secp256k1:: { Secp256k1 , XOnlyPublicKey } ;
698+
699+ let secp = Secp256k1 :: verification_only ( ) ;
700+ // Use a fixed x-only public key for testing
701+ let xonly_pk = XOnlyPublicKey :: from_slice (
702+ & hex:: decode ( "cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115" )
703+ . unwrap ( ) ,
704+ )
705+ . unwrap ( ) ;
706+ let p2tr_script = ScriptBuf :: new_p2tr ( & secp, xonly_pk, None ) ;
707+
708+ // Should succeed with taproot support
709+ let support_taproot = OutputScriptSupport {
710+ segwit : true ,
711+ taproot : true ,
712+ } ;
713+ assert ! ( support_taproot. assert_support( & p2tr_script) . is_ok( ) ) ;
714+
715+ // Should fail without taproot support (but with segwit)
716+ let no_taproot = OutputScriptSupport {
717+ segwit : true ,
718+ taproot : false ,
719+ } ;
720+ let result = no_taproot. assert_support ( & p2tr_script) ;
721+ assert ! ( result. is_err( ) ) ;
722+ assert ! ( result
723+ . unwrap_err( )
724+ . to_string( )
725+ . contains( "Network does not support taproot" ) ) ;
726+
727+ // Should also fail without segwit or taproot
728+ let no_support = OutputScriptSupport {
729+ segwit : false ,
730+ taproot : false ,
731+ } ;
732+ let result = no_support. assert_support ( & p2tr_script) ;
733+ assert ! ( result. is_err( ) ) ;
734+ }
735+
736+ #[ test]
737+ fn test_output_script_support_network_specific ( ) {
738+ // Test Bitcoin - should support segwit and taproot
739+ let btc_support = Network :: Bitcoin . output_script_support ( ) ;
740+ assert ! ( btc_support. segwit) ;
741+ assert ! ( btc_support. taproot) ;
742+
743+ // Test Bitcoin testnet - should support segwit and taproot
744+ let tbtc_support = Network :: BitcoinTestnet3 . output_script_support ( ) ;
745+ assert ! ( tbtc_support. segwit) ;
746+ assert ! ( tbtc_support. taproot) ;
747+
748+ // Test Litecoin - should support segwit but not taproot
749+ let ltc_support = Network :: Litecoin . output_script_support ( ) ;
750+ assert ! ( ltc_support. segwit) ;
751+ assert ! ( !ltc_support. taproot) ;
752+
753+ // Test Bitcoin Gold - should support segwit but not taproot
754+ let btg_support = Network :: BitcoinGold . output_script_support ( ) ;
755+ assert ! ( btg_support. segwit) ;
756+ assert ! ( !btg_support. taproot) ;
757+
758+ // Test Dogecoin - should not support segwit or taproot
759+ let doge_support = Network :: Dogecoin . output_script_support ( ) ;
760+ assert ! ( !doge_support. segwit) ;
761+ assert ! ( !doge_support. taproot) ;
762+
763+ // Test Bitcoin Cash - should not support segwit or taproot
764+ let bch_support = Network :: BitcoinCash . output_script_support ( ) ;
765+ assert ! ( !bch_support. segwit) ;
766+ assert ! ( !bch_support. taproot) ;
767+ }
768+
769+ #[ test]
770+ fn test_get_encode_codec_enforces_script_support ( ) {
771+ // Test that get_encode_codec enforces script support via assert_support
772+
773+ // P2WPKH on Bitcoin should work
774+ let hash = hex:: decode ( "751e76e8199196d454941c45d1b3a323f1433bd6" ) . unwrap ( ) ;
775+ let wpkh = crate :: bitcoin:: WPubkeyHash :: from_byte_array ( hash. try_into ( ) . unwrap ( ) ) ;
776+ let p2wpkh_script = ScriptBuf :: new_p2wpkh ( & wpkh) ;
777+ assert ! ( get_encode_codec( Network :: Bitcoin , & p2wpkh_script, AddressFormat :: Default ) . is_ok( ) ) ;
778+
779+ // P2WPKH on Bitcoin Cash should fail (no segwit support)
780+ let result = get_encode_codec ( Network :: BitcoinCash , & p2wpkh_script, AddressFormat :: Default ) ;
781+ assert ! ( result. is_err( ) ) ;
782+ if let Err ( e) = result {
783+ assert ! ( e. to_string( ) . contains( "Network does not support segwit" ) ) ;
784+ }
785+
786+ // P2TR on Bitcoin should work
787+ use crate :: bitcoin:: secp256k1:: { Secp256k1 , XOnlyPublicKey } ;
788+ let secp = Secp256k1 :: verification_only ( ) ;
789+ let xonly_pk = XOnlyPublicKey :: from_slice (
790+ & hex:: decode ( "cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115" )
791+ . unwrap ( ) ,
792+ )
793+ . unwrap ( ) ;
794+ let p2tr_script = ScriptBuf :: new_p2tr ( & secp, xonly_pk, None ) ;
795+ assert ! ( get_encode_codec( Network :: Bitcoin , & p2tr_script, AddressFormat :: Default ) . is_ok( ) ) ;
796+
797+ // P2TR on Litecoin should fail (no taproot support)
798+ let result = get_encode_codec ( Network :: Litecoin , & p2tr_script, AddressFormat :: Default ) ;
799+ assert ! ( result. is_err( ) ) ;
800+ if let Err ( e) = result {
801+ assert ! ( e. to_string( ) . contains( "Network does not support taproot" ) ) ;
802+ }
803+ }
479804}
0 commit comments