255 lines
9.1 KiB
Rust
255 lines
9.1 KiB
Rust
// SPDX-License-Identifier: CC0-1.0
|
|
|
|
use crate::blockdata::opcodes::{self, Opcode};
|
|
use crate::blockdata::script::{read_uint_iter, Error, PushBytes, Script, ScriptBuf, UintError};
|
|
|
|
/// A "parsed opcode" which allows iterating over a [`Script`] in a more sensible way.
|
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
pub enum Instruction<'a> {
|
|
/// Push a bunch of data.
|
|
PushBytes(&'a PushBytes),
|
|
/// Some non-push opcode.
|
|
Op(Opcode),
|
|
}
|
|
|
|
impl<'a> Instruction<'a> {
|
|
/// Returns the opcode if the instruction is not a data push.
|
|
pub fn opcode(&self) -> Option<Opcode> {
|
|
match self {
|
|
Instruction::Op(op) => Some(*op),
|
|
Instruction::PushBytes(_) => None,
|
|
}
|
|
}
|
|
|
|
/// Returns the pushed bytes if the instruction is a data push.
|
|
pub fn push_bytes(&self) -> Option<&PushBytes> {
|
|
match self {
|
|
Instruction::Op(_) => None,
|
|
Instruction::PushBytes(bytes) => Some(bytes),
|
|
}
|
|
}
|
|
|
|
/// Returns the number interpretted by the script parser
|
|
/// if it can be coerced into a number.
|
|
///
|
|
/// This does not require the script num to be minimal.
|
|
pub fn script_num(&self) -> Option<i64> {
|
|
match self {
|
|
Instruction::Op(op) => {
|
|
let v = op.to_u8();
|
|
match v {
|
|
// OP_PUSHNUM_1 ..= OP_PUSHNUM_16
|
|
0x51..=0x60 => Some(v as i64 - 0x50),
|
|
// OP_PUSHNUM_NEG1
|
|
0x4f => Some(-1),
|
|
_ => None,
|
|
}
|
|
}
|
|
Instruction::PushBytes(bytes) => {
|
|
match super::read_scriptint_non_minimal(bytes.as_bytes()) {
|
|
Ok(v) => Some(v),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the number of bytes required to encode the instruction in script.
|
|
pub(super) fn script_serialized_len(&self) -> usize {
|
|
match self {
|
|
Instruction::Op(_) => 1,
|
|
Instruction::PushBytes(bytes) => ScriptBuf::reserved_len_for_slice(bytes.len()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Iterator over a script returning parsed opcodes.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Instructions<'a> {
|
|
pub(crate) data: core::slice::Iter<'a, u8>,
|
|
pub(crate) enforce_minimal: bool,
|
|
}
|
|
|
|
impl<'a> Instructions<'a> {
|
|
/// Views the remaining script as a slice.
|
|
///
|
|
/// This is analogous to what [`core::str::Chars::as_str`] does.
|
|
pub fn as_script(&self) -> &'a Script { Script::from_bytes(self.data.as_slice()) }
|
|
|
|
/// Sets the iterator to end so that it won't iterate any longer.
|
|
pub(super) fn kill(&mut self) {
|
|
let len = self.data.len();
|
|
self.data.nth(len.max(1) - 1);
|
|
}
|
|
|
|
/// Takes a `len` bytes long slice from iterator and returns it, advancing the iterator.
|
|
///
|
|
/// If the iterator is not long enough [`Error::EarlyEndOfScript`] is returned and the iterator
|
|
/// is killed to avoid returning an infinite stream of errors.
|
|
pub(super) fn take_slice_or_kill(&mut self, len: u32) -> Result<&'a PushBytes, Error> {
|
|
let len = len as usize;
|
|
if self.data.len() >= len {
|
|
let slice = &self.data.as_slice()[..len];
|
|
if len > 0 {
|
|
self.data.nth(len - 1);
|
|
}
|
|
|
|
Ok(slice.try_into().expect("len was created from u32, so can't happen"))
|
|
} else {
|
|
self.kill();
|
|
Err(Error::EarlyEndOfScript)
|
|
}
|
|
}
|
|
|
|
pub(super) fn next_push_data_len(
|
|
&mut self,
|
|
len: PushDataLenLen,
|
|
min_push_len: usize,
|
|
) -> Option<Result<Instruction<'a>, Error>> {
|
|
let n = match read_uint_iter(&mut self.data, len as usize) {
|
|
Ok(n) => n,
|
|
// We do exhaustive matching to not forget to handle new variants if we extend
|
|
// `UintError` type.
|
|
// Overflow actually means early end of script (script is definitely shorter
|
|
// than `usize::MAX`)
|
|
Err(UintError::EarlyEndOfScript) | Err(UintError::NumericOverflow) => {
|
|
self.kill();
|
|
return Some(Err(Error::EarlyEndOfScript));
|
|
}
|
|
};
|
|
if self.enforce_minimal && n < min_push_len {
|
|
self.kill();
|
|
return Some(Err(Error::NonMinimalPush));
|
|
}
|
|
let result = n
|
|
.try_into()
|
|
.map_err(|_| Error::NumericOverflow)
|
|
.and_then(|n| self.take_slice_or_kill(n))
|
|
.map(Instruction::PushBytes);
|
|
Some(result)
|
|
}
|
|
}
|
|
|
|
/// Allowed length of push data length.
|
|
///
|
|
/// This makes it easier to prove correctness of `next_push_data_len`.
|
|
pub(super) enum PushDataLenLen {
|
|
One = 1,
|
|
Two = 2,
|
|
Four = 4,
|
|
}
|
|
|
|
impl<'a> Iterator for Instructions<'a> {
|
|
type Item = Result<Instruction<'a>, Error>;
|
|
|
|
fn next(&mut self) -> Option<Result<Instruction<'a>, Error>> {
|
|
let &byte = self.data.next()?;
|
|
|
|
// classify parameter does not really matter here since we are only using
|
|
// it for pushes and nums
|
|
match Opcode::from(byte).classify(opcodes::ClassifyContext::Legacy) {
|
|
opcodes::Class::PushBytes(n) => {
|
|
// make sure safety argument holds across refactorings
|
|
let n: u32 = n;
|
|
|
|
let op_byte = self.data.as_slice().first();
|
|
match (self.enforce_minimal, op_byte, n) {
|
|
(true, Some(&op_byte), 1)
|
|
if op_byte == 0x81 || (op_byte > 0 && op_byte <= 16) =>
|
|
{
|
|
self.kill();
|
|
Some(Err(Error::NonMinimalPush))
|
|
}
|
|
(_, None, 0) => {
|
|
// the iterator is already empty, may as well use this information to avoid
|
|
// whole take_slice_or_kill function
|
|
Some(Ok(Instruction::PushBytes(PushBytes::empty())))
|
|
}
|
|
_ => Some(self.take_slice_or_kill(n).map(Instruction::PushBytes)),
|
|
}
|
|
}
|
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) =>
|
|
self.next_push_data_len(PushDataLenLen::One, 76),
|
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) =>
|
|
self.next_push_data_len(PushDataLenLen::Two, 0x100),
|
|
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) =>
|
|
self.next_push_data_len(PushDataLenLen::Four, 0x10000),
|
|
// Everything else we can push right through
|
|
_ => Some(Ok(Instruction::Op(Opcode::from(byte)))),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
if self.data.len() == 0 {
|
|
(0, Some(0))
|
|
} else {
|
|
// There will not be more instructions than bytes
|
|
(1, Some(self.data.len()))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> core::iter::FusedIterator for Instructions<'a> {}
|
|
|
|
/// Iterator over script instructions with their positions.
|
|
///
|
|
/// The returned indices can be used for slicing [`Script`] [safely](Script#slicing-safety).
|
|
///
|
|
/// This is analogous to [`core::str::CharIndices`].
|
|
#[derive(Debug, Clone)]
|
|
pub struct InstructionIndices<'a> {
|
|
instructions: Instructions<'a>,
|
|
pos: usize,
|
|
}
|
|
|
|
impl<'a> InstructionIndices<'a> {
|
|
/// Views the remaining script as a slice.
|
|
///
|
|
/// This is analogous to what [`core::str::Chars::as_str`] does.
|
|
#[inline]
|
|
pub fn as_script(&self) -> &'a Script { self.instructions.as_script() }
|
|
|
|
/// Creates `Self` setting `pos` to 0.
|
|
pub(super) fn from_instructions(instructions: Instructions<'a>) -> Self {
|
|
InstructionIndices { instructions, pos: 0 }
|
|
}
|
|
|
|
pub(super) fn remaining_bytes(&self) -> usize { self.instructions.as_script().len() }
|
|
|
|
/// Modifies the iterator using `next_fn` returning the next item.
|
|
///
|
|
/// This generically computes the new position and maps the value to be returned from iterator
|
|
/// method.
|
|
pub(super) fn next_with<F: FnOnce(&mut Self) -> Option<Result<Instruction<'a>, Error>>>(
|
|
&mut self,
|
|
next_fn: F,
|
|
) -> Option<<Self as Iterator>::Item> {
|
|
let prev_remaining = self.remaining_bytes();
|
|
let prev_pos = self.pos;
|
|
let instruction = next_fn(self)?;
|
|
// No underflow: there must be less remaining bytes now than previously
|
|
let consumed = prev_remaining - self.remaining_bytes();
|
|
// No overflow: sum will never exceed slice length which itself can't exceed `usize`
|
|
self.pos += consumed;
|
|
Some(instruction.map(move |instruction| (prev_pos, instruction)))
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for InstructionIndices<'a> {
|
|
/// The `usize` in the tuple represents index at which the returned `Instruction` is located.
|
|
type Item = Result<(usize, Instruction<'a>), Error>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> { self.next_with(|this| this.instructions.next()) }
|
|
|
|
#[inline]
|
|
fn size_hint(&self) -> (usize, Option<usize>) { self.instructions.size_hint() }
|
|
|
|
// the override avoids computing pos multiple times
|
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
|
self.next_with(|this| this.instructions.nth(n))
|
|
}
|
|
}
|
|
|
|
impl core::iter::FusedIterator for InstructionIndices<'_> {}
|