Merge pull request #136 from rust-bitcoin/2018-08-minimal-push
script: make Instructions iterator enforce minimal pushes
This commit is contained in:
commit
3f9b003348
|
@ -1,7 +1,37 @@
|
||||||
extern crate bitcoin;
|
extern crate bitcoin;
|
||||||
|
|
||||||
|
use bitcoin::blockdata::script;
|
||||||
|
use bitcoin::network::serialize;
|
||||||
|
|
||||||
fn do_test(data: &[u8]) {
|
fn do_test(data: &[u8]) {
|
||||||
let _: Result<bitcoin::blockdata::script::Script, _> = bitcoin::network::serialize::deserialize(data);
|
let s: Result<script::Script, _> = serialize::deserialize(data);
|
||||||
|
if let Ok(script) = s {
|
||||||
|
let _: Vec<script::Instruction> = script.iter(false).collect();
|
||||||
|
let enforce_min: Vec<script::Instruction> = script.iter(true).collect();
|
||||||
|
|
||||||
|
let mut b = script::Builder::new();
|
||||||
|
for ins in enforce_min {
|
||||||
|
match ins {
|
||||||
|
script::Instruction::Error(_) => return,
|
||||||
|
script::Instruction::Op(op) => { b = b.push_opcode(op); }
|
||||||
|
script::Instruction::PushBytes(bytes) => {
|
||||||
|
// Any one-byte pushes, except -0, which can be interpreted as numbers, should be
|
||||||
|
// reserialized as numbers. (For -1 through 16, this will use special ops; for
|
||||||
|
// others it'll just reserialize them as pushes.)
|
||||||
|
if bytes.len() == 1 && bytes[0] != 0x80 && bytes[0] != 0x00 {
|
||||||
|
if let Ok(num) = script::read_scriptint(bytes) {
|
||||||
|
b = b.push_int(num);
|
||||||
|
} else {
|
||||||
|
b = b.push_slice(bytes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b = b.push_slice(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(b.into_script(), script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "afl")]
|
#[cfg(feature = "afl")]
|
||||||
|
|
|
@ -194,7 +194,7 @@ pub enum All {
|
||||||
OP_PUSHDATA2 = 0x4d,
|
OP_PUSHDATA2 = 0x4d,
|
||||||
/// Read the next 4 bytes as N; push the next N bytes as an array onto the stack
|
/// Read the next 4 bytes as N; push the next N bytes as an array onto the stack
|
||||||
OP_PUSHDATA4 = 0x4e,
|
OP_PUSHDATA4 = 0x4e,
|
||||||
/// Push the array [0x80] onto the stack
|
/// Push the array [0x81] onto the stack
|
||||||
OP_PUSHNUM_NEG1 = 0x4f,
|
OP_PUSHNUM_NEG1 = 0x4f,
|
||||||
/// Synonym for OP_RETURN
|
/// Synonym for OP_RETURN
|
||||||
OP_RESERVED = 0x50,
|
OP_RESERVED = 0x50,
|
||||||
|
|
|
@ -151,6 +151,9 @@ display_from_debug!(Builder);
|
||||||
/// would help you.
|
/// would help you.
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// Something did a non-minimal push; for more information see
|
||||||
|
/// `https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Push_operators`
|
||||||
|
NonMinimalPush,
|
||||||
/// Some opcode expected a parameter, but it was missing or truncated
|
/// Some opcode expected a parameter, but it was missing or truncated
|
||||||
EarlyEndOfScript,
|
EarlyEndOfScript,
|
||||||
/// Tried to read an array off the stack as a number when it was more than 4 bytes
|
/// Tried to read an array off the stack as a number when it was more than 4 bytes
|
||||||
|
@ -180,6 +183,7 @@ impl error::Error for Error {
|
||||||
|
|
||||||
fn description(&self) -> &'static str {
|
fn description(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
|
Error::NonMinimalPush => "non-minimal datapush",
|
||||||
Error::EarlyEndOfScript => "unexpected end of script",
|
Error::EarlyEndOfScript => "unexpected end of script",
|
||||||
Error::NumericOverflow => "numeric overflow (number on stack larger than 4 bytes)",
|
Error::NumericOverflow => "numeric overflow (number on stack larger than 4 bytes)",
|
||||||
#[cfg(feature="bitcoinconsensus")]
|
#[cfg(feature="bitcoinconsensus")]
|
||||||
|
@ -373,6 +377,17 @@ impl Script {
|
||||||
opcodes::All::from(self.0[0]).classify() == opcodes::Class::IllegalOp)
|
opcodes::All::from(self.0[0]).classify() == opcodes::Class::IllegalOp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over the script in the form of `Instruction`s, which are an enum covering
|
||||||
|
/// opcodes, datapushes and errors. At most one error will be returned and then the
|
||||||
|
/// iterator will end. To instead iterate over the script as sequence of bytes, treat
|
||||||
|
/// it as a slice using `script[..]` or convert it to a vector using `into_bytes()`.
|
||||||
|
pub fn iter(&self, enforce_minimal: bool) -> Instructions {
|
||||||
|
Instructions {
|
||||||
|
data: &self.0[..],
|
||||||
|
enforce_minimal: enforce_minimal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature="bitcoinconsensus")]
|
#[cfg(feature="bitcoinconsensus")]
|
||||||
/// verify spend of an input script
|
/// verify spend of an input script
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
|
@ -408,13 +423,8 @@ pub enum Instruction<'a> {
|
||||||
|
|
||||||
/// Iterator over a script returning parsed opcodes
|
/// Iterator over a script returning parsed opcodes
|
||||||
pub struct Instructions<'a> {
|
pub struct Instructions<'a> {
|
||||||
data: &'a [u8]
|
data: &'a [u8],
|
||||||
}
|
enforce_minimal: bool,
|
||||||
|
|
||||||
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> {
|
impl<'a> Iterator for Instructions<'a> {
|
||||||
|
@ -429,41 +439,87 @@ impl<'a> Iterator for Instructions<'a> {
|
||||||
opcodes::Class::PushBytes(n) => {
|
opcodes::Class::PushBytes(n) => {
|
||||||
let n = n as usize;
|
let n = n as usize;
|
||||||
if self.data.len() < n + 1 {
|
if self.data.len() < n + 1 {
|
||||||
|
self.data = &[]; // Kill iterator so that it does not return an infinite stream of errors
|
||||||
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
}
|
}
|
||||||
|
if self.enforce_minimal {
|
||||||
|
if n == 1 && (self.data[1] == 0x81 || (self.data[1] > 0 && self.data[1] <= 16)) {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::NonMinimalPush));
|
||||||
|
}
|
||||||
|
}
|
||||||
let ret = Some(Instruction::PushBytes(&self.data[1..n+1]));
|
let ret = Some(Instruction::PushBytes(&self.data[1..n+1]));
|
||||||
self.data = &self.data[n + 1..];
|
self.data = &self.data[n + 1..];
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => {
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => {
|
||||||
if self.data.len() < 2 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
if self.data.len() < 2 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
let n = match read_uint(&self.data[1..], 1) {
|
let n = match read_uint(&self.data[1..], 1) {
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(e) => { return Some(Instruction::Error(e)); }
|
Err(e) => {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if self.data.len() < n + 2 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
if self.data.len() < n + 2 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
|
if self.enforce_minimal && n < 76 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::NonMinimalPush));
|
||||||
|
}
|
||||||
let ret = Some(Instruction::PushBytes(&self.data[2..n+2]));
|
let ret = Some(Instruction::PushBytes(&self.data[2..n+2]));
|
||||||
self.data = &self.data[n + 2..];
|
self.data = &self.data[n + 2..];
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => {
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => {
|
||||||
if self.data.len() < 3 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
if self.data.len() < 3 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
let n = match read_uint(&self.data[1..], 2) {
|
let n = match read_uint(&self.data[1..], 2) {
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(e) => { return Some(Instruction::Error(e)); }
|
Err(e) => {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if self.data.len() < n + 3 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
if self.enforce_minimal && n < 0x100 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::NonMinimalPush));
|
||||||
|
}
|
||||||
|
if self.data.len() < n + 3 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
let ret = Some(Instruction::PushBytes(&self.data[3..n + 3]));
|
let ret = Some(Instruction::PushBytes(&self.data[3..n + 3]));
|
||||||
self.data = &self.data[n + 3..];
|
self.data = &self.data[n + 3..];
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) => {
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) => {
|
||||||
if self.data.len() < 5 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
if self.data.len() < 5 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
let n = match read_uint(&self.data[1..], 4) {
|
let n = match read_uint(&self.data[1..], 4) {
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(e) => { return Some(Instruction::Error(e)); }
|
Err(e) => {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if self.data.len() < n + 5 { return Some(Instruction::Error(Error::EarlyEndOfScript)); }
|
if self.enforce_minimal && n < 0x10000 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::NonMinimalPush));
|
||||||
|
}
|
||||||
|
if self.data.len() < n + 5 {
|
||||||
|
self.data = &[];
|
||||||
|
return Some(Instruction::Error(Error::EarlyEndOfScript));
|
||||||
|
}
|
||||||
let ret = Some(Instruction::PushBytes(&self.data[5..n + 5]));
|
let ret = Some(Instruction::PushBytes(&self.data[5..n + 5]));
|
||||||
self.data = &self.data[n + 5..];
|
self.data = &self.data[n + 5..];
|
||||||
ret
|
ret
|
||||||
|
@ -787,6 +843,66 @@ mod test {
|
||||||
assert_eq!(redeem_script.to_v0_p2wsh().to_p2sh(), expected_out);
|
assert_eq!(redeem_script.to_v0_p2wsh().to_p2sh(), expected_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iterator() {
|
||||||
|
let zero = hex_script!("00");
|
||||||
|
let zeropush = hex_script!("0100");
|
||||||
|
|
||||||
|
let nonminimal = hex_script!("4c0169b2"); // PUSHDATA1 for no reason
|
||||||
|
let minimal = hex_script!("0169b2"); // minimal
|
||||||
|
let nonminimal_alt = hex_script!("026900b2"); // non-minimal number but minimal push (should be OK)
|
||||||
|
|
||||||
|
let v_zero: Vec<Instruction> = zero.iter(true).collect();
|
||||||
|
let v_zeropush: Vec<Instruction> = zeropush.iter(true).collect();
|
||||||
|
|
||||||
|
let v_min: Vec<Instruction> = minimal.iter(true).collect();
|
||||||
|
let v_nonmin: Vec<Instruction> = nonminimal.iter(true).collect();
|
||||||
|
let v_nonmin_alt: Vec<Instruction> = nonminimal_alt.iter(true).collect();
|
||||||
|
let slop_v_min: Vec<Instruction> = minimal.iter(false).collect();
|
||||||
|
let slop_v_nonmin: Vec<Instruction> = nonminimal.iter(false).collect();
|
||||||
|
let slop_v_nonmin_alt: Vec<Instruction> = nonminimal_alt.iter(false).collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
v_zero,
|
||||||
|
vec![
|
||||||
|
Instruction::PushBytes(&[]),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
v_zeropush,
|
||||||
|
vec![
|
||||||
|
Instruction::PushBytes(&[0]),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
v_min,
|
||||||
|
vec![
|
||||||
|
Instruction::PushBytes(&[105]),
|
||||||
|
Instruction::Op(opcodes::All::OP_NOP3),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
v_nonmin,
|
||||||
|
vec![
|
||||||
|
Instruction::Error(Error::NonMinimalPush),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
v_nonmin_alt,
|
||||||
|
vec![
|
||||||
|
Instruction::PushBytes(&[105, 0]),
|
||||||
|
Instruction::Op(opcodes::All::OP_NOP3),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(v_min, slop_v_min);
|
||||||
|
assert_eq!(v_min, slop_v_nonmin);
|
||||||
|
assert_eq!(v_nonmin_alt, slop_v_nonmin_alt);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature="bitcoinconsensus")]
|
#[cfg(feature="bitcoinconsensus")]
|
||||||
fn test_bitcoinconsensus () {
|
fn test_bitcoinconsensus () {
|
||||||
|
|
|
@ -237,7 +237,7 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mode = Mode::SeekingKeys;
|
let mut mode = Mode::SeekingKeys;
|
||||||
for instruction in script.into_iter() {
|
for instruction in script.iter(false) {
|
||||||
match instruction {
|
match instruction {
|
||||||
script::Instruction::PushBytes(data) => {
|
script::Instruction::PushBytes(data) => {
|
||||||
let n = data.len();
|
let n = data.len();
|
||||||
|
|
Loading…
Reference in New Issue