OP_NUM2BIN implementation

This commit is contained in:
Svyatoslav Nikolsky 2018-04-11 17:38:59 +03:00
parent 3b93a247e5
commit 72832bc675
4 changed files with 111 additions and 1 deletions

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

@ -93,6 +93,11 @@ pub struct VerificationFlags {
///
/// 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 {
@ -175,4 +180,9 @@ impl VerificationFlags {
self.verify_bin2num = value;
self
}
pub fn verify_num2bin(mut self, value: bool) -> Self {
self.verify_num2bin = value;
self
}
}

View File

@ -653,6 +653,36 @@ pub fn eval_script(
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 |
@ -3680,6 +3710,17 @@ mod tests {
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>) {
@ -3706,4 +3747,60 @@ mod tests {
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]);
}
}

View File

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