Merge pull request #497 from paritytech/op_num2bin

Bitcoin Cash: OP_NUM2BIN implementation
This commit is contained in:
Svyatoslav Nikolsky 2018-04-20 09:45:16 +03:00 committed by GitHub
commit 98e37bef7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 40 deletions

1
Cargo.lock generated
View File

@ -1158,7 +1158,6 @@ dependencies = [
"chain 0.1.0",
"display_derive 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"elastic-array 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"primitives 0.1.0",

View File

@ -35,6 +35,7 @@ pub enum Error {
InvalidSplitRange,
InvalidBitwiseOperation,
DivisionByZero,
ImpossibleEncoding,
// CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY
NegativeLocktime,
@ -98,6 +99,7 @@ impl fmt::Display for Error {
Error::InvalidSplitRange => "Invalid OP_SPLIT range".fmt(f),
Error::InvalidBitwiseOperation => "Invalid bitwise operation (check length of inputs)".fmt(f),
Error::DivisionByZero => "Invalid division operation".fmt(f),
Error::ImpossibleEncoding => "The requested encoding is impossible to satisfy".fmt(f),
// CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY
Error::NegativeLocktime => "Negative locktime".fmt(f),

View File

@ -89,8 +89,15 @@ pub struct VerificationFlags {
/// Support OP_MOD opcode
pub verify_mod: bool,
/// Support OP_RIGHT/OP_BIN2NUM opcode
pub verify_right: bool,
/// Support OP_BIN2NUM opcode
///
/// This opcode replaces OP_RIGHT => enabling both OP_BIN2NUM && OP_RIGHT would be an error
pub verify_bin2num: bool,
/// Support OP_NUM2BIN opcode
///
/// This opcode replaces OP_LEFT => enabling both OP_NUM2BIN && OP_LEFT would be an error
pub verify_num2bin: bool,
}
impl VerificationFlags {
@ -169,8 +176,13 @@ impl VerificationFlags {
self
}
pub fn verify_right(mut self, value: bool) -> Self {
self.verify_right = value;
pub fn verify_bin2num(mut self, value: bool) -> Self {
self.verify_bin2num = value;
self
}
pub fn verify_num2bin(mut self, value: bool) -> Self {
self.verify_num2bin = value;
self
}
}

View File

@ -647,11 +647,42 @@ pub fn eval_script(
}
stack.push((v1 % v2).to_bytes());
},
Opcode::OP_RIGHT if flags.verify_right => {
// OP_BIN2NUM replaces OP_RIGHT
Opcode::OP_RIGHT if flags.verify_bin2num => {
let bin = stack.pop()?;
let n = Num::minimally_encode(&bin, 4)?;
stack.push(n.to_bytes());
},
// OP_NUM2BIN replaces OP_LEFT
Opcode::OP_LEFT if flags.verify_num2bin => {
let bin_size = Num::from_slice(&stack.pop()?, flags.verify_minimaldata, 4)?;
if bin_size.is_negative() || bin_size > MAX_SCRIPT_ELEMENT_SIZE.into() {
return Err(Error::PushSize);
}
let bin_size: usize = bin_size.into();
let num = Num::minimally_encode(&stack.pop()?, 4)?;
let mut num = num.to_bytes();
// check if we can fit number into array of bin_size length
if num.len() > bin_size {
return Err(Error::ImpossibleEncoding);
}
// check if we need to extend binary repr with zero-bytes
if num.len() < bin_size {
let sign_byte = num.last_mut().map(|last_byte| {
let sign_byte = *last_byte & 0x80;
*last_byte = *last_byte & 0x7f;
sign_byte
}).unwrap_or(0x00);
num.resize(bin_size - 1, 0x00);
num.push(sign_byte);
}
stack.push(num);
},
Opcode::OP_CAT | Opcode::OP_SUBSTR | Opcode::OP_LEFT | Opcode::OP_RIGHT |
Opcode::OP_INVERT | Opcode::OP_AND | Opcode::OP_OR | Opcode::OP_XOR |
Opcode::OP_2MUL | Opcode::OP_2DIV | Opcode::OP_MUL | Opcode::OP_DIV |
@ -2026,35 +2057,6 @@ mod tests {
basic_test(&script, result, stack);
}
fn test_right(input: &[u8], result: Result<bool, Error>, output: Vec<u8>) {
let script = Builder::default()
.push_bytes(input)
.push_opcode(Opcode::OP_RIGHT)
.into_script();
let stack = if result.is_ok() {
vec![output.into()].into()
} else {
vec![]
}.into();
let flags = VerificationFlags::default()
.verify_right(true);
basic_test_with_flags(&script, &flags, result, stack);
}
#[test]
fn test_right_all() {
use error::Error;
test_right(&[0x02, 0x00, 0x00, 0x00, 0x00], Ok(true), vec![0x02]);
test_right(&[0x05, 0x00, 0x80], Ok(true), vec![0x85]);
test_right(&[0x02, 0x02, 0x02, 0x02, 0x02], Err(Error::NumberOverflow), vec![]);
test_right(&[0x00], Ok(false), vec![]);
test_right(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], Ok(true), vec![0x01]);
test_right(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], Ok(true), vec![0x81]);
test_right(&[0x80], Ok(false), vec![]);
test_right(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], Ok(false), vec![]);
}
#[test]
fn test_within_invalid_stack() {
let script = Builder::default()
@ -3707,4 +3709,138 @@ mod tests {
basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result,
vec![Num::from(3).to_bytes()].into());
}
#[test]
fn op_bin2num_disabled_by_default() {
let script = Builder::default()
.push_num(0.into())
.push_opcode(Opcode::OP_RIGHT)
.into_script();
let result = Err(Error::DisabledOpcode(Opcode::OP_RIGHT));
basic_test_with_flags(&script, &VerificationFlags::default(), result,
vec![].into());
}
#[test]
fn test_bin2num_all() {
fn test_bin2num(input: &[u8], result: Result<bool, Error>, output: Vec<u8>) {
let script = Builder::default()
.push_bytes(input)
.push_opcode(Opcode::OP_RIGHT)
.into_script();
let stack = if result.is_ok() {
vec![output.into()].into()
} else {
vec![]
}.into();
let flags = VerificationFlags::default()
.verify_bin2num(true);
basic_test_with_flags(&script, &flags, result, stack);
}
test_bin2num(&[0x02, 0x00, 0x00, 0x00, 0x00], Ok(true), vec![0x02]);
test_bin2num(&[0x05, 0x00, 0x80], Ok(true), vec![0x85]);
test_bin2num(&[0x02, 0x02, 0x02, 0x02, 0x02], Err(Error::NumberOverflow), vec![]);
test_bin2num(&[0x00], Ok(false), vec![]);
test_bin2num(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], Ok(true), vec![0x01]);
test_bin2num(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], Ok(true), vec![0x81]);
test_bin2num(&[0x80], Ok(false), vec![]);
test_bin2num(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], Ok(false), vec![]);
}
#[test]
fn op_num2bin_disabled_by_default() {
let script = Builder::default()
.push_num(1.into())
.push_num(1.into())
.push_opcode(Opcode::OP_LEFT)
.into_script();
let result = Err(Error::DisabledOpcode(Opcode::OP_LEFT));
basic_test_with_flags(&script, &VerificationFlags::default(), result,
vec![].into());
}
#[test]
fn test_num2bin_all() {
fn test_num2bin(num: &[u8], size: &[u8], result: Result<bool, Error>, output: Vec<u8>) {
let script = Builder::default()
.push_data(num)
.push_data(size)
.push_opcode(Opcode::OP_LEFT)
.into_script();
let stack = if result.is_ok() {
vec![output.into()].into()
} else {
vec![]
}.into();
let flags = VerificationFlags::default()
.verify_num2bin(true);
basic_test_with_flags(&script, &flags, result, stack);
}
fn test_num2bin_num(num: Num, size: Num, result: Result<bool, Error>, output: Vec<u8>) {
test_num2bin(&*num.to_bytes(), &*size.to_bytes(), result, output)
}
test_num2bin_num(256.into(), 1.into(), Err(Error::ImpossibleEncoding), vec![0x00]);
test_num2bin_num(1.into(), (MAX_SCRIPT_ELEMENT_SIZE + 1).into(), Err(Error::PushSize), vec![0x00]);
test_num2bin_num(0.into(), 0.into(), Ok(false), vec![]);
test_num2bin_num(0.into(), 1.into(), Ok(false), vec![0x00]);
test_num2bin_num(0.into(), 7.into(), Ok(false), vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
test_num2bin_num(1.into(), 1.into(), Ok(true), vec![0x01]);
test_num2bin_num((-42).into(), 1.into(), Ok(true), Num::from(-42).to_bytes().to_vec());
test_num2bin_num((-42).into(), 2.into(), Ok(true), vec![0x2a, 0x80]);
test_num2bin_num((-42).into(), 10.into(), Ok(true), vec![0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]);
test_num2bin_num((-42).into(), 520.into(), Ok(true), ::std::iter::once(0x2a)
.chain(::std::iter::repeat(0x00).take(518))
.chain(::std::iter::once(0x80)).collect());
test_num2bin_num((-42).into(), 521.into(), Err(Error::PushSize), vec![]);
test_num2bin_num((-42).into(), (-3).into(), Err(Error::PushSize), vec![]);
test_num2bin(&vec![0xab, 0xcd, 0xef, 0x42, 0x80], &vec![0x04], Ok(true), vec![0xab, 0xcd, 0xef, 0xc2]);
test_num2bin(&vec![0x80], &vec![0x00], Ok(false), vec![]);
test_num2bin(&vec![0x80], &vec![0x03], Ok(false), vec![0x00, 0x00, 0x00]);
}
#[test]
fn test_num_bin_conversions_are_reverse_ops() {
let script = Builder::default()
// convert num2bin
.push_num(123456789.into())
.push_num(8.into())
.push_opcode(Opcode::OP_LEFT)
// and then back bin2num
.push_opcode(Opcode::OP_RIGHT)
// check that numbers are the same
.push_num(123456789.into())
.push_opcode(Opcode::OP_EQUAL)
.into_script();
let flags = VerificationFlags::default()
.verify_num2bin(true)
.verify_bin2num(true);
basic_test_with_flags(&script, &flags, Ok(true), vec![vec![0x01].into()].into());
}
#[test]
fn test_split_cat_are_reverse_ops() {
let script = Builder::default()
// split array
.push_data(&vec![0x01, 0x02, 0x03, 0x04, 0x05])
.push_num(2.into())
.push_opcode(Opcode::OP_SUBSTR)
// and then concat again
.push_opcode(Opcode::OP_CAT)
// check that numbers are the same
.push_data(&vec![0x01, 0x02, 0x03, 0x04, 0x05])
.push_opcode(Opcode::OP_EQUAL)
.into_script();
let flags = VerificationFlags::default()
.verify_concat(true)
.verify_split(true);
basic_test_with_flags(&script, &flags, Ok(true), vec![vec![0x01].into()].into());
}
}

View File

@ -444,8 +444,9 @@ impl Opcode {
OP_XOR if !flags.verify_xor => true,
OP_DIV if !flags.verify_div => true,
OP_MOD if !flags.verify_mod => true,
OP_RIGHT if !flags.verify_right => true,
OP_LEFT | OP_INVERT | OP_2MUL | OP_2DIV |
OP_RIGHT if !flags.verify_bin2num => true,
OP_LEFT if !flags.verify_num2bin => true,
OP_INVERT | OP_2MUL | OP_2DIV |
OP_MUL | OP_LSHIFT | OP_RSHIFT => true,
_ => false,
}

View File

@ -6,7 +6,6 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
elastic-array = "0.6"
parking_lot = "0.4"
log = "0.4"
bit-vec = "0.4"
lru-cache = "0.1"
primitives = { path = "../primitives" }

View File

@ -1,7 +1,5 @@
extern crate elastic_array;
extern crate parking_lot;
#[macro_use]
extern crate log;
extern crate bit_vec;
extern crate lru_cache;
#[macro_use]