Enforce script size limit when hashing scripts

There are two limits that the Bitcoin network enforces in regard to
hashing scripts

- For P2SH the redeem script must be less than 520 bytes
- For P2WSH the witness script must be less than 10,000 bytes

Currently we are only enforcing the p2sh limit when creating an address
with `Address::p2sh`.

There are various ways to create addresses from script hashes and if
users manually hash a script then use the `ScriptHash` (or
`WScritpHash`) our APIs assume the script that was hashed is valid. This
means there is the potential for users to get burned by creating
addresses that cannot be spent, something we would like to avoid.

- Add fallible constructors to `ScriptHash` and `WScriptHash`
- Add `TryFrom` impls as well to both types
- Remove the `From` impls
This commit is contained in:
Tobin C. Harding 2024-05-23 10:53:03 +10:00
parent 4686d48ec4
commit e87a54f617
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
8 changed files with 215 additions and 94 deletions

View File

@ -56,37 +56,6 @@ impl From<witness_version::TryFromError> 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]

View File

@ -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<NetworkKind>) -> Result<Address, P2shError> {
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<NetworkKind>,
) -> Result<Address, RedeemScriptSizeError> {
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<NetworkKind>) -> 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<KnownHrp>) -> Address {
let program = WitnessProgram::p2wsh(script);
pub fn p2wsh(
witness_script: &Script,
hrp: impl Into<KnownHrp>,
) -> Result<Address, WitnessScriptSizeError> {
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<KnownHrp>) -> 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<NetworkKind>) -> 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<NetworkKind>,
) -> Result<Address, WitnessScriptSizeError> {
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);

View File

@ -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.

View File

@ -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, RedeemScriptSizeError> {
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, WitnessScriptSizeError> {
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<ScriptBuf, WitnessScriptSizeError> {
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<ScriptBuf, RedeemScriptSizeError> {
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].

View File

@ -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<ScriptBuf> 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<Self, RedeemScriptSizeError> {
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]: <https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size>
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<Self, WitnessScriptSizeError> {
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<ScriptBuf> for ScriptHash {
type Error = RedeemScriptSizeError;
fn try_from(redeem_script: ScriptBuf) -> Result<Self, Self::Error> {
Self::from_script(&redeem_script)
}
}
impl From<ScriptBuf> 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, Self::Error> {
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, Self::Error> {
Self::from_script(redeem_script)
}
}
impl From<&Script> for WScriptHash {
fn from(script: &Script) -> WScriptHash { script.wscript_hash() }
impl TryFrom<ScriptBuf> for WScriptHash {
type Error = WitnessScriptSizeError;
fn try_from(witness_script: ScriptBuf) -> Result<Self, Self::Error> {
Self::from_script(&witness_script)
}
}
impl TryFrom<&ScriptBuf> for WScriptHash {
type Error = WitnessScriptSizeError;
fn try_from(witness_script: &ScriptBuf) -> Result<Self, Self::Error> {
Self::from_script(witness_script)
}
}
impl TryFrom<&Script> for WScriptHash {
type Error = WitnessScriptSizeError;
fn try_from(witness_script: &Script) -> Result<Self, Self::Error> {
Self::from_script(witness_script)
}
}
/// Encodes an integer in script(minimal CScriptNum) format.
@ -705,3 +784,39 @@ impl From<UintError> 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 {}

View File

@ -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 {

View File

@ -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<Self, WitnessScriptSizeError> {
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())
}

View File

@ -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]