Eliminate `ScriptNum` (#208)
* Move `set_vch` to simplify later diffs This makes no changes other than swapping the positions of the `set_vch` and `serialize` operations on `ScriptNum`. * Extract integer ↔︎ `Vec<u8>` `fn`s from `ScriptNum` __NB__: I recommend ignoring whitespace when reviewing this commit. `ScriptNum` was used one of three ways: 1. convert a Rust integer to a stack value 2. load a stack value into Rust 3. do arithmetic on stack values Combining these into one interface added extra complexity. For example, `getint()` would clamp the value to `i32` range, but that range could only be violated if arithmetic was performed on `ScriptNum`s, and `getint` was never used on the result of arithmetic. This extracts `parse_num` and `serialize_num` changing the three patterns as follows: 1. `ScriptNum::from(_).getvch()` is now `serialize_num(_)`, and 2. `ScriptNum::new(_).and_then(getint())` is now `parse_num(_)`, 3. `ScriptNum::new(_)` … `getvch()` remains the same. * Make `ScriptNum` a process We never held a `ScriptNum` – we create one from the stack, perform an operation, and serialize the result. This eliminates the type in favor of operations that take a closure on `i64`s. The operations that exist can’t possibly hit the various bounds that were checked on `ScriptNum` operations, so they were removed. As part of the above work, this introduces `cast_from_bool`, which is then used to replace all instances of `VCH_TRUE`/`FALSE`.
This commit is contained in:
parent
cc157ffdce
commit
d3e242b47f
|
@ -1,4 +1,7 @@
|
|||
use std::slice::Iter;
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
slice::Iter,
|
||||
};
|
||||
|
||||
use ripemd::Ripemd160;
|
||||
use sha1::Sha1;
|
||||
|
@ -134,7 +137,7 @@ pub trait SignatureChecker {
|
|||
false
|
||||
}
|
||||
|
||||
fn check_lock_time(&self, _lock_time: &ScriptNum) -> bool {
|
||||
fn check_lock_time(&self, _lock_time: i64) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +149,9 @@ impl SignatureChecker for BaseSignatureChecker {}
|
|||
#[derive(Copy, Clone)]
|
||||
pub struct CallbackTransactionSignatureChecker<'a> {
|
||||
pub sighash: SighashCalculator<'a>,
|
||||
pub lock_time: ScriptNum,
|
||||
/// This is stored as an `i64` instead of the `u32` used by transactions to avoid partial
|
||||
/// conversions when reading from the stack.
|
||||
pub lock_time: i64,
|
||||
pub is_final: bool,
|
||||
}
|
||||
|
||||
|
@ -459,8 +464,50 @@ fn check_minimal_push(data: &[u8], opcode: PushValue) -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
const VCH_FALSE: ValType = Vec::new();
|
||||
const VCH_TRUE: [u8; 1] = [1];
|
||||
fn unop<T: Clone>(
|
||||
stack: &mut Stack<T>,
|
||||
op: impl Fn(T) -> Result<T, ScriptError>,
|
||||
) -> Result<(), ScriptError> {
|
||||
let item = stack.pop()?;
|
||||
op(item).map(|res| stack.push_back(res))
|
||||
}
|
||||
|
||||
fn binfn<T: Clone, R>(
|
||||
stack: &mut Stack<T>,
|
||||
op: impl Fn(T, T) -> Result<R, ScriptError>,
|
||||
) -> Result<R, ScriptError> {
|
||||
let x2 = stack.pop()?;
|
||||
let x1 = stack.pop()?;
|
||||
op(x1, x2)
|
||||
}
|
||||
|
||||
fn binbasic_num<R>(
|
||||
stack: &mut Stack<Vec<u8>>,
|
||||
require_minimal: bool,
|
||||
op: impl Fn(i64, i64) -> Result<R, ScriptError>,
|
||||
) -> Result<R, ScriptError> {
|
||||
binfn(stack, |x1, x2| {
|
||||
let bn2 = parse_num(&x2, require_minimal, None).map_err(ScriptError::ScriptNumError)?;
|
||||
let bn1 = parse_num(&x1, require_minimal, None).map_err(ScriptError::ScriptNumError)?;
|
||||
op(bn1, bn2)
|
||||
})
|
||||
}
|
||||
fn binop<T: Clone>(
|
||||
stack: &mut Stack<T>,
|
||||
op: impl Fn(T, T) -> Result<T, ScriptError>,
|
||||
) -> Result<(), ScriptError> {
|
||||
binfn(stack, op).map(|res| stack.push_back(res))
|
||||
}
|
||||
|
||||
fn cast_from_bool(b: bool) -> ValType {
|
||||
static VCH_FALSE: [u8; 0] = [];
|
||||
static VCH_TRUE: [u8; 1] = [1];
|
||||
if b {
|
||||
VCH_TRUE.to_vec()
|
||||
} else {
|
||||
VCH_FALSE.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct State {
|
||||
|
@ -535,6 +582,39 @@ pub fn eval_step<'a>(
|
|||
let vexec = &mut state.vexec;
|
||||
let altstack = &mut state.altstack;
|
||||
|
||||
let unfn_num =
|
||||
|stackin: &mut Stack<Vec<u8>>, op: &dyn Fn(i64) -> Vec<u8>| -> Result<(), ScriptError> {
|
||||
unop(stackin, |vch| {
|
||||
parse_num(&vch, require_minimal, None)
|
||||
.map_err(ScriptError::ScriptNumError)
|
||||
.map(op)
|
||||
})
|
||||
};
|
||||
|
||||
let unop_num = |stack: &mut Stack<Vec<u8>>,
|
||||
op: &dyn Fn(i64) -> i64|
|
||||
-> Result<(), ScriptError> { unfn_num(stack, &|bn| serialize_num(op(bn))) };
|
||||
|
||||
let binfn_num =
|
||||
|stack: &mut Stack<Vec<u8>>, op: &dyn Fn(i64, i64) -> Vec<u8>| -> Result<(), ScriptError> {
|
||||
binbasic_num(stack, require_minimal, |bn1, bn2| Ok(op(bn1, bn2)))
|
||||
.map(|res| stack.push_back(res))
|
||||
};
|
||||
|
||||
let binop_num =
|
||||
|stack: &mut Stack<Vec<u8>>, op: &dyn Fn(i64, i64) -> i64| -> Result<(), ScriptError> {
|
||||
binfn_num(stack, &|bn1, bn2| serialize_num(op(bn1, bn2)))
|
||||
};
|
||||
|
||||
let binrel =
|
||||
|stack: &mut Stack<Vec<u8>>, op: &dyn Fn(i64, i64) -> bool| -> Result<(), ScriptError> {
|
||||
binfn_num(stack, &|bn1, bn2| cast_from_bool(op(bn1, bn2)))
|
||||
};
|
||||
|
||||
let unrel = |stack: &mut Stack<Vec<u8>>, op: &dyn Fn(i64) -> bool| -> Result<(), ScriptError> {
|
||||
unfn_num(stack, &|bn| cast_from_bool(op(bn)))
|
||||
};
|
||||
|
||||
// Are we in an executing branch of the script?
|
||||
let exec = vexec.iter().all(|value| *value);
|
||||
|
||||
|
@ -556,8 +636,8 @@ pub fn eval_step<'a>(
|
|||
OP_1NEGATE | OP_1 | OP_2 | OP_3 | OP_4 | OP_5 | OP_6 | OP_7 | OP_8 | OP_9
|
||||
| OP_10 | OP_11 | OP_12 | OP_13 | OP_14 | OP_15 | OP_16 => {
|
||||
// ( -- value)
|
||||
let bn = ScriptNum::from(u8::from(pv)) - u8::from(OP_RESERVED).into();
|
||||
stack.push_back(bn.getvch());
|
||||
let bn = i64::from(u8::from(pv)) - i64::from(u8::from(OP_RESERVED));
|
||||
stack.push_back(serialize_num(bn));
|
||||
// The result of these opcodes should always be the minimal way to push the data
|
||||
// they push, so no need for a CheckMinimalPush here.
|
||||
}
|
||||
|
@ -642,18 +722,17 @@ pub fn eval_step<'a>(
|
|||
// Thus as a special case we tell `ScriptNum` to accept up
|
||||
// to 5-byte bignums, which are good until 2**39-1, well
|
||||
// beyond the 2**32-1 limit of the `lock_time` field itself.
|
||||
let lock_time =
|
||||
ScriptNum::new(stack.top(-1)?, require_minimal, Some(5))?;
|
||||
let lock_time = parse_num(stack.top(-1)?, require_minimal, Some(5))?;
|
||||
|
||||
// In the rare event that the argument may be < 0 due to
|
||||
// some arithmetic being done first, you can always use
|
||||
// 0 MAX CHECKLOCKTIMEVERIFY.
|
||||
if lock_time < ScriptNum::ZERO {
|
||||
if lock_time < 0 {
|
||||
return set_error(ScriptError::NegativeLockTime);
|
||||
}
|
||||
|
||||
// Actually compare the specified lock time with the transaction.
|
||||
if !checker.check_lock_time(&lock_time) {
|
||||
if !checker.check_lock_time(lock_time) {
|
||||
return set_error(ScriptError::UnsatisfiedLockTime);
|
||||
}
|
||||
}
|
||||
|
@ -813,9 +892,8 @@ pub fn eval_step<'a>(
|
|||
|
||||
OP_DEPTH => {
|
||||
// -- stacksize
|
||||
let bn = ScriptNum::try_from(stack.size())
|
||||
.map_err(|_| ScriptError::StackSize)?;
|
||||
stack.push_back(bn.getvch())
|
||||
let bn = i64::try_from(stack.size()).map_err(|_| ScriptError::StackSize)?;
|
||||
stack.push_back(serialize_num(bn))
|
||||
}
|
||||
|
||||
OP_DROP => {
|
||||
|
@ -859,9 +937,8 @@ pub fn eval_step<'a>(
|
|||
if stack.size() < 2 {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
}
|
||||
let n =
|
||||
u16::try_from(ScriptNum::new(stack.top(-1)?, require_minimal, None)?)
|
||||
.map_err(|_| ScriptError::InvalidStackOperation)?;
|
||||
let n = u16::try_from(parse_num(stack.top(-1)?, require_minimal, None)?)
|
||||
.map_err(|_| ScriptError::InvalidStackOperation)?;
|
||||
stack.pop()?;
|
||||
if usize::from(n) >= stack.size() {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
|
@ -912,139 +989,70 @@ pub fn eval_step<'a>(
|
|||
if stack.size() < 1 {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
}
|
||||
let bn = ScriptNum::try_from(stack.top(-1)?.len())
|
||||
.expect("stack element size fits in ScriptNum");
|
||||
stack.push_back(bn.getvch())
|
||||
let bn = i64::try_from(stack.top(-1)?.len())
|
||||
.expect("stack element size <= MAX_SCRIPT_ELEMENT_SIZE");
|
||||
stack.push_back(serialize_num(bn))
|
||||
}
|
||||
|
||||
//
|
||||
// Bitwise logic
|
||||
//
|
||||
OP_EQUAL | OP_EQUALVERIFY => {
|
||||
// (x1 x2 - bool)
|
||||
if stack.size() < 2 {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
// (x1 x2 - bool)
|
||||
OP_EQUAL => binop(stack, |x1, x2| Ok(cast_from_bool(x1 == x2)))?,
|
||||
OP_EQUALVERIFY => binfn(stack, |x1, x2| {
|
||||
if x1 == x2 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ScriptError::EqualVerify)
|
||||
}
|
||||
let vch1 = stack.top(-2)?.clone();
|
||||
let vch2 = stack.top(-1)?.clone();
|
||||
let equal = vch1 == vch2;
|
||||
stack.pop()?;
|
||||
stack.pop()?;
|
||||
stack.push_back(if equal { VCH_TRUE.to_vec() } else { VCH_FALSE });
|
||||
if op == OP_EQUALVERIFY {
|
||||
if equal {
|
||||
stack.pop()?;
|
||||
} else {
|
||||
return set_error(ScriptError::EqualVerify);
|
||||
}
|
||||
}
|
||||
}
|
||||
})?,
|
||||
|
||||
//
|
||||
// Numeric
|
||||
//
|
||||
OP_1ADD | OP_1SUB | OP_NEGATE | OP_ABS | OP_NOT | OP_0NOTEQUAL => {
|
||||
// (in -- out)
|
||||
if stack.size() < 1 {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
}
|
||||
let mut bn = ScriptNum::new(stack.top(-1)?, require_minimal, None)?;
|
||||
match op {
|
||||
OP_1ADD => bn = bn + ScriptNum::ONE,
|
||||
OP_1SUB => bn = bn - ScriptNum::ONE,
|
||||
OP_NEGATE => bn = -bn,
|
||||
OP_ABS => {
|
||||
if bn < ScriptNum::ZERO {
|
||||
bn = -bn
|
||||
}
|
||||
}
|
||||
OP_NOT => bn = ScriptNum::from(bn == ScriptNum::ZERO),
|
||||
OP_0NOTEQUAL => bn = ScriptNum::from(bn != ScriptNum::ZERO),
|
||||
_ => panic!("invalid opcode"),
|
||||
}
|
||||
stack.pop()?;
|
||||
stack.push_back(bn.getvch())
|
||||
}
|
||||
|
||||
OP_ADD
|
||||
| OP_SUB
|
||||
| OP_BOOLAND
|
||||
| OP_BOOLOR
|
||||
| OP_NUMEQUAL
|
||||
| OP_NUMEQUALVERIFY
|
||||
| OP_NUMNOTEQUAL
|
||||
| OP_LESSTHAN
|
||||
| OP_GREATERTHAN
|
||||
| OP_LESSTHANOREQUAL
|
||||
| OP_GREATERTHANOREQUAL
|
||||
| OP_MIN
|
||||
| OP_MAX => {
|
||||
// (x1 x2 -- out)
|
||||
if stack.size() < 2 {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
// (in -- out)
|
||||
OP_1ADD => unop_num(stack, &|x| x + 1)?,
|
||||
OP_1SUB => unop_num(stack, &|x| x - 1)?,
|
||||
OP_NEGATE => unop_num(stack, &|x| -x)?,
|
||||
OP_ABS => unop_num(stack, &|x| x.abs())?,
|
||||
OP_NOT => unrel(stack, &|x| x == 0)?,
|
||||
OP_0NOTEQUAL => unrel(stack, &|x| x != 0)?,
|
||||
|
||||
// (x1 x2 -- out)
|
||||
OP_ADD => binop_num(stack, &|x1, x2| x1 + x2)?,
|
||||
OP_SUB => binop_num(stack, &|x1, x2| x1 - x2)?,
|
||||
OP_BOOLAND => binrel(stack, &|x1, x2| x1 != 0 && x2 != 0)?,
|
||||
OP_BOOLOR => binrel(stack, &|x1, x2| x1 != 0 || x2 != 0)?,
|
||||
OP_NUMEQUAL => binrel(stack, &|x1, x2| x1 == x2)?,
|
||||
OP_NUMEQUALVERIFY => binbasic_num(stack, require_minimal, |x1, x2| {
|
||||
if x1 == x2 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ScriptError::NumEqualVerify)
|
||||
}
|
||||
let bn1 = ScriptNum::new(stack.top(-2)?, require_minimal, None)?;
|
||||
let bn2 = ScriptNum::new(stack.top(-1)?, require_minimal, None)?;
|
||||
let bn = match op {
|
||||
OP_ADD => bn1 + bn2,
|
||||
|
||||
OP_SUB => bn1 - bn2,
|
||||
|
||||
OP_BOOLAND => {
|
||||
ScriptNum::from(bn1 != ScriptNum::ZERO && bn2 != ScriptNum::ZERO)
|
||||
}
|
||||
OP_BOOLOR => {
|
||||
ScriptNum::from(bn1 != ScriptNum::ZERO || bn2 != ScriptNum::ZERO)
|
||||
}
|
||||
OP_NUMEQUAL => ScriptNum::from(bn1 == bn2),
|
||||
OP_NUMEQUALVERIFY => ScriptNum::from(bn1 == bn2),
|
||||
OP_NUMNOTEQUAL => ScriptNum::from(bn1 != bn2),
|
||||
OP_LESSTHAN => ScriptNum::from(bn1 < bn2),
|
||||
OP_GREATERTHAN => ScriptNum::from(bn1 > bn2),
|
||||
OP_LESSTHANOREQUAL => ScriptNum::from(bn1 <= bn2),
|
||||
OP_GREATERTHANOREQUAL => ScriptNum::from(bn1 >= bn2),
|
||||
OP_MIN => {
|
||||
if bn1 < bn2 {
|
||||
bn1
|
||||
} else {
|
||||
bn2
|
||||
}
|
||||
}
|
||||
OP_MAX => {
|
||||
if bn1 > bn2 {
|
||||
bn1
|
||||
} else {
|
||||
bn2
|
||||
}
|
||||
}
|
||||
_ => panic!("invalid opcode"),
|
||||
};
|
||||
stack.pop()?;
|
||||
stack.pop()?;
|
||||
stack.push_back(bn.getvch());
|
||||
|
||||
if op == OP_NUMEQUALVERIFY {
|
||||
if cast_to_bool(stack.top(-1)?) {
|
||||
stack.pop()?;
|
||||
} else {
|
||||
return set_error(ScriptError::NumEqualVerify);
|
||||
}
|
||||
}
|
||||
}
|
||||
})?,
|
||||
OP_NUMNOTEQUAL => binrel(stack, &|x1, x2| x1 != x2)?,
|
||||
OP_LESSTHAN => binrel(stack, &|x1, x2| x1 < x2)?,
|
||||
OP_GREATERTHAN => binrel(stack, &|x1, x2| x1 > x2)?,
|
||||
OP_LESSTHANOREQUAL => binrel(stack, &|x1, x2| x1 <= x2)?,
|
||||
OP_GREATERTHANOREQUAL => binrel(stack, &|x1, x2| x1 >= x2)?,
|
||||
OP_MIN => binop_num(stack, &min)?,
|
||||
OP_MAX => binop_num(stack, &max)?,
|
||||
|
||||
OP_WITHIN => {
|
||||
// (x min max -- out)
|
||||
if stack.size() < 3 {
|
||||
return set_error(ScriptError::InvalidStackOperation);
|
||||
}
|
||||
let bn1 = ScriptNum::new(stack.top(-3)?, require_minimal, None)?;
|
||||
let bn2 = ScriptNum::new(stack.top(-2)?, require_minimal, None)?;
|
||||
let bn3 = ScriptNum::new(stack.top(-1)?, require_minimal, None)?;
|
||||
let bn1 = parse_num(stack.top(-3)?, require_minimal, None)?;
|
||||
let bn2 = parse_num(stack.top(-2)?, require_minimal, None)?;
|
||||
let bn3 = parse_num(stack.top(-1)?, require_minimal, None)?;
|
||||
let value = bn2 <= bn1 && bn1 < bn3;
|
||||
stack.pop()?;
|
||||
stack.pop()?;
|
||||
stack.pop()?;
|
||||
stack.push_back(if value { VCH_TRUE.to_vec() } else { VCH_FALSE })
|
||||
stack.push_back(cast_from_bool(value))
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -1089,11 +1097,7 @@ pub fn eval_step<'a>(
|
|||
|
||||
stack.pop()?;
|
||||
stack.pop()?;
|
||||
stack.push_back(if success {
|
||||
VCH_TRUE.to_vec()
|
||||
} else {
|
||||
VCH_FALSE
|
||||
});
|
||||
stack.push_back(cast_from_bool(success));
|
||||
if op == OP_CHECKSIGVERIFY {
|
||||
if success {
|
||||
stack.pop()?;
|
||||
|
@ -1114,7 +1118,7 @@ pub fn eval_step<'a>(
|
|||
return set_error(ScriptError::InvalidStackOperation);
|
||||
};
|
||||
|
||||
let mut keys_count = u8::try_from(ScriptNum::new(
|
||||
let mut keys_count = u8::try_from(parse_num(
|
||||
stack.top(-isize::from(i))?,
|
||||
require_minimal,
|
||||
None,
|
||||
|
@ -1135,7 +1139,7 @@ pub fn eval_step<'a>(
|
|||
return set_error(ScriptError::InvalidStackOperation);
|
||||
}
|
||||
|
||||
let mut sigs_count = u8::try_from(ScriptNum::new(
|
||||
let mut sigs_count = u8::try_from(parse_num(
|
||||
stack.top(-isize::from(i))?,
|
||||
require_minimal,
|
||||
None,
|
||||
|
@ -1202,11 +1206,7 @@ pub fn eval_step<'a>(
|
|||
}
|
||||
stack.pop()?;
|
||||
|
||||
stack.push_back(if success {
|
||||
VCH_TRUE.to_vec()
|
||||
} else {
|
||||
VCH_FALSE
|
||||
});
|
||||
stack.push_back(cast_from_bool(success));
|
||||
|
||||
if op == OP_CHECKMULTISIGVERIFY {
|
||||
if success {
|
||||
|
@ -1332,7 +1332,7 @@ impl SignatureChecker for CallbackTransactionSignatureChecker<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_lock_time(&self, lock_time: &ScriptNum) -> bool {
|
||||
fn check_lock_time(&self, lock_time: i64) -> bool {
|
||||
// There are two kinds of nLockTime: lock-by-blockheight
|
||||
// and lock-by-blocktime, distinguished by whether
|
||||
// nLockTime < LOCKTIME_THRESHOLD.
|
||||
|
@ -1340,11 +1340,11 @@ impl SignatureChecker for CallbackTransactionSignatureChecker<'_> {
|
|||
// We want to compare apples to apples, so fail the script
|
||||
// unless the type of nLockTime being tested is the same as
|
||||
// the nLockTime in the transaction.
|
||||
if self.lock_time < LOCKTIME_THRESHOLD && *lock_time >= LOCKTIME_THRESHOLD
|
||||
|| self.lock_time >= LOCKTIME_THRESHOLD && *lock_time < LOCKTIME_THRESHOLD
|
||||
if self.lock_time < LOCKTIME_THRESHOLD && lock_time >= LOCKTIME_THRESHOLD
|
||||
|| self.lock_time >= LOCKTIME_THRESHOLD && lock_time < LOCKTIME_THRESHOLD
|
||||
// Now that we know we're comparing apples-to-apples, the
|
||||
// comparison is a simple numeric one.
|
||||
|| *lock_time > self.lock_time
|
||||
|| lock_time > self.lock_time
|
||||
{
|
||||
false
|
||||
// Finally the nLockTime feature can be disabled and thus
|
||||
|
|
289
src/script.rs
289
src/script.rs
|
@ -1,10 +1,5 @@
|
|||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::{
|
||||
num::TryFromIntError,
|
||||
ops::{Add, Neg, Sub},
|
||||
};
|
||||
|
||||
use enum_primitive::FromPrimitive;
|
||||
|
||||
use super::script_error::*;
|
||||
|
@ -16,7 +11,7 @@ pub const MAX_SCRIPT_SIZE: usize = 10000;
|
|||
|
||||
// Threshold for lock_time: below this value it is interpreted as block number,
|
||||
// otherwise as UNIX timestamp.
|
||||
pub const LOCKTIME_THRESHOLD: ScriptNum = ScriptNum(500000000); // Tue Nov 5 00:53:20 1985 UTC
|
||||
pub const LOCKTIME_THRESHOLD: i64 = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
/** Script opcodes */
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
|
@ -265,219 +260,113 @@ impl From<Operation> for u8 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct ScriptNum(i64);
|
||||
const DEFAULT_MAX_NUM_SIZE: usize = 4;
|
||||
|
||||
impl ScriptNum {
|
||||
pub const ZERO: ScriptNum = ScriptNum(0);
|
||||
pub const ONE: ScriptNum = ScriptNum(1);
|
||||
|
||||
const DEFAULT_MAX_NUM_SIZE: usize = 4;
|
||||
|
||||
pub fn new(
|
||||
vch: &Vec<u8>,
|
||||
require_minimal: bool,
|
||||
max_num_size: Option<usize>,
|
||||
) -> Result<Self, ScriptNumError> {
|
||||
let max_num_size = max_num_size.unwrap_or(Self::DEFAULT_MAX_NUM_SIZE);
|
||||
if vch.len() > max_num_size {
|
||||
return Err(ScriptNumError::Overflow {
|
||||
max_num_size,
|
||||
actual: vch.len(),
|
||||
});
|
||||
}
|
||||
if require_minimal && !vch.is_empty() {
|
||||
// Check that the number is encoded with the minimum possible
|
||||
// number of bytes.
|
||||
//
|
||||
// If the most-significant-byte - excluding the sign bit - is zero
|
||||
// then we're not minimal. Note how this test also rejects the
|
||||
// negative-zero encoding, 0x80.
|
||||
if (vch.last().expect("not empty") & 0x7F) == 0 {
|
||||
// One exception: if there's more than one byte and the most
|
||||
// significant bit of the second-most-significant-byte is set
|
||||
// then it would have conflicted with the sign bit if one
|
||||
// fewer byte were used, and so such encodings are minimal.
|
||||
// An example of this is +-255, which have minimal encodings
|
||||
// [0xff, 0x00] and [0xff, 0x80] respectively.
|
||||
if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 {
|
||||
return Err(ScriptNumError::NonMinimalEncoding);
|
||||
pub fn parse_num(
|
||||
vch: &Vec<u8>,
|
||||
require_minimal: bool,
|
||||
max_num_size: Option<usize>,
|
||||
) -> Result<i64, ScriptNumError> {
|
||||
match vch.last() {
|
||||
None => Ok(0),
|
||||
Some(vch_back) => {
|
||||
let max_num_size = max_num_size.unwrap_or(DEFAULT_MAX_NUM_SIZE);
|
||||
if vch.len() > max_num_size {
|
||||
return Err(ScriptNumError::Overflow {
|
||||
max_num_size,
|
||||
actual: vch.len(),
|
||||
});
|
||||
}
|
||||
if require_minimal {
|
||||
// Check that the number is encoded with the minimum possible number of bytes.
|
||||
//
|
||||
// If the most-significant-byte - excluding the sign bit - is zero then we're not
|
||||
// minimal. Note how this test also rejects the negative-zero encoding, 0x80.
|
||||
if (vch_back & 0x7F) == 0 {
|
||||
// One exception: if there's more than one byte and the most significant bit of
|
||||
// the second-most-significant-byte is set then it would have conflicted with
|
||||
// the sign bit if one fewer byte were used, and so such encodings are minimal.
|
||||
// An example of this is +-255, which have minimal encodings [0xff, 0x00] and
|
||||
// [0xff, 0x80] respectively.
|
||||
if vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0 {
|
||||
return Err(ScriptNumError::NonMinimalEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::set_vch(vch).map(ScriptNum)
|
||||
}
|
||||
|
||||
pub fn getint(&self) -> i32 {
|
||||
if self.0 > i32::MAX.into() {
|
||||
i32::MAX
|
||||
} else if self.0 < i32::MIN.into() {
|
||||
i32::MIN
|
||||
} else {
|
||||
self.0.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] {
|
||||
// Match the behaviour of the C++ code, which special-cased this encoding to avoid
|
||||
// an undefined shift of a signed type by 64 bits.
|
||||
return Ok(i64::MIN);
|
||||
};
|
||||
|
||||
pub fn getvch(&self) -> Vec<u8> {
|
||||
Self::serialize(&self.0)
|
||||
}
|
||||
// Ensure defined behaviour (in Rust, left shift of `i64` by 64 bits is an arithmetic
|
||||
// overflow that may panic or give an unspecified result). The above encoding of
|
||||
// `i64::MIN` is the only allowed 9-byte encoding.
|
||||
if vch.len() > 8 {
|
||||
return Err(ScriptNumError::Overflow {
|
||||
max_num_size: 8,
|
||||
actual: vch.len(),
|
||||
});
|
||||
};
|
||||
|
||||
pub fn serialize(value: &i64) -> Vec<u8> {
|
||||
if *value == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if *value == i64::MIN {
|
||||
// The code below was based on buggy C++ code, that produced the
|
||||
// "wrong" result for INT64_MIN. In that case we intentionally return
|
||||
// the result that the C++ code as compiled for zcashd (with `-fwrapv`)
|
||||
// originally produced on an x86_64 system.
|
||||
return vec![0, 0, 0, 0, 0, 0, 0, 128, 128];
|
||||
}
|
||||
|
||||
let mut result = Vec::new();
|
||||
let neg = *value < 0;
|
||||
let mut absvalue = value.unsigned_abs();
|
||||
|
||||
while absvalue != 0 {
|
||||
result.push((absvalue & 0xff).try_into().expect("fits in u8"));
|
||||
absvalue >>= 8;
|
||||
}
|
||||
|
||||
// - If the most significant byte is >= 0x80 and the value is positive, push a
|
||||
// new zero-byte to make the significant byte < 0x80 again.
|
||||
// - If the most significant byte is >= 0x80 and the value is negative, push a
|
||||
// new 0x80 byte that will be popped off when converting to an integral.
|
||||
// - If the most significant byte is < 0x80 and the value is negative, add 0x80
|
||||
// to it, since it will be subtracted and interpreted as a negative when
|
||||
// converting to an integral.
|
||||
|
||||
let result_back = result.last_mut().expect("not empty");
|
||||
if *result_back & 0x80 != 0 {
|
||||
result.push(if neg { 0x80 } else { 0 });
|
||||
} else if neg {
|
||||
*result_back |= 0x80;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn set_vch(vch: &Vec<u8>) -> Result<i64, ScriptNumError> {
|
||||
match vch.last() {
|
||||
None => Ok(0),
|
||||
Some(vch_back) => {
|
||||
if *vch == vec![0, 0, 0, 0, 0, 0, 0, 128, 128] {
|
||||
// Match the behaviour of the C++ code, which special-cased this
|
||||
// encoding to avoid an undefined shift of a signed type by 64 bits.
|
||||
return Ok(i64::MIN);
|
||||
};
|
||||
|
||||
// Ensure defined behaviour (in Rust, left shift of `i64` by 64 bits
|
||||
// is an arithmetic overflow that may panic or give an unspecified
|
||||
// result). The above encoding of `i64::MIN` is the only allowed
|
||||
// 9-byte encoding.
|
||||
if vch.len() > 8 {
|
||||
return Err(ScriptNumError::Overflow {
|
||||
max_num_size: 8,
|
||||
actual: vch.len(),
|
||||
});
|
||||
};
|
||||
|
||||
let mut result: i64 = 0;
|
||||
for (i, vch_i) in vch.iter().enumerate() {
|
||||
result |= i64::from(*vch_i) << (8 * i);
|
||||
}
|
||||
|
||||
// If the input vector's most significant byte is 0x80, remove it from
|
||||
// the result's msb and return a negative.
|
||||
if vch_back & 0x80 != 0 {
|
||||
return Ok(-(result & !(0x80 << (8 * (vch.len() - 1)))));
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
let mut result: i64 = 0;
|
||||
for (i, vch_i) in vch.iter().enumerate() {
|
||||
result |= i64::from(*vch_i) << (8 * i);
|
||||
}
|
||||
|
||||
// If the input vector's most significant byte is 0x80, remove it from the result's msb
|
||||
// and return a negative.
|
||||
if vch_back & 0x80 != 0 {
|
||||
return Ok(-(result & !(0x80 << (8 * (vch.len() - 1)))));
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for ScriptNum {
|
||||
fn from(value: i32) -> Self {
|
||||
ScriptNum(value.into())
|
||||
pub fn serialize_num(value: i64) -> Vec<u8> {
|
||||
if value == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for ScriptNum {
|
||||
fn from(value: u32) -> Self {
|
||||
ScriptNum(value.into())
|
||||
if value == i64::MIN {
|
||||
// The code below was based on buggy C++ code, that produced the "wrong" result for
|
||||
// INT64_MIN. In that case we intentionally return the result that the C++ code as compiled
|
||||
// for zcashd (with `-fwrapv`) originally produced on an x86_64 system.
|
||||
return vec![0, 0, 0, 0, 0, 0, 0, 128, 128];
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ScriptNum {
|
||||
fn from(value: u8) -> Self {
|
||||
ScriptNum(value.into())
|
||||
let mut result = Vec::new();
|
||||
let neg = value < 0;
|
||||
let mut absvalue = value.abs();
|
||||
|
||||
while absvalue != 0 {
|
||||
result.push(
|
||||
(absvalue & 0xff)
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
);
|
||||
absvalue >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: This instance will be obsolete if we convert bool directly to a `Vec<u8>`, which is also
|
||||
/// more efficient.
|
||||
impl From<bool> for ScriptNum {
|
||||
fn from(value: bool) -> Self {
|
||||
ScriptNum(value.into())
|
||||
// - If the most significant byte is >= 0x80 and the value is positive, push a new zero-byte to
|
||||
// make the significant byte < 0x80 again.
|
||||
// - If the most significant byte is >= 0x80 and the value is negative, push a new 0x80 byte
|
||||
// that will be popped off when converting to an integral.
|
||||
// - If the most significant byte is < 0x80 and the value is negative, add 0x80 to it, since it
|
||||
// will be subtracted and interpreted as a negative when converting to an integral.
|
||||
|
||||
if result.last().map_or(true, |last| last & 0x80 != 0) {
|
||||
result.push(if neg { 0x80 } else { 0 });
|
||||
} else if neg {
|
||||
if let Some(last) = result.last_mut() {
|
||||
*last |= 0x80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for ScriptNum {
|
||||
type Error = TryFromIntError;
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
value.try_into().map(ScriptNum)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ScriptNum> for u16 {
|
||||
type Error = TryFromIntError;
|
||||
fn try_from(value: ScriptNum) -> Result<Self, Self::Error> {
|
||||
value.getint().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ScriptNum> for u8 {
|
||||
type Error = TryFromIntError;
|
||||
fn try_from(value: ScriptNum) -> Result<Self, Self::Error> {
|
||||
value.getint().try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ScriptNum {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.checked_add(other.0)
|
||||
.expect("caller should avoid overflow"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for ScriptNum {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.checked_sub(other.0)
|
||||
.expect("caller should avoid underflow"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for ScriptNum {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
assert!(self.0 != i64::MIN);
|
||||
Self(-self.0)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/** Serialized script, used inside transaction inputs and outputs */
|
||||
|
|
Loading…
Reference in New Issue