diff --git a/chronik/bitcoinsuite-core/src/script/iter.rs b/chronik/bitcoinsuite-core/src/script/iter.rs new file mode 100644 index 000000000..0db1f23b3 --- /dev/null +++ b/chronik/bitcoinsuite-core/src/script/iter.rs @@ -0,0 +1,43 @@ +// Copyright (c) 2023 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +use bytes::Bytes; + +use crate::{error::DataError, script::Op}; + +/// Iterate over the [`Op`]s in a Script. +/// +/// Will read ops until parsing an opcode fails, in which case it returns +/// [`Err`] (and then stops yielding items). +#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +pub struct ScriptOpIter { + remaining_bytecode: Bytes, +} + +impl ScriptOpIter { + pub(crate) fn new(bytecode: Bytes) -> Self { + ScriptOpIter { + remaining_bytecode: bytecode, + } + } +} + +impl Iterator for ScriptOpIter { + type Item = Result; + + fn next(&mut self) -> Option { + if self.remaining_bytecode.is_empty() { + None + } else { + match Op::read_op(&mut self.remaining_bytecode) { + Ok(op) => Some(Ok(op)), + Err(err) => { + // Stop iteration by truncating the remaining bytecode + self.remaining_bytecode.truncate(0); + Some(Err(err)) + } + } + } + } +} diff --git a/chronik/bitcoinsuite-core/src/script/mod.rs b/chronik/bitcoinsuite-core/src/script/mod.rs index 2c9a18766..2edc5492a 100644 --- a/chronik/bitcoinsuite-core/src/script/mod.rs +++ b/chronik/bitcoinsuite-core/src/script/mod.rs @@ -1,21 +1,25 @@ // Copyright (c) 2023 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. //! Module for structs and definitions regarding Script. +mod iter; +mod op; pub mod opcode; mod pubkey; mod pubkey_variant; #[allow(clippy::module_inception)] mod script; mod script_mut; mod uncompressed_pubkey; mod variant; +pub use self::iter::*; +pub use self::op::*; pub use self::pubkey::*; pub use self::pubkey_variant::*; pub use self::script::*; pub use self::script_mut::*; pub use self::uncompressed_pubkey::*; pub use self::variant::*; diff --git a/chronik/bitcoinsuite-core/src/script/op.rs b/chronik/bitcoinsuite-core/src/script/op.rs new file mode 100644 index 000000000..d702c2cea --- /dev/null +++ b/chronik/bitcoinsuite-core/src/script/op.rs @@ -0,0 +1,47 @@ +// Copyright (c) 2023 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +use bytes::Bytes; + +use crate::{ + bytes::{read_array, read_bytes}, + error::DataError, + script::opcode::*, +}; + +/// An operation in a script. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Op { + /// Opcode that stands by itself, with no data following it e.g. [`OP_0`], + /// [`OP_1NEGATE`], [`OP_RETURN`], [`OP_EQUAL`], [`OP_CHECKSIG`]. + Code(Opcode), + /// Opcode that pushes the bytes following it onto the stack. + Push(Opcode, Bytes), +} + +impl Op { + /// Read the next [`Op`] in the script bytecode, including the + /// payload for [`Op::Push`] opcodes. + pub fn read_op(data: &mut Bytes) -> Result { + let opcode_num = read_bytes(data, 1)?[0]; + Ok(match Opcode(opcode_num) { + opcode @ Opcode(0x01..=0x4b) => { + Op::Push(opcode, read_bytes(data, opcode_num as usize)?) + } + opcode @ OP_PUSHDATA1 => { + let size = read_bytes(data, 1)?[0] as usize; + Op::Push(opcode, read_bytes(data, size)?) + } + opcode @ OP_PUSHDATA2 => { + let size = u16::from_le_bytes(read_array(data)?); + Op::Push(opcode, read_bytes(data, size as usize)?) + } + opcode @ OP_PUSHDATA4 => { + let size = u32::from_le_bytes(read_array(data)?); + Op::Push(opcode, read_bytes(data, size as usize)?) + } + otherwise => Op::Code(otherwise), + }) + } +} diff --git a/chronik/bitcoinsuite-core/src/script/script.rs b/chronik/bitcoinsuite-core/src/script/script.rs index 658dfb766..a61f14023 100644 --- a/chronik/bitcoinsuite-core/src/script/script.rs +++ b/chronik/bitcoinsuite-core/src/script/script.rs @@ -1,168 +1,217 @@ // Copyright (c) 2023 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. use bytes::Bytes; use crate::{ hash::{Hashed, ShaRmd160}, - script::{opcode::*, PubKey, ScriptMut, UncompressedPubKey}, + script::{opcode::*, PubKey, ScriptMut, ScriptOpIter, UncompressedPubKey}, }; /// A Bitcoin script. /// /// This is immutable, and uses [`Bytes`] to store the bytecode, making it cheap /// to copy. #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Script(Bytes); impl Script { /// Create a new script from the given bytecode. pub fn new(bytecode: Bytes) -> Self { Script(bytecode) } /// Pay-to-public-key-hash: /// `OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG` /// ``` /// # use bitcoinsuite_core::{script::Script, hash::ShaRmd160}; /// # use hex_literal::hex; /// let hash = ShaRmd160(hex!("00112233445566778899aabbccddeeff00112233")); /// let script = Script::p2pkh(&hash); /// assert_eq!( /// script.hex(), /// "76a91400112233445566778899aabbccddeeff0011223388ac", /// ); /// ``` pub fn p2pkh(hash: &ShaRmd160) -> Script { let mut script = ScriptMut::with_capacity(2 + 1 + ShaRmd160::SIZE + 2); script.put_opcodes([OP_DUP, OP_HASH160]); script.put_bytecode(&[ShaRmd160::SIZE as u8]); script.put_bytecode(hash.as_le_bytes()); script.put_opcodes([OP_EQUALVERIFY, OP_CHECKSIG]); script.freeze() } /// Pay-to-script-hash: `OP_HASH160