diff --git a/bitcoin/src/address/error.rs b/bitcoin/src/address/error.rs index 68b9682fb..7c36fd78e 100644 --- a/bitcoin/src/address/error.rs +++ b/bitcoin/src/address/error.rs @@ -56,37 +56,6 @@ impl From for FromScriptError { fn from(e: witness_version::TryFromError) -> Self { Self::WitnessVersion(e) } } -/// Error while generating address from a p2sh script. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub enum P2shError { - /// Address size more than 520 bytes is not allowed. - ExcessiveScriptSize, -} - -internals::impl_from_infallible!(P2shError); - -impl fmt::Display for P2shError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use P2shError::*; - - match *self { - ExcessiveScriptSize => write!(f, "script size exceed 520 bytes"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for P2shError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use P2shError::*; - - match self { - ExcessiveScriptSize => None, - } - } -} - /// Address type is either invalid or not supported in rust-bitcoin. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index ee3d6089a..31f4cf8fa 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -37,12 +37,14 @@ use hashes::{sha256, HashEngine}; use secp256k1::{Secp256k1, Verification, XOnlyPublicKey}; use crate::blockdata::constants::{ - MAX_SCRIPT_ELEMENT_SIZE, PUBKEY_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST, - SCRIPT_ADDRESS_PREFIX_MAIN, SCRIPT_ADDRESS_PREFIX_TEST, + PUBKEY_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST, SCRIPT_ADDRESS_PREFIX_MAIN, + SCRIPT_ADDRESS_PREFIX_TEST, }; use crate::blockdata::script::witness_program::WitnessProgram; use crate::blockdata::script::witness_version::WitnessVersion; -use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash}; +use crate::blockdata::script::{ + self, RedeemScriptSizeError, Script, ScriptBuf, ScriptHash, WScriptHash, WitnessScriptSizeError, +}; use crate::consensus::Params; use crate::crypto::key::{ CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey, @@ -56,7 +58,7 @@ use crate::taproot::TapNodeHash; pub use self::{ error::{ FromScriptError, InvalidBase58PayloadLengthError, InvalidLegacyPrefixError, LegacyAddressTooLongError, - NetworkValidationError, ParseError, P2shError, UnknownAddressTypeError, UnknownHrpError + NetworkValidationError, ParseError, UnknownAddressTypeError, UnknownHrpError }, }; @@ -400,11 +402,11 @@ impl Address { /// This address type was introduced with BIP16 and is the popular type to implement multi-sig /// these days. #[inline] - pub fn p2sh(script: &Script, network: impl Into) -> Result { - if script.len() > MAX_SCRIPT_ELEMENT_SIZE { - return Err(P2shError::ExcessiveScriptSize); - } - let hash = script.script_hash(); + pub fn p2sh( + redeem_script: &Script, + network: impl Into, + ) -> Result { + let hash = redeem_script.script_hash()?; Ok(Address::p2sh_from_hash(hash, network)) } @@ -431,23 +433,36 @@ impl Address { /// This is a segwit address type that looks familiar (as p2sh) to legacy clients. pub fn p2shwpkh(pk: CompressedPublicKey, network: impl Into) -> Address { let builder = script::Builder::new().push_int(0).push_slice(pk.wpubkey_hash()); - let script_hash = builder.as_script().script_hash(); + let script_hash = builder.as_script().script_hash().expect("script is less than 520 bytes"); Address::p2sh_from_hash(script_hash, network) } /// Creates a witness pay to script hash address. - pub fn p2wsh(script: &Script, hrp: impl Into) -> Address { - let program = WitnessProgram::p2wsh(script); + pub fn p2wsh( + witness_script: &Script, + hrp: impl Into, + ) -> Result { + let program = WitnessProgram::p2wsh(witness_script)?; + Ok(Address::from_witness_program(program, hrp)) + } + + /// Creates a witness pay to script hash address. + pub fn p2wsh_from_hash(hash: WScriptHash, hrp: impl Into) -> Address { + let program = WitnessProgram::p2wsh_from_hash(hash); Address::from_witness_program(program, hrp) } /// Creates a pay to script address that embeds a witness pay to script hash address. /// /// This is a segwit address type that looks familiar (as p2sh) to legacy clients. - pub fn p2shwsh(script: &Script, network: impl Into) -> Address { - let builder = script::Builder::new().push_int(0).push_slice(script.wscript_hash()); - let script_hash = builder.as_script().script_hash(); - Address::p2sh_from_hash(script_hash, network) + pub fn p2shwsh( + witness_script: &Script, + network: impl Into, + ) -> Result { + let hash = witness_script.wscript_hash()?; + let builder = script::Builder::new().push_int(0).push_slice(&hash); + let script_hash = builder.as_script().script_hash().expect("script is less than 520 bytes"); + Ok(Address::p2sh_from_hash(script_hash, network)) } /// Creates a pay to taproot address from an untweaked key. @@ -957,7 +972,10 @@ mod tests { #[test] fn test_p2sh_parse_for_large_script() { let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap(); - assert_eq!(Address::p2sh(&script, NetworkKind::Test), Err(P2shError::ExcessiveScriptSize)); + assert_eq!( + Address::p2sh(&script, NetworkKind::Test), + Err(RedeemScriptSizeError { size: script.len() }) + ); } #[test] @@ -976,7 +994,7 @@ mod tests { fn test_p2wsh() { // stolen from Bitcoin transaction 5df912fda4becb1c29e928bec8d64d93e9ba8efa9b5b405bd683c86fd2c65667 let script = ScriptBuf::from_hex("52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae").unwrap(); - let addr = Address::p2wsh(&script, KnownHrp::Mainnet); + let addr = Address::p2wsh(&script, KnownHrp::Mainnet).expect("script is valid"); assert_eq!( &addr.to_string(), "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej" @@ -1001,7 +1019,7 @@ mod tests { fn test_p2shwsh() { // stolen from Bitcoin transaction f9ee2be4df05041d0e0a35d7caa3157495ca4f93b233234c9967b6901dacf7a9 let script = ScriptBuf::from_hex("522103e5529d8eaa3d559903adb2e881eb06c86ac2574ffa503c45f4e942e2a693b33e2102e5f10fcdcdbab211e0af6a481f5532536ec61a5fdbf7183770cf8680fe729d8152ae").unwrap(); - let addr = Address::p2shwsh(&script, NetworkKind::Main); + let addr = Address::p2shwsh(&script, NetworkKind::Main).expect("script is valid"); assert_eq!(&addr.to_string(), "36EqgNnsWW94SreZgBWc1ANC6wpFZwirHr"); assert_eq!(addr.address_type(), Some(AddressType::P2sh)); roundtrips(&addr, Bitcoin); diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index 8aa42d22c..248d20e6f 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -40,8 +40,12 @@ pub const SCRIPT_ADDRESS_PREFIX_MAIN: u8 = 5; // 0x05 pub const PUBKEY_ADDRESS_PREFIX_TEST: u8 = 111; // 0x6f /// Test (tesnet, signet, regtest) script address prefix. pub const SCRIPT_ADDRESS_PREFIX_TEST: u8 = 196; // 0xc4 -/// The maximum allowed script size. -pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; +/// The maximum allowed redeem script size for a P2SH output. +pub const MAX_REDEEM_SCRIPT_SIZE: usize = 520; +/// The maximum allowed redeem script size of the witness script. +pub const MAX_WITNESS_SCRIPT_SIZE: usize = 10_000; +/// The maximum allowed size of any single witness stack element. +pub const MAX_STACK_ELEMENT_SIZE: usize = 520; /// How may blocks between halvings. pub const SUBSIDY_HALVING_INTERVAL: u32 = 210_000; /// Maximum allowed value for an integer in Script. diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 050e0c578..dd7f6e022 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -13,8 +13,8 @@ use crate::blockdata::opcodes::all::*; use crate::blockdata::opcodes::{self, Opcode}; use crate::blockdata::script::witness_version::WitnessVersion; use crate::blockdata::script::{ - bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, ScriptBuf, - ScriptHash, WScriptHash, + bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, + RedeemScriptSizeError, ScriptBuf, ScriptHash, WScriptHash, WitnessScriptSizeError, }; use crate::consensus::Encodable; use crate::key::{PublicKey, UntweakedPublicKey, WPubkeyHash}; @@ -115,13 +115,17 @@ impl Script { /// Creates a new script builder pub fn builder() -> Builder { Builder::new() } - /// Returns 160-bit hash of the script. + /// Returns 160-bit hash of the script for P2SH outputs. #[inline] - pub fn script_hash(&self) -> ScriptHash { ScriptHash::hash(self.as_bytes()) } + pub fn script_hash(&self) -> Result { + ScriptHash::from_script(self) + } /// Returns 256-bit hash of the script for P2WSH outputs. #[inline] - pub fn wscript_hash(&self) -> WScriptHash { WScriptHash::hash(self.as_bytes()) } + pub fn wscript_hash(&self) -> Result { + WScriptHash::from_script(self) + } /// Computes leaf hash of tapscript. #[inline] @@ -148,7 +152,9 @@ impl Script { /// Computes the P2WSH output corresponding to this witnessScript (aka the "witness redeem /// script"). #[inline] - pub fn to_p2wsh(&self) -> ScriptBuf { ScriptBuf::new_p2wsh(&self.wscript_hash()) } + pub fn to_p2wsh(&self) -> Result { + self.wscript_hash().map(|hash| ScriptBuf::new_p2wsh(&hash)) + } /// Computes P2TR output with a given internal key and a single script spending path equal to /// the current script, assuming that the script is a Tapscript. @@ -376,7 +382,9 @@ impl Script { } /// Computes the P2SH output corresponding to this redeem script. - pub fn to_p2sh(&self) -> ScriptBuf { ScriptBuf::new_p2sh(&self.script_hash()) } + pub fn to_p2sh(&self) -> Result { + self.script_hash().map(|hash| ScriptBuf::new_p2sh(&hash)) + } /// Returns the script code used for spending a P2WPKH output if this script is a script pubkey /// for a P2WPKH output. The `scriptCode` is described in [BIP143]. diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs index 855d543aa..32cb55d92 100644 --- a/bitcoin/src/blockdata/script/mod.rs +++ b/bitcoin/src/blockdata/script/mod.rs @@ -67,6 +67,7 @@ use core::ops::{Deref, DerefMut}; use hashes::{hash160, sha256}; use io::{BufRead, Write}; +use crate::blockdata::constants::{MAX_REDEEM_SCRIPT_SIZE, MAX_WITNESS_SCRIPT_SIZE}; use crate::blockdata::opcodes::all::*; use crate::blockdata::opcodes::{self, Opcode}; use crate::consensus::{encode, Decodable, Encodable}; @@ -92,28 +93,106 @@ hashes::hash_newtype! { } impl_asref_push_bytes!(ScriptHash, WScriptHash); -impl From for ScriptHash { - fn from(script: ScriptBuf) -> ScriptHash { script.script_hash() } +impl ScriptHash { + /// Creates a `ScriptHash` after first checking the script size. + /// + /// # 520-byte limitation on serialized script size + /// + /// > As a consequence of the requirement for backwards compatibility the serialized script is + /// > itself subject to the same rules as any other PUSHDATA operation, including the rule that + /// > no data greater than 520 bytes may be pushed to the stack. Thus it is not possible to + /// > spend a P2SH output if the redemption script it refers to is >520 bytes in length. + /// + /// ref: [BIP-16](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size) + pub fn from_script(redeem_script: &Script) -> Result { + if redeem_script.len() > MAX_REDEEM_SCRIPT_SIZE { + return Err(RedeemScriptSizeError { size: redeem_script.len() }); + } + + Ok(ScriptHash::hash(redeem_script.as_bytes())) + } + + /// Creates a `ScriptHash` from any script irrespective of script size. + /// + /// If you hash a script that exceeds 520 bytes in size and use it to create a P2SH output + /// then the output will be unspendable (see [BIP-16]). + /// + /// [BIP-16]: + pub fn from_script_unchecked(script: &Script) -> Self { ScriptHash::hash(script.as_bytes()) } } -impl From<&ScriptBuf> for ScriptHash { - fn from(script: &ScriptBuf) -> ScriptHash { script.script_hash() } +impl WScriptHash { + /// Creates a `WScriptHash` after first checking the script size. + /// + /// # 10,000-byte limit on the witness script + /// + /// > The witnessScript (≤ 10,000 bytes) is popped off the initial witness stack. SHA256 of the + /// > witnessScript must match the 32-byte witness program. + /// + /// ref: [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) + pub fn from_script(witness_script: &Script) -> Result { + if witness_script.len() > MAX_WITNESS_SCRIPT_SIZE { + return Err(WitnessScriptSizeError { size: witness_script.len() }); + } + + Ok(WScriptHash::hash(witness_script.as_bytes())) + } + + /// Creates a `WScriptHash` from any script irrespective of script size. + /// + /// If you hash a script that exceeds 10,000 bytes in size and use it to create a Segwit + /// output then the output will be unspendable (see [BIP-141]). + /// + /// ref: [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) + pub fn from_script_unchecked(script: &Script) -> Self { WScriptHash::hash(script.as_bytes()) } } -impl From<&Script> for ScriptHash { - fn from(script: &Script) -> ScriptHash { script.script_hash() } +impl TryFrom for ScriptHash { + type Error = RedeemScriptSizeError; + + fn try_from(redeem_script: ScriptBuf) -> Result { + Self::from_script(&redeem_script) + } } -impl From for WScriptHash { - fn from(script: ScriptBuf) -> WScriptHash { script.wscript_hash() } +impl TryFrom<&ScriptBuf> for ScriptHash { + type Error = RedeemScriptSizeError; + + fn try_from(redeem_script: &ScriptBuf) -> Result { + Self::from_script(redeem_script) + } } -impl From<&ScriptBuf> for WScriptHash { - fn from(script: &ScriptBuf) -> WScriptHash { script.wscript_hash() } +impl TryFrom<&Script> for ScriptHash { + type Error = RedeemScriptSizeError; + + fn try_from(redeem_script: &Script) -> Result { + Self::from_script(redeem_script) + } } -impl From<&Script> for WScriptHash { - fn from(script: &Script) -> WScriptHash { script.wscript_hash() } +impl TryFrom for WScriptHash { + type Error = WitnessScriptSizeError; + + fn try_from(witness_script: ScriptBuf) -> Result { + Self::from_script(&witness_script) + } +} + +impl TryFrom<&ScriptBuf> for WScriptHash { + type Error = WitnessScriptSizeError; + + fn try_from(witness_script: &ScriptBuf) -> Result { + Self::from_script(witness_script) + } +} + +impl TryFrom<&Script> for WScriptHash { + type Error = WitnessScriptSizeError; + + fn try_from(witness_script: &Script) -> Result { + Self::from_script(witness_script) + } } /// Encodes an integer in script(minimal CScriptNum) format. @@ -705,3 +784,39 @@ impl From for Error { } } } + +/// Error while hashing a redeem script. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RedeemScriptSizeError { + /// Invalid redeem script size (cannot exceed 520 bytes). + pub size: usize, +} + +internals::impl_from_infallible!(RedeemScriptSizeError); + +impl fmt::Display for RedeemScriptSizeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "redeem script size exceeds {} bytes: {}", MAX_REDEEM_SCRIPT_SIZE, self.size) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RedeemScriptSizeError {} + +/// Error while hashing a witness script. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WitnessScriptSizeError { + /// Invalid witness script size (cannot exceed 10,000 bytes). + pub size: usize, +} + +internals::impl_from_infallible!(WitnessScriptSizeError); + +impl fmt::Display for WitnessScriptSizeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "witness script size exceeds {} bytes: {}", MAX_WITNESS_SCRIPT_SIZE, self.size) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for WitnessScriptSizeError {} diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index 431456c33..760368dfd 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -210,15 +210,15 @@ fn script_generators() { assert!(ScriptBuf::new_p2wpkh(&wpubkey_hash).is_p2wpkh()); let script = Builder::new().push_opcode(OP_NUMEQUAL).push_verify().into_script(); - let script_hash = script.script_hash(); + let script_hash = script.script_hash().expect("script is less than 520 bytes"); let p2sh = ScriptBuf::new_p2sh(&script_hash); assert!(p2sh.is_p2sh()); - assert_eq!(script.to_p2sh(), p2sh); + assert_eq!(script.to_p2sh().unwrap(), p2sh); - let wscript_hash = script.wscript_hash(); + let wscript_hash = script.wscript_hash().expect("script is less than 10,000 bytes"); let p2wsh = ScriptBuf::new_p2wsh(&wscript_hash); assert!(p2wsh.is_p2wsh()); - assert_eq!(script.to_p2wsh(), p2wsh); + assert_eq!(script.to_p2wsh().unwrap(), p2wsh); // Test data are taken from the second output of // 2ccb3a1f745eb4eefcf29391460250adda5fab78aaddb902d25d3cd97d9d8e61 transaction @@ -364,9 +364,12 @@ fn non_minimal_scriptints() { #[test] fn script_hashes() { let script = ScriptBuf::from_hex("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap(); - assert_eq!(script.script_hash().to_string(), "8292bcfbef1884f73c813dfe9c82fd7e814291ea"); assert_eq!( - script.wscript_hash().to_string(), + script.script_hash().unwrap().to_string(), + "8292bcfbef1884f73c813dfe9c82fd7e814291ea" + ); + assert_eq!( + script.wscript_hash().unwrap().to_string(), "3e1525eb183ad4f9b3c5fa3175bdca2a52e947b135bbb90383bf9f6408e2c324" ); assert_eq!( @@ -552,26 +555,26 @@ fn p2sh_p2wsh_conversion() { let expected_witout = ScriptBuf::from_hex("0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64") .unwrap(); - assert!(witness_script.to_p2wsh().is_p2wsh()); - assert_eq!(witness_script.to_p2wsh(), expected_witout); + assert!(witness_script.to_p2wsh().unwrap().is_p2wsh()); + assert_eq!(witness_script.to_p2wsh().unwrap(), expected_witout); // p2sh let redeem_script = ScriptBuf::from_hex("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8").unwrap(); let expected_p2shout = ScriptBuf::from_hex("a91491b24bf9f5288532960ac687abb035127b1d28a587").unwrap(); - assert!(redeem_script.to_p2sh().is_p2sh()); - assert_eq!(redeem_script.to_p2sh(), expected_p2shout); + assert!(redeem_script.to_p2sh().unwrap().is_p2sh()); + assert_eq!(redeem_script.to_p2sh().unwrap(), expected_p2shout); // p2sh-p2wsh - let redeem_script = ScriptBuf::from_hex("410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac").unwrap(); + let witness_script = ScriptBuf::from_hex("410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac").unwrap(); let expected_witout = ScriptBuf::from_hex("0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64") .unwrap(); let expected_out = ScriptBuf::from_hex("a914f386c2ba255cc56d20cfa6ea8b062f8b5994551887").unwrap(); - assert!(redeem_script.to_p2sh().is_p2sh()); - assert_eq!(redeem_script.to_p2wsh(), expected_witout); - assert_eq!(redeem_script.to_p2wsh().to_p2sh(), expected_out); + assert!(witness_script.to_p2sh().unwrap().is_p2sh()); + assert_eq!(witness_script.to_p2wsh().unwrap(), expected_witout); + assert_eq!(witness_script.to_p2wsh().unwrap().to_p2sh().unwrap(), expected_out); } macro_rules! unwrap_all { diff --git a/bitcoin/src/blockdata/script/witness_program.rs b/bitcoin/src/blockdata/script/witness_program.rs index 28dfde8c3..c277c8872 100644 --- a/bitcoin/src/blockdata/script/witness_program.rs +++ b/bitcoin/src/blockdata/script/witness_program.rs @@ -13,7 +13,7 @@ use internals::array_vec::ArrayVec; use secp256k1::{Secp256k1, Verification}; use crate::blockdata::script::witness_version::WitnessVersion; -use crate::blockdata::script::{PushBytes, Script}; +use crate::blockdata::script::{PushBytes, Script, WScriptHash, WitnessScriptSizeError}; use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey}; use crate::taproot::TapNodeHash; @@ -77,8 +77,12 @@ impl WitnessProgram { } /// Creates a [`WitnessProgram`] from `script` for a P2WSH output. - pub fn p2wsh(script: &Script) -> Self { - let hash = script.wscript_hash(); + pub fn p2wsh(script: &Script) -> Result { + script.wscript_hash().map(Self::p2wsh_from_hash) + } + + /// Creates a [`WitnessProgram`] from `script` for a P2WSH output. + pub fn p2wsh_from_hash(hash: WScriptHash) -> Self { WitnessProgram::new_p2wsh(hash.to_byte_array()) } diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index ee53fb1ed..f8cc1a084 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -1757,10 +1757,10 @@ mod tests { assert!(redeem_script.is_p2wpkh()); assert_eq!( - redeem_script.to_p2sh(), + redeem_script.to_p2sh().unwrap(), psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey ); - assert_eq!(redeem_script.to_p2sh(), expected_out); + assert_eq!(redeem_script.to_p2sh().unwrap(), expected_out); for output in psbt.outputs { assert_eq!(output.get_pairs().len(), 0) @@ -1803,10 +1803,10 @@ mod tests { assert!(redeem_script.is_p2wpkh()); assert_eq!( - redeem_script.to_p2sh(), + redeem_script.to_p2sh().unwrap(), psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey ); - assert_eq!(redeem_script.to_p2sh(), expected_out); + assert_eq!(redeem_script.to_p2sh().unwrap(), expected_out); for output in psbt.outputs { assert!(!output.get_pairs().is_empty()) @@ -1828,11 +1828,11 @@ mod tests { assert!(redeem_script.is_p2wsh()); assert_eq!( - redeem_script.to_p2sh(), + redeem_script.to_p2sh().unwrap(), psbt.inputs[0].witness_utxo.as_ref().unwrap().script_pubkey ); - assert_eq!(redeem_script.to_p2sh(), expected_out); + assert_eq!(redeem_script.to_p2sh().unwrap(), expected_out); } #[test]