[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.
This commit is contained in:
parent
5e03adc9aa
commit
66eb08aab5
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "bitcoin"
|
name = "bitcoin"
|
||||||
version = "0.3.10"
|
version = "0.4.0"
|
||||||
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
|
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
|
||||||
license = "CC0-1.0"
|
license = "CC0-1.0"
|
||||||
homepage = "https://github.com/apoelstra/rust-bitcoin/"
|
homepage = "https://github.com/apoelstra/rust-bitcoin/"
|
||||||
|
|
|
@ -65,24 +65,25 @@ fn bitcoin_genesis_tx() -> Transaction {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inputs
|
// Inputs
|
||||||
let mut in_script = script::Builder::new();
|
let in_script = script::Builder::new().push_scriptint(486604799)
|
||||||
in_script.push_scriptint(486604799);
|
.push_scriptint(4)
|
||||||
in_script.push_scriptint(4);
|
.push_slice("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".as_bytes())
|
||||||
in_script.push_slice("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".as_bytes());
|
.into_script();
|
||||||
ret.input.push(TxIn {
|
ret.input.push(TxIn {
|
||||||
prev_hash: Default::default(),
|
prev_hash: Default::default(),
|
||||||
prev_index: 0xFFFFFFFF,
|
prev_index: 0xFFFFFFFF,
|
||||||
script_sig: in_script.into_script(),
|
script_sig: in_script,
|
||||||
sequence: MAX_SEQUENCE
|
sequence: MAX_SEQUENCE
|
||||||
});
|
});
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
let mut out_script = script::Builder::new();
|
let out_script = script::Builder::new()
|
||||||
out_script.push_slice(&hex_bytes("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap());
|
.push_slice(&hex_bytes("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap())
|
||||||
out_script.push_opcode(opcodes::All::OP_CHECKSIG);
|
.push_opcode(opcodes::All::OP_CHECKSIG)
|
||||||
|
.into_script();
|
||||||
ret.output.push(TxOut {
|
ret.output.push(TxOut {
|
||||||
value: 50 * COIN_VALUE,
|
value: 50 * COIN_VALUE,
|
||||||
script_pubkey: out_script.into_script()
|
script_pubkey: out_script
|
||||||
});
|
});
|
||||||
|
|
||||||
// end
|
// end
|
||||||
|
|
|
@ -2059,8 +2059,7 @@ impl Script {
|
||||||
// Compute the section of script that needs to be hashed: everything
|
// Compute the section of script that needs to be hashed: everything
|
||||||
// from the last CODESEPARATOR, except the signature itself.
|
// from the last CODESEPARATOR, except the signature itself.
|
||||||
let mut script = (&self.0[codeseparator_index..]).to_vec();
|
let mut script = (&self.0[codeseparator_index..]).to_vec();
|
||||||
let mut remove = Builder::new();
|
let remove = Builder::new().push_slice(sig_slice);
|
||||||
remove.push_slice(sig_slice);
|
|
||||||
script_find_and_remove(&mut script, &remove[..]);
|
script_find_and_remove(&mut script, &remove[..]);
|
||||||
// Also all of the OP_CODESEPARATORS, even the unevaluated ones
|
// Also all of the OP_CODESEPARATORS, even the unevaluated ones
|
||||||
script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]);
|
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.
|
// from the last CODESEPARATOR, except the signatures themselves.
|
||||||
let mut script = (&self.0[codeseparator_index..]).to_vec();
|
let mut script = (&self.0[codeseparator_index..]).to_vec();
|
||||||
for sig in &sigs {
|
for sig in &sigs {
|
||||||
let mut remove = Builder::new();
|
let remove = Builder::new().push_slice(&sig[..]);
|
||||||
remove.push_slice(&sig[..]);
|
|
||||||
script_find_and_remove(&mut script, &remove[..]);
|
script_find_and_remove(&mut script, &remove[..]);
|
||||||
script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]);
|
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
|
/// Adds instructions to push an integer onto the stack. Integers are
|
||||||
/// encoded as little-endian signed-magnitude numbers, but there are
|
/// encoded as little-endian signed-magnitude numbers, but there are
|
||||||
/// dedicated opcodes to push some small integers.
|
/// 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
|
// We can special-case -1, 1-16
|
||||||
if data == -1 || (data >= 1 && data <= 16) {
|
if data == -1 || (data >= 1 && data <= 16) {
|
||||||
self.0.push((data + opcodes::OP_TRUE as i64) as u8);
|
self.0.push((data + opcodes::OP_TRUE as i64) as u8);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
// We can also special-case zero
|
// We can also special-case zero
|
||||||
else if data == 0 {
|
else if data == 0 {
|
||||||
self.0.push(opcodes::OP_FALSE as u8);
|
self.0.push(opcodes::OP_FALSE as u8);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
// Otherwise encode it as data
|
// 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
|
/// Adds instructions to push an integer onto the stack, using the explicit
|
||||||
/// encoding regardless of the availability of dedicated opcodes.
|
/// encoding regardless of the availability of dedicated opcodes.
|
||||||
pub fn push_scriptint(&mut self, data: i64) {
|
pub fn push_scriptint(self, data: i64) -> Builder {
|
||||||
self.push_slice(&build_scriptint(data));
|
self.push_slice(&build_scriptint(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds instructions to push some arbitrary data onto the stack
|
/// 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
|
// Start with a PUSH opcode
|
||||||
match data.len() {
|
match data.len() {
|
||||||
n if n < opcodes::Ordinary::OP_PUSHDATA1 as usize => { self.0.push(n as u8); },
|
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
|
// Then push the acraw
|
||||||
self.0.extend(data.iter().cloned());
|
self.0.extend(data.iter().cloned());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a single opcode to the script
|
/// 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.0.push(data as u8);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the `Builder` into an unmodifiable `Script`
|
/// Converts the `Builder` into an unmodifiable `Script`
|
||||||
|
@ -2773,25 +2775,37 @@ mod test {
|
||||||
assert_eq!(&script[..], &comp[..]);
|
assert_eq!(&script[..], &comp[..]);
|
||||||
|
|
||||||
// small ints
|
// small ints
|
||||||
script.push_int(1); comp.push(82u8); assert_eq!(&script[..], &comp[..]);
|
script = script.push_int(1); comp.push(82u8); assert_eq!(&script[..], &comp[..]);
|
||||||
script.push_int(0); comp.push(0u8); assert_eq!(&script[..], &comp[..]);
|
script = script.push_int(0); comp.push(0u8); assert_eq!(&script[..], &comp[..]);
|
||||||
script.push_int(4); comp.push(85u8); assert_eq!(&script[..], &comp[..]);
|
script = 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(80u8); assert_eq!(&script[..], &comp[..]);
|
||||||
// forced scriptint
|
// 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
|
// big ints
|
||||||
script.push_int(17); comp.extend([1u8, 17].iter().cloned()); assert_eq!(&script[..], &comp[..]);
|
script = 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(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
|
// 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 = 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, 128].iter().cloned()); assert_eq!(&script[..], &comp[..]);
|
||||||
|
|
||||||
// data
|
// 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
|
// opcodes
|
||||||
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.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]
|
#[test]
|
||||||
|
@ -2824,7 +2838,7 @@ mod test {
|
||||||
let mut script = Builder::new();
|
let mut script = Builder::new();
|
||||||
assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_ok());
|
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());
|
assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,22 +76,22 @@ impl Address {
|
||||||
/// Generates a script pubkey spending to this address
|
/// Generates a script pubkey spending to this address
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn script_pubkey(&self) -> script::Script {
|
pub fn script_pubkey(&self) -> script::Script {
|
||||||
let mut script = script::Builder::new();
|
|
||||||
match self.ty {
|
match self.ty {
|
||||||
Type::PubkeyHash => {
|
Type::PubkeyHash => {
|
||||||
script.push_opcode(opcodes::All::OP_DUP);
|
script::Builder::new()
|
||||||
script.push_opcode(opcodes::All::OP_HASH160);
|
.push_opcode(opcodes::All::OP_DUP)
|
||||||
script.push_slice(&self.hash[..]);
|
.push_opcode(opcodes::All::OP_HASH160)
|
||||||
script.push_opcode(opcodes::All::OP_EQUALVERIFY);
|
.push_slice(&self.hash[..])
|
||||||
script.push_opcode(opcodes::All::OP_CHECKSIG);
|
.push_opcode(opcodes::All::OP_EQUALVERIFY)
|
||||||
|
.push_opcode(opcodes::All::OP_CHECKSIG)
|
||||||
}
|
}
|
||||||
Type::ScriptHash => {
|
Type::ScriptHash => {
|
||||||
script.push_opcode(opcodes::All::OP_HASH160);
|
script::Builder::new()
|
||||||
script.push_slice(&self.hash[..]);
|
.push_opcode(opcodes::All::OP_HASH160)
|
||||||
script.push_opcode(opcodes::All::OP_EQUAL);
|
.push_slice(&self.hash[..])
|
||||||
|
.push_opcode(opcodes::All::OP_EQUAL)
|
||||||
}
|
}
|
||||||
}
|
}.into_script()
|
||||||
script.into_script()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,14 +113,14 @@ impl Template {
|
||||||
let mut key_index = 0;
|
let mut key_index = 0;
|
||||||
let mut ret = script::Builder::new();
|
let mut ret = script::Builder::new();
|
||||||
for elem in &self.0 {
|
for elem in &self.0 {
|
||||||
match *elem {
|
ret = match *elem {
|
||||||
TemplateElement::Op(opcode) => ret.push_opcode(opcode),
|
TemplateElement::Op(opcode) => ret.push_opcode(opcode),
|
||||||
TemplateElement::Key => {
|
TemplateElement::Key => {
|
||||||
if key_index == keys.len() {
|
if key_index == keys.len() {
|
||||||
return Err(Error::TooFewKeys(key_index));
|
return Err(Error::TooFewKeys(key_index));
|
||||||
}
|
}
|
||||||
ret.push_slice(&keys[key_index].serialize_vec(&secp, true)[..]);
|
|
||||||
key_index += 1;
|
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<PublicKey>),
|
||||||
match instruction {
|
match instruction {
|
||||||
script::Instruction::PushBytes(data) => {
|
script::Instruction::PushBytes(data) => {
|
||||||
let n = data.len();
|
let n = data.len();
|
||||||
match PublicKey::from_slice(&secp, data) {
|
ret = match PublicKey::from_slice(&secp, data) {
|
||||||
Ok(key) => {
|
Ok(key) => {
|
||||||
if n == 65 { return Err(Error::UncompressedKey); }
|
if n == 65 { return Err(Error::UncompressedKey); }
|
||||||
if mode == Mode::SeekingCheckMulti { return Err(Error::ExpectedChecksig); }
|
if mode == Mode::SeekingCheckMulti { return Err(Error::ExpectedChecksig); }
|
||||||
retkeys.push(key);
|
retkeys.push(key);
|
||||||
ret.push_opcode(opcodes::All::from(PUBKEY));
|
|
||||||
mode = Mode::CopyingKeys;
|
mode = Mode::CopyingKeys;
|
||||||
|
ret.push_opcode(opcodes::All::from(PUBKEY))
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Arbitrary pushes are only allowed before we've found any keys.
|
// Arbitrary pushes are only allowed before we've found any keys.
|
||||||
// Otherwise we have to wait for a N CHECKSIG pair.
|
// Otherwise we have to wait for a N CHECKSIG pair.
|
||||||
match mode {
|
match mode {
|
||||||
Mode::SeekingKeys => { ret.push_slice(data); }
|
Mode::SeekingKeys => { ret.push_slice(data) }
|
||||||
Mode::CopyingKeys => { return Err(Error::ExpectedKey); },
|
Mode::CopyingKeys => { return Err(Error::ExpectedKey); },
|
||||||
Mode::SeekingCheckMulti => { return Err(Error::ExpectedChecksig); }
|
Mode::SeekingCheckMulti => { return Err(Error::ExpectedChecksig); }
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>),
|
||||||
// All other opcodes do nothing
|
// All other opcodes do nothing
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
ret.push_opcode(op);
|
ret = ret.push_opcode(op);
|
||||||
}
|
}
|
||||||
script::Instruction::Error(e) => { return Err(Error::Script(e)); }
|
script::Instruction::Error(e) => { return Err(Error::Script(e)); }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue