diff --git a/script/src/error.rs b/script/src/error.rs index 51fd3957..2c42e2ca 100644 --- a/script/src/error.rs +++ b/script/src/error.rs @@ -34,6 +34,7 @@ pub enum Error { UnbalancedConditional, InvalidSplitRange, InvalidBitwiseOperation, + DivisionByZero, // CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY NegativeLocktime, @@ -96,6 +97,7 @@ impl fmt::Display for Error { Error::UnbalancedConditional => "Unbalanced conditional".fmt(f), 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), // CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY Error::NegativeLocktime => "Negative locktime".fmt(f), diff --git a/script/src/flags.rs b/script/src/flags.rs index b119587a..5f8e12cb 100644 --- a/script/src/flags.rs +++ b/script/src/flags.rs @@ -82,6 +82,12 @@ pub struct VerificationFlags { /// Support OP_XOR opcode pub verify_xor: bool, + + /// Support OP_DIV opcode + pub verify_div: bool, + + /// Support OP_MOD opcode + pub verify_mod: bool, } impl VerificationFlags { @@ -149,5 +155,15 @@ impl VerificationFlags { self.verify_xor = value; self } + + pub fn verify_div(mut self, value: bool) -> Self { + self.verify_div = value; + self + } + + pub fn verify_mod(mut self, value: bool) -> Self { + self.verify_mod = value; + self + } } diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 1751c595..548d7e25 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -631,6 +631,23 @@ pub fn eval_script( *byte_to_update = *byte_to_update ^ byte_mask; } }, + Opcode::OP_DIV if flags.verify_div => { + let v1 = Num::from_slice(&stack.pop()?, flags.verify_minimaldata, 4)?; + let v2 = Num::from_slice(&stack.pop()?, flags.verify_minimaldata, 4)?; + if v2.is_zero() { + return Err(Error::DivisionByZero); + } + stack.push((v1 / v2).to_bytes()); + }, + Opcode::OP_MOD if flags.verify_mod => { + let v1 = Num::from_slice(&stack.pop()?, flags.verify_minimaldata, 4)?; + let v2 = Num::from_slice(&stack.pop()?, flags.verify_minimaldata, 4)?; + if v2.is_zero() { + return Err(Error::DivisionByZero); + } + stack.push((v1 % v2).to_bytes()); + + }, 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 | @@ -3459,4 +3476,202 @@ mod tests { basic_test_with_flags(&script, &VerificationFlags::default().verify_xor(true), result, vec![vec![0x62, 0x2e].into()].into()); } + + #[test] + fn op_div_disabled_by_default() { + let script = Builder::default() + .push_num(13.into()) + .push_num(5.into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Err(Error::DisabledOpcode(Opcode::OP_DIV)); + basic_test_with_flags(&script, &VerificationFlags::default(), result, + vec![].into()); + } + + #[test] + fn op_div_num_by_nan_fails() { + // a b OP_DIV -> failure where !isnum(a) or !isnum(b). Both operands must be valid numbers + let script = Builder::default() + .push_opcode(Opcode::OP_1SUB) + .push_num(5.into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Err(Error::InvalidStackOperation); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![].into()); + } + + #[test] + fn op_div_nan_by_num_fails() { + // a b OP_DIV -> failure where !isnum(a) or !isnum(b). Both operands must be valid numbers + let script = Builder::default() + .push_num(5.into()) + .push_opcode(Opcode::OP_1SUB) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Err(Error::InvalidStackOperation); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![].into()); + } + + #[test] + fn op_div_num_by_zero_fails() { + // a 0 OP_DIV -> failure. Division by positive zero (all sizes), negative zero (all sizes), OP_0 + let script = Builder::default() + .push_num(0.into()) + .push_num(5.into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Err(Error::DivisionByZero); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![].into()); + } + + #[test] + fn op_div_negative_by_negative_succeeds() { + let script = Builder::default() + .push_num((-5).into()) + .push_num((-13).into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![Num::from(2).to_bytes()].into()); + } + + #[test] + fn op_div_negative_by_positive_succeeds() { + let script = Builder::default() + .push_num(5.into()) + .push_num((-13).into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![Num::from(-2).to_bytes()].into()); + } + + #[test] + fn op_div_positive_by_negative_succeeds() { + let script = Builder::default() + .push_num((-5).into()) + .push_num(13.into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![Num::from(-2).to_bytes()].into()); + } + + #[test] + fn op_div_positive_by_positive_succeeds() { + let script = Builder::default() + .push_num(5.into()) + .push_num(13.into()) + .push_opcode(Opcode::OP_DIV) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_div(true), result, + vec![Num::from(2).to_bytes()].into()); + } + + #[test] + fn op_mod_disabled_by_default() { + let script = Builder::default() + .push_num(13.into()) + .push_num(5.into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Err(Error::DisabledOpcode(Opcode::OP_MOD)); + basic_test_with_flags(&script, &VerificationFlags::default(), result, + vec![].into()); + } + + #[test] + fn op_mod_num_by_nan_fails() { + // a b OP_MOD -> failure where !isnum(a) or !isnum(b). Both operands must be valid numbers + let script = Builder::default() + .push_opcode(Opcode::OP_1SUB) + .push_num(5.into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Err(Error::InvalidStackOperation); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![].into()); + } + + #[test] + fn op_mod_nan_by_num_fails() { + // a b OP_MOD -> failure where !isnum(a) or !isnum(b). Both operands must be valid numbers + let script = Builder::default() + .push_num(5.into()) + .push_opcode(Opcode::OP_1SUB) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Err(Error::InvalidStackOperation); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![].into()); + } + + #[test] + fn op_mod_num_by_zero_fails() { + // a 0 OP_MOD -> failure. Division by positive zero (all sizes), negative zero (all sizes), OP_0 + let script = Builder::default() + .push_num(0.into()) + .push_num(5.into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Err(Error::DivisionByZero); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![].into()); + } + + #[test] + fn op_mod_negative_by_negative_succeeds() { + let script = Builder::default() + .push_num((-5).into()) + .push_num((-13).into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![Num::from(-3).to_bytes()].into()); + } + + #[test] + fn op_mod_negative_by_positive_succeeds() { + let script = Builder::default() + .push_num(5.into()) + .push_num((-13).into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![Num::from(-3).to_bytes()].into()); + } + + #[test] + fn op_mod_positive_by_negative_succeeds() { + let script = Builder::default() + .push_num((-5).into()) + .push_num(13.into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![Num::from(3).to_bytes()].into()); + } + + #[test] + fn op_mod_positive_by_positive_succeeds() { + let script = Builder::default() + .push_num(5.into()) + .push_num(13.into()) + .push_opcode(Opcode::OP_MOD) + .into_script(); + let result = Ok(true); + basic_test_with_flags(&script, &VerificationFlags::default().verify_mod(true), result, + vec![Num::from(3).to_bytes()].into()); + } } diff --git a/script/src/num.rs b/script/src/num.rs index db80b853..5db18af7 100644 --- a/script/src/num.rs +++ b/script/src/num.rs @@ -210,3 +210,19 @@ impl ops::Neg for Num { (-self.value).into() } } + +impl ops::Div for Num { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + (self.value / rhs.value).into() + } +} + +impl ops::Rem for Num { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + (self.value % rhs.value).into() + } +} diff --git a/script/src/opcode.rs b/script/src/opcode.rs index 4ac74ac3..e12fd511 100644 --- a/script/src/opcode.rs +++ b/script/src/opcode.rs @@ -442,9 +442,10 @@ impl Opcode { OP_AND if !flags.verify_and => true, OP_OR if !flags.verify_or => true, OP_XOR if !flags.verify_xor => true, + OP_DIV if !flags.verify_div => true, + OP_MOD if !flags.verify_mod => true, OP_LEFT | OP_RIGHT | OP_INVERT | OP_2MUL | OP_2DIV | - OP_MUL | OP_DIV | OP_MOD | OP_LSHIFT | - OP_RSHIFT => true, + OP_MUL | OP_LSHIFT | OP_RSHIFT => true, _ => false, } }