From 66eb08aab5dd9253ba3250b212420d6ac3665dda Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 4 Nov 2015 11:04:54 -0600 Subject: [PATCH] [BREAKING CHANGE] Make `script::Builder` implement the actual Builder pattern Rather than having methods taking &mut self, have them consume self and return another Builder, so that methods can be chained. Bump major version number. --- Cargo.toml | 2 +- src/blockdata/constants.rs | 19 ++++++------ src/blockdata/script.rs | 60 +++++++++++++++++++++++--------------- src/util/address.rs | 22 +++++++------- src/util/contracthash.rs | 12 ++++---- 5 files changed, 65 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6f3afce..d4aba7f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bitcoin" -version = "0.3.10" +version = "0.4.0" authors = ["Andrew Poelstra "] license = "CC0-1.0" homepage = "https://github.com/apoelstra/rust-bitcoin/" diff --git a/src/blockdata/constants.rs b/src/blockdata/constants.rs index a474afa5..ab5196b6 100644 --- a/src/blockdata/constants.rs +++ b/src/blockdata/constants.rs @@ -65,24 +65,25 @@ fn bitcoin_genesis_tx() -> Transaction { }; // Inputs - let mut in_script = script::Builder::new(); - in_script.push_scriptint(486604799); - in_script.push_scriptint(4); - in_script.push_slice("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".as_bytes()); + let in_script = script::Builder::new().push_scriptint(486604799) + .push_scriptint(4) + .push_slice("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".as_bytes()) + .into_script(); ret.input.push(TxIn { prev_hash: Default::default(), prev_index: 0xFFFFFFFF, - script_sig: in_script.into_script(), + script_sig: in_script, sequence: MAX_SEQUENCE }); // Outputs - let mut out_script = script::Builder::new(); - out_script.push_slice(&hex_bytes("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap()); - out_script.push_opcode(opcodes::All::OP_CHECKSIG); + let out_script = script::Builder::new() + .push_slice(&hex_bytes("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap()) + .push_opcode(opcodes::All::OP_CHECKSIG) + .into_script(); ret.output.push(TxOut { value: 50 * COIN_VALUE, - script_pubkey: out_script.into_script() + script_pubkey: out_script }); // end diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index 8f7840f6..1492b141 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -2059,8 +2059,7 @@ impl Script { // Compute the section of script that needs to be hashed: everything // from the last CODESEPARATOR, except the signature itself. let mut script = (&self.0[codeseparator_index..]).to_vec(); - let mut remove = Builder::new(); - remove.push_slice(sig_slice); + let remove = Builder::new().push_slice(sig_slice); script_find_and_remove(&mut script, &remove[..]); // Also all of the OP_CODESEPARATORS, even the unevaluated ones script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]); @@ -2110,8 +2109,7 @@ impl Script { // from the last CODESEPARATOR, except the signatures themselves. let mut script = (&self.0[codeseparator_index..]).to_vec(); for sig in &sigs { - let mut remove = Builder::new(); - remove.push_slice(&sig[..]); + let remove = Builder::new().push_slice(&sig[..]); script_find_and_remove(&mut script, &remove[..]); script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]); } @@ -2629,27 +2627,29 @@ impl Builder { /// Adds instructions to push an integer onto the stack. Integers are /// encoded as little-endian signed-magnitude numbers, but there are /// dedicated opcodes to push some small integers. - pub fn push_int(&mut self, data: i64) { + pub fn push_int(mut self, data: i64) -> Builder { // We can special-case -1, 1-16 if data == -1 || (data >= 1 && data <= 16) { self.0.push((data + opcodes::OP_TRUE as i64) as u8); + self } // We can also special-case zero else if data == 0 { self.0.push(opcodes::OP_FALSE as u8); + self } // Otherwise encode it as data - else { self.push_scriptint(data); } + else { self.push_scriptint(data) } } /// Adds instructions to push an integer onto the stack, using the explicit /// encoding regardless of the availability of dedicated opcodes. - pub fn push_scriptint(&mut self, data: i64) { - self.push_slice(&build_scriptint(data)); + pub fn push_scriptint(self, data: i64) -> Builder { + self.push_slice(&build_scriptint(data)) } /// Adds instructions to push some arbitrary data onto the stack - pub fn push_slice(&mut self, data: &[u8]) { + pub fn push_slice(mut self, data: &[u8]) -> Builder { // Start with a PUSH opcode match data.len() { n if n < opcodes::Ordinary::OP_PUSHDATA1 as usize => { self.0.push(n as u8); }, @@ -2673,11 +2673,13 @@ impl Builder { } // Then push the acraw self.0.extend(data.iter().cloned()); + self } /// Adds a single opcode to the script - pub fn push_opcode(&mut self, data: opcodes::All) { + pub fn push_opcode(mut self, data: opcodes::All) -> Builder { self.0.push(data as u8); + self } /// Converts the `Builder` into an unmodifiable `Script` @@ -2773,25 +2775,37 @@ mod test { assert_eq!(&script[..], &comp[..]); // small ints - script.push_int(1); comp.push(82u8); assert_eq!(&script[..], &comp[..]); - script.push_int(0); comp.push(0u8); assert_eq!(&script[..], &comp[..]); - script.push_int(4); comp.push(85u8); assert_eq!(&script[..], &comp[..]); - script.push_int(-1); comp.push(80u8); assert_eq!(&script[..], &comp[..]); + script = script.push_int(1); comp.push(82u8); assert_eq!(&script[..], &comp[..]); + script = script.push_int(0); comp.push(0u8); assert_eq!(&script[..], &comp[..]); + script = script.push_int(4); comp.push(85u8); assert_eq!(&script[..], &comp[..]); + script = script.push_int(-1); comp.push(80u8); assert_eq!(&script[..], &comp[..]); // forced scriptint - script.push_scriptint(4); comp.extend([1u8, 4].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_scriptint(4); comp.extend([1u8, 4].iter().cloned()); assert_eq!(&script[..], &comp[..]); // big ints - script.push_int(17); comp.extend([1u8, 17].iter().cloned()); assert_eq!(&script[..], &comp[..]); - script.push_int(10000); comp.extend([2u8, 16, 39].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_int(17); comp.extend([1u8, 17].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_int(10000); comp.extend([2u8, 16, 39].iter().cloned()); assert_eq!(&script[..], &comp[..]); // notice the sign bit set here, hence the extra zero/128 at the end - script.push_int(10000000); comp.extend([4u8, 128, 150, 152, 0].iter().cloned()); assert_eq!(&script[..], &comp[..]); - script.push_int(-10000000); comp.extend([4u8, 128, 150, 152, 128].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_int(10000000); comp.extend([4u8, 128, 150, 152, 0].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_int(-10000000); comp.extend([4u8, 128, 150, 152, 128].iter().cloned()); assert_eq!(&script[..], &comp[..]); // data - script.push_slice("NRA4VR".as_bytes()); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_slice("NRA4VR".as_bytes()); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(&script[..], &comp[..]); // opcodes - script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]); - script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]); + script = script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]); + script = script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]); + } + + #[test] + fn script_builder() { + // from txid 3bb5e6434c11fb93f64574af5d116736510717f2c595eb45b52c28e31622dfff which was in my mempool when I wrote the test + let script = Builder::new().push_opcode(opcodes::All::OP_DUP) + .push_opcode(opcodes::All::OP_HASH160) + .push_slice(&"16e1ae70ff0fa102905d4af297f6912bda6cce19".from_hex().unwrap()) + .push_opcode(opcodes::All::OP_EQUALVERIFY) + .push_opcode(opcodes::All::OP_CHECKSIG) + .into_script(); + assert_eq!(&format!("{:x}", script), "76a91416e1ae70ff0fa102905d4af297f6912bda6cce1988ac"); } #[test] @@ -2824,7 +2838,7 @@ mod test { let mut script = Builder::new(); assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_ok()); - script.push_opcode(opcodes::All::OP_RETURN); + script = script.push_opcode(opcodes::All::OP_RETURN); assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_err()); } diff --git a/src/util/address.rs b/src/util/address.rs index 934319d9..6623caea 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -76,22 +76,22 @@ impl Address { /// Generates a script pubkey spending to this address #[inline] pub fn script_pubkey(&self) -> script::Script { - let mut script = script::Builder::new(); match self.ty { Type::PubkeyHash => { - script.push_opcode(opcodes::All::OP_DUP); - script.push_opcode(opcodes::All::OP_HASH160); - script.push_slice(&self.hash[..]); - script.push_opcode(opcodes::All::OP_EQUALVERIFY); - script.push_opcode(opcodes::All::OP_CHECKSIG); + script::Builder::new() + .push_opcode(opcodes::All::OP_DUP) + .push_opcode(opcodes::All::OP_HASH160) + .push_slice(&self.hash[..]) + .push_opcode(opcodes::All::OP_EQUALVERIFY) + .push_opcode(opcodes::All::OP_CHECKSIG) } Type::ScriptHash => { - script.push_opcode(opcodes::All::OP_HASH160); - script.push_slice(&self.hash[..]); - script.push_opcode(opcodes::All::OP_EQUAL); + script::Builder::new() + .push_opcode(opcodes::All::OP_HASH160) + .push_slice(&self.hash[..]) + .push_opcode(opcodes::All::OP_EQUAL) } - } - script.into_script() + }.into_script() } } diff --git a/src/util/contracthash.rs b/src/util/contracthash.rs index 31b1c822..3e1f34f7 100644 --- a/src/util/contracthash.rs +++ b/src/util/contracthash.rs @@ -113,14 +113,14 @@ impl Template { let mut key_index = 0; let mut ret = script::Builder::new(); for elem in &self.0 { - match *elem { + ret = match *elem { TemplateElement::Op(opcode) => ret.push_opcode(opcode), TemplateElement::Key => { if key_index == keys.len() { return Err(Error::TooFewKeys(key_index)); } - ret.push_slice(&keys[key_index].serialize_vec(&secp, true)[..]); key_index += 1; + ret.push_slice(&keys[key_index - 1].serialize_vec(&secp, true)[..]) } } } @@ -215,19 +215,19 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec), match instruction { script::Instruction::PushBytes(data) => { let n = data.len(); - match PublicKey::from_slice(&secp, data) { + ret = match PublicKey::from_slice(&secp, data) { Ok(key) => { if n == 65 { return Err(Error::UncompressedKey); } if mode == Mode::SeekingCheckMulti { return Err(Error::ExpectedChecksig); } retkeys.push(key); - ret.push_opcode(opcodes::All::from(PUBKEY)); mode = Mode::CopyingKeys; + ret.push_opcode(opcodes::All::from(PUBKEY)) } Err(_) => { // Arbitrary pushes are only allowed before we've found any keys. // Otherwise we have to wait for a N CHECKSIG pair. match mode { - Mode::SeekingKeys => { ret.push_slice(data); } + Mode::SeekingKeys => { ret.push_slice(data) } Mode::CopyingKeys => { return Err(Error::ExpectedKey); }, Mode::SeekingCheckMulti => { return Err(Error::ExpectedChecksig); } } @@ -257,7 +257,7 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec), // All other opcodes do nothing _ => {} } - ret.push_opcode(op); + ret = ret.push_opcode(op); } script::Instruction::Error(e) => { return Err(Error::Script(e)); } }