diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index 8543de68..e3f5e4b7 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -29,7 +29,7 @@ use std::{error, fmt, io}; #[cfg(feature = "serde")] use serde; -use hash_types::{ScriptHash, WScriptHash}; +use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash}; use blockdata::opcodes; use consensus::{encode, Decodable, Encodable}; use hashes::Hash; @@ -216,6 +216,65 @@ impl Script { /// Creates a new empty script pub fn new() -> Script { Script(vec![].into_boxed_slice()) } + /// Generates P2PK-type of scriptPubkey + pub fn new_p2pk(pubkey: &PublicKey) -> Script { + Builder::new() + .push_key(pubkey) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script() + } + + /// Generates P2PKH-type of scriptPubkey + pub fn new_p2pkh(pubkey_hash: &PubkeyHash) -> Script { + Builder::new() + .push_opcode(opcodes::all::OP_DUP) + .push_opcode(opcodes::all::OP_HASH160) + .push_slice(&pubkey_hash[..]) + .push_opcode(opcodes::all::OP_EQUALVERIFY) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script() + } + + /// Generates P2SH-type of scriptPubkey with a given hash of the redeem script + pub fn new_p2sh(script_hash: &ScriptHash) -> Script { + Builder::new() + .push_opcode(opcodes::all::OP_HASH160) + .push_slice(&script_hash[..]) + .push_opcode(opcodes::all::OP_EQUAL) + .into_script() + } + + /// Generates P2WPKH-type of scriptPubkey + pub fn new_v0_wpkh(pubkey_hash: &WPubkeyHash) -> Script { + Script::new_witness_program(::bech32::u5::try_from_u8(0).unwrap(), &pubkey_hash.to_vec()) + } + + /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script + pub fn new_v0_wsh(script_hash: &WScriptHash) -> Script { + Script::new_witness_program(::bech32::u5::try_from_u8(0).unwrap(), &script_hash.to_vec()) + } + + /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script + pub fn new_witness_program(ver: ::bech32::u5, program: &[u8]) -> Script { + let mut verop = ver.to_u8(); + assert!(verop <= 16); + if verop > 0 { + verop = 0x50 + verop; + } + Builder::new() + .push_opcode(verop.into()) + .push_slice(&program) + .into_script() + } + + /// Generates OP_RETURN-type of scriptPubkey for a given data + pub fn new_op_return(data: &[u8]) -> Script { + Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice(data) + .into_script() + } + /// The length in bytes of the script pub fn len(&self) -> usize { self.0.len() } @@ -233,18 +292,13 @@ impl Script { /// Compute the P2SH output corresponding to this redeem script pub fn to_p2sh(&self) -> Script { - Builder::new().push_opcode(opcodes::all::OP_HASH160) - .push_slice(&ScriptHash::hash(&self.0)[..]) - .push_opcode(opcodes::all::OP_EQUAL) - .into_script() + Script::new_p2sh(&ScriptHash::hash(&self.0)) } /// Compute the P2WSH output corresponding to this witnessScript (aka the "witness redeem /// script") pub fn to_v0_p2wsh(&self) -> Script { - Builder::new().push_int(0) - .push_slice(&WScriptHash::hash(&self.0)[..]) - .into_script() + Script::new_v0_wsh(&WScriptHash::hash(&self.0)) } /// Checks whether a script pubkey is a p2sh output @@ -565,7 +619,7 @@ impl<'a> Iterator for Instructions<'a> { impl Builder { /// Creates a new empty script - pub fn new() -> Builder { + pub fn new() -> Self { Builder(vec![], None) } @@ -771,14 +825,15 @@ impl Decodable for Script { #[cfg(test)] mod test { use std::str::FromStr; - use hashes::hex::FromHex; use super::*; use super::build_scriptint; + use hashes::hex::{FromHex, ToHex}; use consensus::encode::{deserialize, serialize}; use blockdata::opcodes; use util::key::PublicKey; + use util::psbt::serialize::Serialize; #[test] fn script() { @@ -828,6 +883,38 @@ mod test { assert_eq!(&format!("{:x}", script), "76a91416e1ae70ff0fa102905d4af297f6912bda6cce1988ac"); } + #[test] + fn script_generators() { + let pubkey = PublicKey::from_str("0234e6a79c5359c613762d537e0e19d86c77c1666d8c9ab050f23acd198e97f93e").unwrap(); + assert!(Script::new_p2pk(&pubkey).is_p2pk()); + + let pubkey_hash = PubkeyHash::hash(&pubkey.serialize()); + assert!(Script::new_p2pkh(&pubkey_hash).is_p2pkh()); + + let wpubkey_hash = WPubkeyHash::hash(&pubkey.serialize()); + assert!(Script::new_v0_wpkh(&wpubkey_hash).is_v0_p2wpkh()); + + let script = Builder::new().push_opcode(opcodes::all::OP_NUMEQUAL) + .push_verify() + .into_script(); + let script_hash = ScriptHash::hash(&script.serialize()); + let p2sh = Script::new_p2sh(&script_hash); + assert!(p2sh.is_p2sh()); + assert_eq!(script.to_p2sh(), p2sh); + + let wscript_hash = WScriptHash::hash(&script.serialize()); + let p2wsh = Script::new_v0_wsh(&wscript_hash); + assert!(p2wsh.is_v0_p2wsh()); + assert_eq!(script.to_v0_p2wsh(), p2wsh); + + // Test data are taken from the second output of + // 2ccb3a1f745eb4eefcf29391460250adda5fab78aaddb902d25d3cd97d9d8e61 transaction + let data = Vec::::from_hex("aa21a9ed20280f53f2d21663cac89e6bd2ad19edbabb048cda08e73ed19e9268d0afea2a").unwrap(); + let op_return = Script::new_op_return(&data); + assert!(op_return.is_op_return()); + assert_eq!(op_return.to_hex(), "6a24aa21a9ed20280f53f2d21663cac89e6bd2ad19edbabb048cda08e73ed19e9268d0afea2a"); + } + #[test] fn script_builder_verify() { let simple = Builder::new() diff --git a/src/util/address.rs b/src/util/address.rs index ba4311c1..771926ce 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -41,9 +41,7 @@ use std::str::FromStr; use bech32; use hashes::Hash; - use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash}; -use blockdata::opcodes; use blockdata::script; use network::constants::Network; use util::base58; @@ -157,11 +155,11 @@ impl FromStr for AddressType { /// The method used to produce an address #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Payload { - /// pay-to-pkhash address + /// P2PKH address PubkeyHash(PubkeyHash), /// P2SH address ScriptHash(ScriptHash), - /// Segwit address + /// Segwit addresses WitnessProgram { /// The witness program version version: bech32::u5, @@ -200,29 +198,15 @@ impl Payload { /// Generates a script pubkey spending to this [Payload]. pub fn script_pubkey(&self) -> script::Script { match *self { - Payload::PubkeyHash(ref hash) => script::Builder::new() - .push_opcode(opcodes::all::OP_DUP) - .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) - .push_opcode(opcodes::all::OP_EQUALVERIFY) - .push_opcode(opcodes::all::OP_CHECKSIG), - Payload::ScriptHash(ref hash) => script::Builder::new() - .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) - .push_opcode(opcodes::all::OP_EQUAL), + Payload::PubkeyHash(ref hash) => + script::Script::new_p2pkh(hash), + Payload::ScriptHash(ref hash) => + script::Script::new_p2sh(hash), Payload::WitnessProgram { version: ver, program: ref prog, - } => { - assert!(ver.to_u8() <= 16); - let mut verop = ver.to_u8(); - if verop > 0 { - verop += 0x50; - } - script::Builder::new().push_opcode(verop.into()).push_slice(&prog) - } + } => script::Script::new_witness_program(ver, prog) } - .into_script() } }