primitives: split RedeemScript and RedeemScriptBuf from script
What changed, and why it matters
This commit is a code-quality and type-safety refactor in the rust-bitcoin library. It introduces a dedicated RedeemScript type so that P2SH redeem scripts can no longer be accidentally confused with other script types (such as witness scripts). The change makes several previously-allowed unsafe conversions fail at compile time, which helps prevent future bugs rather than fixing an active vulnerability.
No immediate action required. Users of the library should update code that passes ScriptBuf where RedeemScriptBuf is now expected, and review any custom conversions between redeem and witness scripts for correctness.
Security signals we found
Type-system hardening to prevent cross-casting of script types
Author acknowledges prior sloppy handling of redeem/witness scripts in tests
Compile-time prevention of invalid script type reinterpretation
PSBT redeem_script fields narrowed to RedeemScriptBuf
Evidence from the diff
The patch splits RedeemScript/RedeemScriptBuf out of the generic Script type and adds a ScriptHashableTag marker trait limiting script_hash/to_p2sh/p2wpkh_script_code/witness_version/is_p2wsh/is_p2wpkh to RedeemScript and ScriptPubKey. PSBT Input/Output redeem_script fields are changed from ScriptBuf to RedeemScriptBuf. The author notes that previous test code was ‘sloppy’ and reinterpreted redeem scripts as witness scripts, and that such code ‘no longer compiles’. This is a hardening change, not a runtime bug fix.
Changed components
bitcoin/src/blockdata/script/borrowed.rsbitcoin/src/blockdata/script/mod.rsbitcoin/src/blockdata/script/tests.rsbitcoin/src/psbt/map/input.rsbitcoin/src/psbt/map/output.rsbitcoin/src/crypto/sighash.rsbitcoin/src/address/mod.rsprimitives/src/script/mod.rsprimitives/src/script/tag.rsInspect captured patch +153 / −81
diff --git a/bitcoin/examples/ecdsa-psbt b/bitcoin/examples/ecdsa-psbt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/bitcoin/examples/ecdsa-psbt
@@ -0,0 +1 @@
+
diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs
index f5a61d16..a7a6eee1 100644
--- a/bitcoin/examples/ecdsa-psbt-simple.rs
+++ b/bitcoin/examples/ecdsa-psbt-simple.rs
@@ -31,8 +31,9 @@ use bitcoin::locktime::absolute;
use bitcoin::psbt::Input;
use bitcoin::secp256k1::{Secp256k1, Signing};
use bitcoin::{
- consensus, transaction, Address, Amount, EcdsaSighashType, Network, OutPoint, Psbt, ScriptBuf,
- ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
+ consensus, transaction, Address, Amount, EcdsaSighashType, Network, OutPoint, Psbt,
+ RedeemScriptBuf, ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Txid,
+ Witness,
};
// The master xpriv, from which we derive the keys we control.
@@ -202,14 +203,14 @@ fn main() {
psbt.inputs = vec![
Input {
witness_utxo: Some(utxos[0].clone()),
- redeem_script: Some(ScriptBuf::new_p2wpkh(wpkhs[0])),
+ redeem_script: Some(RedeemScriptBuf::new_p2wpkh(wpkhs[0])),
bip32_derivation: bip32_derivations[0].clone(),
sighash_type: Some(ty),
..Default::default()
},
Input {
witness_utxo: Some(utxos[1].clone()),
- redeem_script: Some(ScriptBuf::new_p2wpkh(wpkhs[1])),
+ redeem_script: Some(RedeemScriptBuf::new_p2wpkh(wpkhs[1])),
bip32_derivation: bip32_derivations[1].clone(),
sighash_type: Some(ty),
..Default::default()
diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs
index 0909c211..c2402514 100644
--- a/bitcoin/examples/ecdsa-psbt.rs
+++ b/bitcoin/examples/ecdsa-psbt.rs
@@ -38,7 +38,7 @@ use bitcoin::locktime::absolute;
use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType};
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
use bitcoin::{
- transaction, Address, Amount, CompressedPublicKey, Network, OutPoint, ScriptBuf,
+ transaction, Address, Amount, CompressedPublicKey, Network, OutPoint, RedeemScriptBuf,
ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
};
@@ -208,7 +208,7 @@ impl WatchOnly {
let pk = self.input_xpub.to_public_key();
let wpkh = pk.wpubkey_hash();
- let redeem_script = ScriptBuf::new_p2wpkh(wpkh);
+ let redeem_script = RedeemScriptBuf::new_p2wpkh(wpkh);
input.redeem_script = Some(redeem_script);
let fingerprint = self.master_fingerprint;
diff --git a/bitcoin/examples/sighash.rs b/bitcoin/examples/sighash.rs
index bac1fb94..52f2a681 100644
--- a/bitcoin/examples/sighash.rs
+++ b/bitcoin/examples/sighash.rs
@@ -1,6 +1,7 @@
use bitcoin::ext::*;
use bitcoin::{
- consensus, ecdsa, sighash, Amount, CompressedPublicKey, Script, ScriptPubKeyBuf, Transaction,
+ consensus, ecdsa, sighash, Amount, CompressedPublicKey, Script, ScriptPubKey, ScriptPubKeyBuf,
+ Transaction,
};
use hex_lit::hex;
@@ -78,7 +79,7 @@ fn compute_sighash_legacy(raw_tx: &[u8], inp_idx: usize, script_pubkey_bytes_opt
script_pubkey_p2sh.push_bytes().unwrap().as_bytes()
}
};
- let script_code = Script::from_bytes(script_pubkey_bytes);
+ let script_code = ScriptPubKey::from_bytes(script_pubkey_bytes);
let pushbytes_0 = instructions.remove(0).unwrap();
assert!(
pushbytes_0.push_bytes().unwrap().as_bytes().is_empty(),
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 66e997d8..c73a4363 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -64,9 +64,9 @@ use crate::prelude::{String, ToOwned};
use crate::script::witness_program::WitnessProgram;
use crate::script::witness_version::WitnessVersion;
use crate::script::{
- self, GenericScriptExt as _, RedeemScriptSizeError, Script, ScriptExt as _, ScriptHash,
- ScriptPubKey, ScriptPubKeyBuf, ScriptPubKeyBufExt as _, ScriptPubKeyExt as _, WScriptHash,
- WitnessScriptSizeError,
+ self, GenericScript, GenericScriptExt as _, RedeemScriptSizeError, Script, ScriptExt as _,
+ ScriptHash, ScriptHashableTag, ScriptPubKey, ScriptPubKeyBuf, ScriptPubKeyBufExt as _,
+ ScriptPubKeyExt as _, WScriptHash, WitnessScriptSizeError,
};
use crate::taproot::TapNodeHash;
@@ -506,8 +506,8 @@ impl Address {
/// This address type was introduced with BIP-0016 and is the popular type to implement multi-sig
/// these days.
#[inline]
- pub fn p2sh(
- redeem_script: &Script,
+ pub fn p2sh<T: ScriptHashableTag>(
+ redeem_script: &GenericScript<T>,
network: impl Into<NetworkKind>,
) -> Result<Address, RedeemScriptSizeError> {
let hash = redeem_script.script_hash()?;
@@ -1021,7 +1021,7 @@ mod tests {
use super::*;
use crate::network::Network::{Bitcoin, Testnet};
use crate::network::{params, TestnetVersion};
- use crate::script::{GenericScriptBufExt as _, ScriptBuf};
+ use crate::script::{GenericScriptBufExt as _, RedeemScriptBuf, ScriptBuf};
fn roundtrips(addr: &Address, network: Network) {
assert_eq!(
@@ -1098,7 +1098,7 @@ mod tests {
#[test]
fn p2sh_parse() {
- let script = ScriptBuf::from_hex_no_length_prefix("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae").unwrap();
+ let script = RedeemScriptBuf::from_hex_no_length_prefix("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae").unwrap();
let addr = Address::p2sh(&script, NetworkKind::Test).unwrap();
assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr");
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
@@ -1107,7 +1107,7 @@ mod tests {
#[test]
fn p2sh_parse_for_large_script() {
- let script = ScriptBuf::from_hex_no_length_prefix("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap();
+ let script = RedeemScriptBuf::from_hex_no_length_prefix("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap();
let res = Address::p2sh(&script, NetworkKind::Test);
assert_eq!(res.unwrap_err().invalid_size(), script.len())
}
diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs
index ffb345c9..c70de812 100644
--- a/bitcoin/src/blockdata/script/borrowed.rs
+++ b/bitcoin/src/blockdata/script/borrowed.rs
@@ -9,9 +9,9 @@ use secp256k1::{Secp256k1, Verification};
use super::witness_version::WitnessVersion;
use super::{
- Builder, GenericScript, Instruction, InstructionIndices, Instructions, PushBytes,
- RedeemScriptSizeError, Script, ScriptHash, ScriptPubKey, ScriptSig, WScriptHash,
- WitnessScriptSizeError,
+ Builder, GenericScript, Instruction, InstructionIndices, Instructions, PushBytes, RedeemScript,
+ RedeemScriptSizeError, Script, ScriptHash, ScriptHashableTag, ScriptPubKey, ScriptSig,
+ WScriptHash, WitnessScriptSizeError,
};
use crate::consensus::{self, Encodable};
use crate::key::{PublicKey, UntweakedPublicKey, WPubkeyHash};
@@ -157,18 +157,21 @@ internal_macros::define_extension_trait! {
self.as_bytes().first().copied().map(From::from)
}
- // These methods should exist for only ScriptPubKey and RedeemScript. When we
- // introduce RedeemScript we'll also introduce another marker trait to limit
- // them. For now just put them on every Script type.
+ // These methods only exist for scriptPubKey and redeemScript, as indicated by the
+ // where clauses on them.
/// Returns 160-bit hash of the script for P2SH outputs.
#[inline]
- fn script_hash(&self) -> Result<ScriptHash, RedeemScriptSizeError> {
+ fn script_hash(&self) -> Result<ScriptHash, RedeemScriptSizeError>
+ where T: ScriptHashableTag
+ {
ScriptHash::from_script(self)
}
/// Computes the P2SH output corresponding to this redeem script.
- fn to_p2sh(&self) -> Result<ScriptPubKeyBuf, RedeemScriptSizeError> {
+ fn to_p2sh(&self) -> Result<ScriptPubKeyBuf, RedeemScriptSizeError>
+ where T: ScriptHashableTag
+ {
self.script_hash().map(ScriptPubKeyBuf::new_p2sh)
}
@@ -176,7 +179,9 @@ internal_macros::define_extension_trait! {
/// for a P2WPKH output. The `scriptCode` is described in [BIP-0143].
///
/// [BIP-0143]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki>
- fn p2wpkh_script_code(&self) -> Option<ScriptBuf> {
+ fn p2wpkh_script_code(&self) -> Option<ScriptBuf>
+ where T: ScriptHashableTag
+ {
if self.is_p2wpkh() {
// The `self` script is 0x00, 0x14, <pubkey_hash>
let bytes = <[u8; 20]>::try_from(&self.as_bytes()[2..]).expect("length checked in is_p2wpkh()");
@@ -198,7 +203,9 @@ internal_macros::define_extension_trait! {
/// > special meaning. The value of the first push is called the "version byte". The following
/// > byte vector pushed is called the "witness program".
#[inline]
- fn witness_version(&self) -> Option<WitnessVersion> {
+ fn witness_version(&self) -> Option<WitnessVersion>
+ where T: ScriptHashableTag
+ {
let script_len = self.len();
if !(4..=42).contains(&script_len) {
return None;
@@ -220,7 +227,9 @@ internal_macros::define_extension_trait! {
/// Checks whether a script pubkey is a P2WSH output.
#[inline]
- fn is_p2wsh(&self) -> bool {
+ fn is_p2wsh(&self) -> bool
+ where T: ScriptHashableTag
+ {
self.len() == 34
&& self.witness_version() == Some(WitnessVersion::V0)
&& self.as_bytes()[1] == OP_PUSHBYTES_32.to_u8()
@@ -228,7 +237,9 @@ internal_macros::define_extension_trait! {
/// Checks whether a script pubkey is a P2WPKH output.
#[inline]
- fn is_p2wpkh(&self) -> bool {
+ fn is_p2wpkh(&self) -> bool
+ where T: ScriptHashableTag
+ {
self.len() == 22
&& self.witness_version() == Some(WitnessVersion::V0)
&& self.as_bytes()[1] == OP_PUSHBYTES_20.to_u8()
@@ -572,14 +583,14 @@ internal_macros::define_extension_trait! {
/// It merely gets the last push of the script.
///
/// Use [`Script::is_p2sh`] on the scriptPubKey to check whether it is actually a P2SH script.
- fn redeem_script(&self) -> Option<&Script> {
+ fn redeem_script(&self) -> Option<&RedeemScript> {
// Script must consist entirely of pushes.
if self.instructions().any(|i| i.is_err() || i.unwrap().push_bytes().is_none()) {
return None;
}
if let Some(Ok(Instruction::PushBytes(b))) = self.instructions().last() {
- Some(Script::from_bytes(b.as_bytes()))
+ Some(RedeemScript::from_bytes(b.as_bytes()))
} else {
None
}
diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs
index 9aee65c2..aac2d030 100644
--- a/bitcoin/src/blockdata/script/mod.rs
+++ b/bitcoin/src/blockdata/script/mod.rs
@@ -82,9 +82,10 @@ pub use self::{
};
#[doc(inline)]
pub use primitives::script::{
- GenericScript, GenericScriptBuf, RedeemScriptSizeError, Script, ScriptBuf, ScriptHash,
- ScriptPubKey, ScriptPubKeyBuf, ScriptPubKeyTag, ScriptSig, ScriptSigBuf, ScriptSigTag, Tag,
- WScriptHash, Whatever, WitnessScriptSizeError,
+ GenericScript, GenericScriptBuf, RedeemScript, RedeemScriptBuf, RedeemScriptSizeError,
+ RedeemScriptTag, Script, ScriptBuf, ScriptHash, ScriptHashableTag, ScriptPubKey,
+ ScriptPubKeyBuf, ScriptPubKeyTag, ScriptSig, ScriptSigBuf, ScriptSigTag, Tag, WScriptHash,
+ Whatever, WitnessScriptSizeError,
};
pub(crate) use self::borrowed::GenericScriptExtPriv;
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index bc6afc6d..f7a8245e 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -240,12 +240,13 @@ fn script_generators() {
let wpubkey_hash = pubkey.wpubkey_hash().unwrap();
assert!(ScriptPubKeyBuf::new_p2wpkh(wpubkey_hash).is_p2wpkh());
- let script = Builder::new().push_opcode(OP_NUMEQUAL).push_verify().into_script();
+ let script = RedeemScript::builder().push_opcode(OP_NUMEQUAL).push_verify().into_script();
let script_hash = script.script_hash().expect("script is less than 520 bytes");
let p2sh = ScriptPubKeyBuf::new_p2sh(script_hash);
assert!(p2sh.is_p2sh());
assert_eq!(script.to_p2sh().unwrap(), p2sh);
+ let script = Script::builder().push_opcode(OP_NUMEQUAL).push_verify().into_script();
let wscript_hash = script.wscript_hash().expect("script is less than 10,000 bytes");
let p2wsh = ScriptPubKeyBuf::new_p2wsh(wscript_hash);
assert!(p2wsh.is_p2wsh());
@@ -396,11 +397,12 @@ fn non_minimal_scriptints() {
#[test]
fn script_hashes() {
- let script = ScriptBuf::from_hex_no_length_prefix("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap();
+ let script = RedeemScriptBuf::from_hex_no_length_prefix("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap();
assert_eq!(
script.script_hash().unwrap().to_string(),
"8292bcfbef1884f73c813dfe9c82fd7e814291ea"
);
+ let script = ScriptBuf::from_hex_no_length_prefix("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap();
assert_eq!(
script.wscript_hash().unwrap().to_string(),
"3e1525eb183ad4f9b3c5fa3175bdca2a52e947b135bbb90383bf9f6408e2c324"
@@ -631,7 +633,7 @@ fn p2sh_p2wsh_conversion() {
assert_eq!(witness_script.to_p2wsh().unwrap(), expected_without);
// p2sh
- let redeem_script = ScriptBuf::from_hex_no_length_prefix("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8").unwrap();
+ let redeem_script = RedeemScriptBuf::from_hex_no_length_prefix("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8").unwrap();
let expected_p2shout = ScriptPubKeyBuf::from_hex_no_length_prefix(
"a91491b24bf9f5288532960ac687abb035127b1d28a587",
)
@@ -649,7 +651,7 @@ fn p2sh_p2wsh_conversion() {
"a914f386c2ba255cc56d20cfa6ea8b062f8b5994551887",
)
.unwrap();
- assert!(witness_script.to_p2sh().unwrap().is_p2sh());
+ // assert!(witness_script.to_p2sh().unwrap().is_p2sh()); // This is meaningless and no longer compiles
assert_eq!(witness_script.to_p2wsh().unwrap(), expected_without);
assert_eq!(witness_script.to_p2wsh().unwrap().to_p2sh().unwrap(), expected_out);
}
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 92d060c8..53de5e26 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -22,7 +22,7 @@ use io::Write;
use crate::consensus::{encode, Encodable};
use crate::prelude::{Borrow, BorrowMut, String, ToOwned};
-use crate::script::GenericScriptExt as _;
+use crate::script::{GenericScriptExt as _, ScriptHashableTag};
use crate::taproot::{LeafVersion, TapLeafHash, TapLeafTag, TAPROOT_ANNEX_PREFIX};
use crate::transaction::TransactionExt as _;
use crate::witness::Witness;
@@ -871,7 +871,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
///
/// `script_pubkey` is the `scriptPubkey` (native SegWit) of the spend transaction
/// ([`TxOut::script_pubkey`]) or the `redeemScript` (wrapped SegWit).
- pub fn p2wpkh_signature_hash<T>(
+ pub fn p2wpkh_signature_hash<T: ScriptHashableTag>(
&mut self,
input_index: usize,
script_pubkey: &crate::script::GenericScript<T>,
@@ -937,7 +937,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
///
/// This function can't handle the SIGHASH_SINGLE bug internally, so it returns [`EncodeSigningDataResult`]
/// that must be handled by the caller (see [`EncodeSigningDataResult::is_sighash_single_bug`]).
- pub fn legacy_encode_signing_data_to<W: Write + ?Sized, U: Into<u32>, T>(
+ pub fn legacy_encode_signing_data_to<W: Write + ?Sized, U: Into<u32>, T: ScriptHashableTag>(
&self,
writer: &mut W,
input_index: usize,
@@ -962,7 +962,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
return EncodeSigningDataResult::SighashSingleBug;
}
- fn encode_signing_data_to_inner<W: Write + ?Sized, T>(
+ fn encode_signing_data_to_inner<W: Write + ?Sized, T: ScriptHashableTag>(
self_: &Transaction,
writer: &mut W,
input_index: usize,
@@ -1058,7 +1058,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
/// Does NOT attempt to support OP_CODESEPARATOR. In general this would require evaluating
/// `script_pubkey` to determine which separators get evaluated and which don't, which we don't
/// have the information to determine.
- pub fn legacy_signature_hash<T>(
+ pub fn legacy_signature_hash<T: ScriptHashableTag>(
&self,
input_index: usize,
script_pubkey: &crate::script::GenericScript<T>,
@@ -1403,7 +1403,7 @@ impl<E> EncodeSigningDataResult<E> {
/// # use bitcoin::Transaction;
/// # let mut writer = sha256d::Hash::engine();
/// # let input_index = 0;
- /// # let script_pubkey = bitcoin::ScriptBuf::new();
+ /// # let script_pubkey = bitcoin::ScriptPubKeyBuf::new();
/// # let sighash_u32 = 0u32;
/// # const SOME_TX: &'static str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
/// # let raw_tx = Vec::from_hex(SOME_TX).unwrap();
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index dcdb1ea4..e732ff2d 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -152,7 +152,10 @@ pub use primitives::{
},
merkle_tree::{TxMerkleNode, WitnessMerkleNode},
pow::CompactTarget, // No `pow` module outside of `primitives`.
- script::{Script, ScriptBuf, ScriptPubKey, ScriptPubKeyBuf, ScriptSig, ScriptSigBuf},
+ script::{
+ RedeemScript, RedeemScriptBuf, Script, ScriptBuf, ScriptPubKey, ScriptPubKeyBuf, ScriptSig,
+ ScriptSigBuf,
+ },
sequence::{self, Sequence}, // No `sequence` module outside of `primitives`.
transaction::{OutPoint, Transaction, TxIn, TxOut, Txid, Version as TransactionVersion, Wtxid},
witness::Witness,
diff --git a/bitcoin/src/psbt/map/input.rs b/bitcoin/src/psbt/map/input.rs
index bf8679c8..16dfa131 100644
--- a/bitcoin/src/psbt/map/input.rs
+++ b/bitcoin/src/psbt/map/input.rs
@@ -12,7 +12,7 @@ use crate::prelude::{btree_map, BTreeMap, Borrow, Box, ToOwned, Vec};
use crate::psbt::map::Map;
use crate::psbt::serialize::Deserialize;
use crate::psbt::{error, raw, Error};
-use crate::script::{ScriptBuf, ScriptSigBuf};
+use crate::script::{RedeemScriptBuf, ScriptBuf, ScriptSigBuf};
use crate::sighash::{
EcdsaSighashType, InvalidSighashTypeError, NonStandardSighashTypeError, SighashTypeParseError,
TapSighashType,
@@ -83,7 +83,7 @@ pub struct Input {
/// must use the sighash type.
pub sighash_type: Option<PsbtSighashType>,
/// The redeem script for this input.
- pub redeem_script: Option<ScriptBuf>,
+ pub redeem_script: Option<RedeemScriptBuf>,
/// The witness script for this input.
pub witness_script: Option<ScriptBuf>,
/// A map from public keys needed to sign this input to their corresponding
@@ -283,7 +283,7 @@ impl Input {
}
PSBT_IN_REDEEM_SCRIPT => {
impl_psbt_insert_pair! {
- self.redeem_script <= <raw_key: _>|<raw_value: ScriptBuf>
+ self.redeem_script <= <raw_key: _>|<raw_value: RedeemScriptBuf>
}
}
PSBT_IN_WITNESS_SCRIPT => {
diff --git a/bitcoin/src/psbt/map/output.rs b/bitcoin/src/psbt/map/output.rs
index db1e2eeb..13e5543c 100644
--- a/bitcoin/src/psbt/map/output.rs
+++ b/bitcoin/src/psbt/map/output.rs
@@ -5,7 +5,7 @@ use crate::crypto::key::XOnlyPublicKey;
use crate::prelude::{btree_map, BTreeMap, Vec};
use crate::psbt::map::Map;
use crate::psbt::{raw, Error};
-use crate::script::ScriptBuf;
+use crate::script::{RedeemScriptBuf, ScriptBuf};
use crate::taproot::{TapLeafHash, TapTree};
/// Type: Redeem ScriptBuf PSBT_OUT_REDEEM_SCRIPT = 0x00
@@ -30,7 +30,7 @@ const PSBT_OUT_PROPRIETARY: u64 = 0xFC;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
pub struct Output {
/// The redeem script for this output.
- pub redeem_script: Option<ScriptBuf>,
+ pub redeem_script: Option<RedeemScriptBuf>,
/// The witness script for this output.
pub witness_script: Option<ScriptBuf>,
/// A map from public keys needed to spend this output to their
@@ -57,7 +57,7 @@ impl Output {
match raw_key.type_value {
PSBT_OUT_REDEEM_SCRIPT => {
impl_psbt_insert_pair! {
- self.redeem_script <= <raw_key: _>|<raw_value: ScriptBuf>
+ self.redeem_script <= <raw_key: _>|<raw_value: RedeemScriptBuf>
}
}
PSBT_OUT_WITNESS_SCRIPT => {
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 5ad068fa..8956f6ec 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1343,7 +1343,9 @@ mod tests {
use crate::locktime::absolute;
use crate::network::NetworkKind;
use crate::psbt::serialize::{Deserialize, Serialize};
- use crate::script::{GenericScriptBufExt as _, ScriptBuf, ScriptPubKeyBuf, ScriptSigBuf};
+ use crate::script::{
+ GenericScriptBufExt as _, RedeemScriptBuf, ScriptBuf, ScriptPubKeyBuf, ScriptSigBuf,
+ };
use crate::transaction::{self, OutPoint, TxIn};
use crate::witness::Witness;
use crate::Sequence;
@@ -1513,7 +1515,7 @@ mod tests {
let expected: Output = Output {
redeem_script: Some(
- ScriptBuf::from_hex_no_length_prefix(
+ RedeemScriptBuf::from_hex_no_length_prefix(
"76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
)
.unwrap(),
diff --git a/bitcoin/tests/bip_174.rs b/bitcoin/tests/bip_174.rs
index 3a34f996..f99f94e0 100644
--- a/bitcoin/tests/bip_174.rs
+++ b/bitcoin/tests/bip_174.rs
@@ -9,11 +9,11 @@ use bitcoin::consensus::encode::{deserialize, serialize_hex};
use bitcoin::hex::FromHex;
use bitcoin::opcodes::OP_0;
use bitcoin::psbt::{Psbt, PsbtSighashType};
-use bitcoin::script::{GenericScriptBufExt as _, PushBytes};
+use bitcoin::script::{GenericScriptBuf, GenericScriptBufExt as _, PushBytes};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{
- absolute, script, transaction, NetworkKind, OutPoint, PrivateKey, PublicKey, ScriptBuf,
- ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
+ absolute, script, transaction, NetworkKind, OutPoint, PrivateKey, PublicKey, ScriptPubKeyBuf,
+ ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
};
#[track_caller]
@@ -23,8 +23,8 @@ fn hex_psbt(s: &str) -> Psbt {
}
#[track_caller]
-fn hex_script(s: &str) -> ScriptBuf {
- ScriptBuf::from_hex_no_length_prefix(s).expect("valid hex digits")
+fn hex_script<T>(s: &str) -> GenericScriptBuf<T> {
+ GenericScriptBuf::from_hex_no_length_prefix(s).expect("valid hex digits")
}
#[test]
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index e0516195..3e9523d0 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -74,7 +74,10 @@ pub use self::{
block::{
Block, Checked as BlockChecked, Unchecked as BlockUnchecked, Validation as BlockValidation,
},
- script::{Script, ScriptBuf, ScriptPubKey, ScriptPubKeyBuf, ScriptSig, ScriptSigBuf},
+ script::{
+ RedeemScript, RedeemScriptBuf, Script, ScriptBuf, ScriptPubKey, ScriptPubKeyBuf, ScriptSig,
+ ScriptSigBuf,
+ },
transaction::{Transaction, TxIn, TxOut},
witness::Witness,
};
diff --git a/primitives/src/script/mod.rs b/primitives/src/script/mod.rs
index 6ba0fb9b..11ffab1a 100644
--- a/primitives/src/script/mod.rs
+++ b/primitives/src/script/mod.rs
@@ -30,7 +30,7 @@ use crate::prelude::{Borrow, BorrowMut, Box, Cow, ToOwned, Vec};
pub use self::{
borrowed::GenericScript,
owned::GenericScriptBuf,
- tag::{Tag, ScriptPubKeyTag, ScriptSigTag, Whatever},
+ tag::{Tag, RedeemScriptTag, ScriptPubKeyTag, ScriptSigTag, Whatever},
};
/// Placeholder doc (will be replaced in later commit)
@@ -39,6 +39,12 @@ pub type Script = GenericScript<Whatever>;
/// Placeholder doc (will be replaced in later commit)
pub type ScriptBuf = GenericScriptBuf<Whatever>;
+/// A P2SH redeem script.
+pub type RedeemScriptBuf = GenericScriptBuf<RedeemScriptTag>;
+
+/// A reference to a P2SH redeem script.
+pub type RedeemScript = GenericScript<RedeemScriptTag>;
+
/// A reference to a script public key (scriptPubKey).
pub type ScriptPubKey = GenericScript<ScriptPubKeyTag>;
@@ -79,6 +85,27 @@ hashes::impl_debug_only_for_newtype!(ScriptHash, WScriptHash);
#[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(ScriptHash, WScriptHash);
+/// Either a redeem script or a Segwit version 0 scriptpubkey.
+///
+/// In the case of P2SH-wrapped Segwit version outputs, we take a Segwit scriptPubKey
+/// and put it in a redeem script slot. The Bitcoin script interpreter has special
+/// logic to handle this case, which is reflected in our API in several methods
+/// relating to P2SH and signature hashing. These methods take either a normal
+/// P2SH redeem script, or a Segwit version 0 scriptpubkey.
+///
+/// Segwit version 1 (Taproot) and higher do **not** support P2SH-wrapping, and such
+/// scriptPubKeys should not be used with this trait.
+pub trait ScriptHashableTag: sealed::Sealed {}
+
+impl ScriptHashableTag for RedeemScriptTag {}
+impl ScriptHashableTag for ScriptPubKeyTag {}
+
+mod sealed {
+ pub trait Sealed {}
+ impl Sealed for super::RedeemScriptTag {}
+ impl Sealed for super::ScriptPubKeyTag {}
+}
+
impl ScriptHash {
/// Constructs a new `ScriptHash` after first checking the script size.
///
@@ -91,7 +118,10 @@ impl ScriptHash {
///
/// ref: [BIP-16](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size)
#[inline]
- pub fn from_script<T>(redeem_script: &GenericScript<T>) -> Result<Self, RedeemScriptSizeError> {
+ pub fn from_script<T>(redeem_script: &GenericScript<T>) -> Result<Self, RedeemScriptSizeError>
+ where
+ T: ScriptHashableTag,
+ {
if redeem_script.len() > MAX_REDEEM_SCRIPT_SIZE {
return Err(RedeemScriptSizeError { size: redeem_script.len() });
}
@@ -143,29 +173,29 @@ impl WScriptHash {
}
}
-impl TryFrom<ScriptBuf> for ScriptHash {
+impl<T: ScriptHashableTag> TryFrom<GenericScriptBuf<T>> for ScriptHash {
type Error = RedeemScriptSizeError;
#[inline]
- fn try_from(redeem_script: ScriptBuf) -> Result<Self, Self::Error> {
+ fn try_from(redeem_script: GenericScriptBuf<T>) -> Result<Self, Self::Error> {
Self::from_script(&redeem_script)
}
}
-impl TryFrom<&ScriptBuf> for ScriptHash {
+impl<T: ScriptHashableTag> TryFrom<&GenericScriptBuf<T>> for ScriptHash {
type Error = RedeemScriptSizeError;
#[inline]
- fn try_from(redeem_script: &ScriptBuf) -> Result<Self, Self::Error> {
+ fn try_from(redeem_script: &GenericScriptBuf<T>) -> Result<Self, Self::Error> {
Self::from_script(redeem_script)
}
}
-impl TryFrom<&Script> for ScriptHash {
+impl<T: ScriptHashableTag> TryFrom<&GenericScript<T>> for ScriptHash {
type Error = RedeemScriptSizeError;
#[inline]
- fn try_from(redeem_script: &Script) -> Result<Self, Self::Error> {
+ fn try_from(redeem_script: &GenericScript<T>) -> Result<Self, Self::Error> {
Self::from_script(redeem_script)
}
}
@@ -728,10 +758,10 @@ mod tests {
#[test]
fn script_hash_from_script() {
- let script = Script::from_bytes(&[0x51; 520]);
+ let script = RedeemScript::from_bytes(&[0x51; 520]);
assert!(ScriptHash::from_script(script).is_ok());
- let script = Script::from_bytes(&[0x51; 521]);
+ let script = RedeemScript::from_bytes(&[0x51; 521]);
assert!(ScriptHash::from_script(script).is_err());
}
@@ -759,29 +789,29 @@ mod tests {
}
#[test]
- fn try_from_scriptbuf_for_scripthash() {
- let script = ScriptBuf::from(vec![0x51; 520]);
+ fn try_from_scriptpubkeybuf_for_scripthash() {
+ let script = ScriptPubKeyBuf::from(vec![0x51; 520]);
assert!(ScriptHash::try_from(script).is_ok());
- let script = ScriptBuf::from(vec![0x51; 521]);
+ let script = ScriptPubKeyBuf::from(vec![0x51; 521]);
assert!(ScriptHash::try_from(script).is_err());
}
#[test]
- fn try_from_scriptbuf_ref_for_scripthash() {
- let script = ScriptBuf::from(vec![0x51; 520]);
+ fn try_from_scriptpubkeybuf_ref_for_scripthash() {
+ let script = ScriptPubKeyBuf::from(vec![0x51; 520]);
assert!(ScriptHash::try_from(&script).is_ok());
- let script = ScriptBuf::from(vec![0x51; 521]);
+ let script = ScriptPubKeyBuf::from(vec![0x51; 521]);
assert!(ScriptHash::try_from(&script).is_err());
}
#[test]
fn try_from_script_for_scripthash() {
- let script = Script::from_bytes(&[0x51; 520]);
+ let script = RedeemScript::from_bytes(&[0x51; 520]);
assert!(ScriptHash::try_from(script).is_ok());
- let script = Script::from_bytes(&[0x51; 521]);
+ let script = RedeemScript::from_bytes(&[0x51; 521]);
assert!(ScriptHash::try_from(script).is_err());
}
@@ -902,7 +932,7 @@ mod tests {
#[test]
fn redeem_script_size_error() {
- let script = ScriptBuf::from(vec![0x51; 521]);
+ let script = RedeemScriptBuf::from(vec![0x51; 521]);
let result = ScriptHash::try_from(script);
let err = result.unwrap_err();
diff --git a/primitives/src/script/tag.rs b/primitives/src/script/tag.rs
index f4e2f977..82b71fc6 100644
--- a/primitives/src/script/tag.rs
+++ b/primitives/src/script/tag.rs
@@ -14,6 +14,11 @@ pub enum Whatever {}
impl Tag for Whatever {}
+/// A P2SH redeem script.
+#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub enum RedeemScriptTag {}
+impl Tag for RedeemScriptTag {}
+
/// A script signature (scriptSig).
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum ScriptSigTag {}
diff --git a/primitives/tests/api.rs b/primitives/tests/api.rs
index 9a65e501..c572b8f6 100644
--- a/primitives/tests/api.rs
+++ b/primitives/tests/api.rs
@@ -17,9 +17,9 @@ use arbitrary::Arbitrary;
use bitcoin_primitives::block::{Checked, Unchecked};
use bitcoin_primitives::script::{self, ScriptHash, WScriptHash};
use bitcoin_primitives::{
- absolute, block, merkle_tree, pow, relative, transaction, witness, OutPoint, Script, ScriptBuf,
- ScriptPubKey, ScriptPubKeyBuf, ScriptSig, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut,
- Txid, Witness, Wtxid,
+ absolute, block, merkle_tree, pow, relative, transaction, witness, OutPoint, RedeemScript,
+ RedeemScriptBuf, Script, ScriptBuf, ScriptPubKey, ScriptPubKeyBuf, ScriptSig, ScriptSigBuf,
+ Sequence, Transaction, TxIn, TxOut, Txid, Witness, Wtxid,
};
use hashes::sha256t;
@@ -45,11 +45,13 @@ struct Structs<'a> {
h: merkle_tree::WitnessMerkleNode,
i: pow::CompactTarget,
j: &'a Script,
+ j1: &'a RedeemScript,
j2: &'a ScriptPubKey,
j3: &'a ScriptSig,
k: ScriptHash,
l: WScriptHash,
m: ScriptBuf,
+ m1: RedeemScriptBuf,
m2: ScriptPubKeyBuf,
m3: ScriptSigBuf,
n: Sequence,
@@ -65,6 +67,7 @@ struct Structs<'a> {
}
static SCRIPT: ScriptBuf = ScriptBuf::new();
+static REDEEM_SCRIPT: RedeemScriptBuf = RedeemScriptBuf::new();
static SCRIPT_SIG: ScriptSigBuf = ScriptSigBuf::new();
static SCRIPT_PUB_KEY: ScriptPubKeyBuf = ScriptPubKeyBuf::new();
static BYTES: [u8; 32] = [0x00; 32];
@@ -86,6 +89,7 @@ struct CommonTraits {
k: ScriptHash,
l: WScriptHash,
m: ScriptBuf,
+ m1: RedeemScriptBuf,
m2: ScriptPubKeyBuf,
m3: ScriptSigBuf,
n: Sequence,
@@ -116,6 +120,7 @@ struct Clone<'a> {
k: ScriptHash,
l: WScriptHash,
m: ScriptBuf,
+ m1: RedeemScriptBuf,
m2: ScriptPubKeyBuf,
m3: ScriptSigBuf,
n: Sequence,
@@ -147,6 +152,7 @@ struct Ord {
k: ScriptHash,
l: WScriptHash,
m: ScriptBuf,
+ m1: RedeemScriptBuf,
m2: ScriptPubKeyBuf,
m3: ScriptSigBuf,
n: Sequence,
@@ -166,9 +172,11 @@ struct Ord {
struct Default {
a: block::Version,
b: &'static Script,
+ b1: &'static RedeemScript,
b2: &'static ScriptPubKey,
b3: &'static ScriptSig,
c: ScriptBuf,
+ c1: RedeemScriptBuf,
c2: ScriptPubKeyBuf,
c3: ScriptSigBuf,
d: Sequence,
@@ -284,11 +292,13 @@ fn api_all_non_error_types_have_non_empty_debug() {
merkle_tree::WitnessMerkleNode::from_byte_array(BYTES);
pow::CompactTarget::from_consensus(0x1d00_ffff);
SCRIPT.as_script();
+ REDEEM_SCRIPT.as_script();
SCRIPT_SIG.as_script();
SCRIPT_PUB_KEY.as_script();
- ScriptHash::from_script(&SCRIPT).unwrap();
+ ScriptHash::from_script(&REDEEM_SCRIPT).unwrap();
WScriptHash::from_script(&SCRIPT).unwrap();
SCRIPT.clone();
+ REDEEM_SCRIPT.clone();
SCRIPT_SIG.clone();
SCRIPT_PUB_KEY.clone();
Sequence::arbitrary(&mut u).unwrap();
@@ -326,9 +336,11 @@ fn regression_default() {
let want = Default {
a: block::Version::NO_SOFT_FORK_SIGNALLING,
b: Script::from_bytes(&[]),
+ b1: RedeemScript::from_bytes(&[]),
b2: ScriptPubKey::from_bytes(&[]),
b3: ScriptSig::from_bytes(&[]),
c: ScriptBuf::from_bytes(Vec::new()),
+ c1: RedeemScriptBuf::from_bytes(Vec::new()),
c2: ScriptPubKeyBuf::from_bytes(Vec::new()),
c3: ScriptSigBuf::from_bytes(Vec::new()),
d: Sequence::MAX,
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.