Add support for deserializing a script into a contracthash template
This is something Matt's contracthashtool does, so to copy its functionality I need to support it. And it also seems generally useful.
This commit is contained in:
parent
c1f81ea3c3
commit
ed34bb30f1
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "bitcoin"
|
name = "bitcoin"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
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/"
|
||||||
|
|
|
@ -2472,6 +2472,88 @@ impl From<Vec<u8>> for Script {
|
||||||
|
|
||||||
impl_index_newtype!(Script, u8);
|
impl_index_newtype!(Script, u8);
|
||||||
|
|
||||||
|
/// A "parsed opcode" which allows iterating over a Script in a more sensible way
|
||||||
|
pub enum Instruction<'a> {
|
||||||
|
/// Push a bunch of data
|
||||||
|
PushBytes(&'a [u8]),
|
||||||
|
/// Some non-push opcode
|
||||||
|
Op(opcodes::All),
|
||||||
|
/// An opcode we were unable to parse
|
||||||
|
Error(Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over a script returning parsed opcodes
|
||||||
|
pub struct Instructions<'a> {
|
||||||
|
data: &'a [u8]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a Script {
|
||||||
|
type Item = Instruction<'a>;
|
||||||
|
type IntoIter = Instructions<'a>;
|
||||||
|
fn into_iter(self) -> Instructions<'a> { Instructions { data: &self.0[..] } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Instructions<'a> {
|
||||||
|
type Item = Instruction<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Instruction<'a>> {
|
||||||
|
if self.data.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match opcodes::All::from(self.data[0]).classify() {
|
||||||
|
opcodes::Class::PushBytes(n) => {
|
||||||
|
let n = n as usize;
|
||||||
|
if self.data.len() < n + 1 {
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
|
let ret = Some(Instruction::PushBytes(&self.data[1..n+1]));
|
||||||
|
self.data = &self.data[n + 1..];
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => {
|
||||||
|
if self.data.len() < 2 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
||||||
|
let n = match read_uint(&self.data[1..], 1) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => { return Some(Instruction::Error(e)); }
|
||||||
|
};
|
||||||
|
if self.data.len() < n + 2 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
||||||
|
let ret = Some(Instruction::PushBytes(&self.data[2..n+2]));
|
||||||
|
self.data = &self.data[n + 2..];
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => {
|
||||||
|
if self.data.len() < 3 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
||||||
|
let n = match read_uint(&self.data[1..], 2) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => { return Some(Instruction::Error(e)); }
|
||||||
|
};
|
||||||
|
if self.data.len() < n + 3 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
||||||
|
let ret = Some(Instruction::PushBytes(&self.data[3..n + 3]));
|
||||||
|
self.data = &self.data[n + 3..];
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) => {
|
||||||
|
if self.data.len() < 5 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
||||||
|
let n = match read_uint(&self.data[1..], 4) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => { return Some(Instruction::Error(e)); }
|
||||||
|
};
|
||||||
|
if self.data.len() < n + 5 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
||||||
|
let ret = Some(Instruction::PushBytes(&self.data[5..n + 5]));
|
||||||
|
self.data = &self.data[n + 5..];
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
// Everything else we can push right through
|
||||||
|
_ => {
|
||||||
|
let ret = Some(Instruction::Op(opcodes::All::from(self.data[0])));
|
||||||
|
self.data = &self.data[1..];
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
/// Creates a new empty script
|
/// Creates a new empty script
|
||||||
pub fn new() -> Builder { Builder(vec![]) }
|
pub fn new() -> Builder { Builder(vec![]) }
|
||||||
|
@ -2584,7 +2666,8 @@ mod test {
|
||||||
use secp256k1::Secp256k1;
|
use secp256k1::Secp256k1;
|
||||||
use serialize::hex::FromHex;
|
use serialize::hex::FromHex;
|
||||||
|
|
||||||
use super::{Error, Script, Builder, build_scriptint, read_scriptint, read_scriptbool};
|
use super::*;
|
||||||
|
use super::build_scriptint;
|
||||||
use super::MaybeOwned::Owned;
|
use super::MaybeOwned::Owned;
|
||||||
|
|
||||||
use network::serialize::{deserialize, serialize};
|
use network::serialize::{deserialize, serialize};
|
||||||
|
@ -2610,6 +2693,11 @@ mod test {
|
||||||
assert_eq!(script.evaluate(&s, &mut stack, Some((&tx, n)), None), Ok(()));
|
assert_eq!(script.evaluate(&s, &mut stack, Some((&tx, n)), None), Ok(()));
|
||||||
assert!(stack.len() >= 1);
|
assert!(stack.len() >= 1);
|
||||||
assert_eq!(read_scriptbool(&stack.pop().unwrap()[..]), true);
|
assert_eq!(read_scriptbool(&stack.pop().unwrap()[..]), true);
|
||||||
|
for instruction in (&script).into_iter() {
|
||||||
|
if let Instruction::Error(_) = instruction {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,17 +37,30 @@ pub enum Error {
|
||||||
BadTweak(secp256k1::Error),
|
BadTweak(secp256k1::Error),
|
||||||
/// Other secp256k1 related error
|
/// Other secp256k1 related error
|
||||||
Secp(secp256k1::Error),
|
Secp(secp256k1::Error),
|
||||||
|
/// Script parsing error
|
||||||
|
Script(script::Error),
|
||||||
|
/// Encountered an uncompressed key in a script we were deserializing. The
|
||||||
|
/// reserialization will compress it which might be surprising so we call
|
||||||
|
/// this an error.
|
||||||
|
UncompressedKey,
|
||||||
|
/// Expected a public key when deserializing a script, but we got something else.
|
||||||
|
ExpectedKey,
|
||||||
|
/// Expected some sort of CHECKSIG operator when deserializing a script, but
|
||||||
|
/// we got something else.
|
||||||
|
ExpectedChecksig,
|
||||||
/// Did not have enough keys to instantiate a script template
|
/// Did not have enough keys to instantiate a script template
|
||||||
TooFewKeys(usize)
|
TooFewKeys(usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An element of a script template
|
/// An element of a script template
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
enum TemplateElement {
|
enum TemplateElement {
|
||||||
Op(opcodes::All),
|
Op(opcodes::All),
|
||||||
Key
|
Key
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A script template
|
/// A script template
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Template(Vec<TemplateElement>);
|
pub struct Template(Vec<TemplateElement>);
|
||||||
|
|
||||||
impl Template {
|
impl Template {
|
||||||
|
@ -115,12 +128,81 @@ pub fn create_address(secp: &Secp256k1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract the keys and template from a completed script
|
||||||
|
pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>), Error> {
|
||||||
|
let mut ret = script::Builder::new();
|
||||||
|
let mut retkeys = vec![];
|
||||||
|
let secp = Secp256k1::without_caps();
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum Mode {
|
||||||
|
SeekingKeys,
|
||||||
|
CopyingKeys,
|
||||||
|
SeekingCheckMulti
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mode = Mode::SeekingKeys;
|
||||||
|
for instruction in script.into_iter() {
|
||||||
|
match instruction {
|
||||||
|
script::Instruction::PushBytes(data) => {
|
||||||
|
let n = data.len();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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::CopyingKeys => { return Err(Error::ExpectedKey); },
|
||||||
|
Mode::SeekingCheckMulti => { return Err(Error::ExpectedChecksig); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
script::Instruction::Op(op) => {
|
||||||
|
match op.classify() {
|
||||||
|
// CHECKSIG should only come after a list of keys
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIG) |
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIGVERIFY) => {
|
||||||
|
if mode == Mode::SeekingKeys { return Err(Error::ExpectedKey); }
|
||||||
|
mode = Mode::SeekingKeys;
|
||||||
|
}
|
||||||
|
// CHECKMULTISIG should only come after a number
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKMULTISIG) |
|
||||||
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKMULTISIGVERIFY) => {
|
||||||
|
if mode == Mode::SeekingKeys { return Err(Error::ExpectedKey); }
|
||||||
|
if mode == Mode::CopyingKeys { return Err(Error::ExpectedKey); }
|
||||||
|
mode = Mode::SeekingKeys;
|
||||||
|
}
|
||||||
|
// Numbers after keys mean we expect a CHECKMULTISIG.
|
||||||
|
opcodes::Class::PushNum(_) => {
|
||||||
|
if mode == Mode::SeekingCheckMulti { return Err(Error::ExpectedChecksig); }
|
||||||
|
if mode == Mode::CopyingKeys { mode = Mode::SeekingCheckMulti; }
|
||||||
|
}
|
||||||
|
// All other opcodes do nothing
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
ret.push_opcode(op);
|
||||||
|
}
|
||||||
|
script::Instruction::Error(e) => { return Err(Error::Script(e)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((Template::from(&ret[..]), retkeys))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use secp256k1::Secp256k1;
|
use secp256k1::Secp256k1;
|
||||||
use secp256k1::key::PublicKey;
|
use secp256k1::key::PublicKey;
|
||||||
use serialize::hex::FromHex;
|
use serialize::hex::FromHex;
|
||||||
|
|
||||||
|
use blockdata::script::Script;
|
||||||
use network::constants::Network;
|
use network::constants::Network;
|
||||||
use util::base58::ToBase58;
|
use util::base58::ToBase58;
|
||||||
|
|
||||||
|
@ -149,6 +231,19 @@ mod tests {
|
||||||
let addr = create_address(&secp, Network::Testnet, &contract, keys, &alpha_template!()).unwrap();
|
let addr = create_address(&secp, Network::Testnet, &contract, keys, &alpha_template!()).unwrap();
|
||||||
assert_eq!(addr.to_base58check(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr".to_owned());
|
assert_eq!(addr.to_base58check(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn script() {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
let alpha_keys = alpha_keys!(&secp);
|
||||||
|
let alpha_template = alpha_template!();
|
||||||
|
|
||||||
|
let alpha_redeem = Script::from(hex!("55210269992fb441ae56968e5b77d46a3e53b69f136444ae65a94041fc937bdb28d93321021df31471281d4478df85bfce08a10aab82601dca949a79950f8ddf7002bd915a2102174c82021492c2c6dfcbfa4187d10d38bed06afb7fdcd72c880179fddd641ea121033f96e43d72c33327b6a4631ccaa6ea07f0b106c88b9dc71c9000bb6044d5e88a210313d8748790f2a86fb524579b46ce3c68fedd58d2a738716249a9f7d5458a15c221030b632eeb079eb83648886122a04c7bf6d98ab5dfb94cf353ee3e9382a4c2fab02102fb54a7fcaa73c307cfd70f3fa66a2e4247a71858ca731396343ad30c7c4009ce57ae"));
|
||||||
|
let (template, keys) = untemplate(&alpha_redeem).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(keys, alpha_keys);
|
||||||
|
assert_eq!(template, alpha_template);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue