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:
Greg Pfeil 2025-04-30 15:12:50 -06:00 committed by GitHub
parent cc157ffdce
commit d3e242b47f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 233 additions and 344 deletions

View File

@ -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

View File

@ -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 */