Skip to main content

Command Palette

Search for a command to run...

Parsing Input scriptSig in Bitcoin Transactions Using Rust Programming Language

Updated
12 min read

In the previous article, we saw how we can parse bytes from a hex encoded transaction into a transaction structure (version, inputs, outputs and lock time).

Previous Articles.

  1. Parsing Bitcoin Transactions in Rust Programming Language Standard Library — https://448.africa/parsing-bitcoin-transactions-in-rust-programming-language-standard-library-20c06a23a564

In this article we will look at parsing input scriptSig bytes as a Bitcoin script.

Understanding Bitcoin Script

Bitcoin script is a simple, stack-based, Forth-like programming language used in the Bitcoin protocol for transaction processing. It’s not Turing-complete, meaning it doesn’t have loops, which is a design choice to prevent attacks on the network like infinite loops and logic bombs that would be evaluated in a way that causes denial of service attacks.

Each Bitcoin transaction includes a script that validates the conditions under which the transaction can be redeemed. The script is processed from left to right, and if it completes without any errors and the final result is true, the transaction is considered valid.

Most Common Bitcoin Scripts

Bitcoin’s primary focus is sending bitcoins from one address to one or many addresses. This means that most scripts are created to allow one or multiple wallets to spend bitcoins. The Bitcoin core network has 5 standard scripts that are known to all nodes and miners. The standard scripts and their Assembly Protocol (ASM) representation:
 — Note that all the bytes should appear as hex

  1. Pay to Public Key (P2PK) — This is rarely used.
    <Public Key> OP_CHECKSIG

  2. Pay to Public Key Hash (P2PKH)
    OP_DUP OP_HASH160 <PublicKey Hash> OP_EQUAL OP_CHECKSIG

  3. Pay to Multi-signature (P2MS) — limited to 15 keys
    M <Public Key 1> <Public Key 2> … <Public Key N> N OP_CHECKMULTISIG — where M is the threshold and N is the number of signatures

  4. Pay to Script Hash (P2SH)
    OP_HASH160 <20 bytes of the hash> OP_EQUAL

  5. Pay to Witness Public Key Hash (P2WPKH)
    OP_0 *<20-byte hash>*

  6. Pay to Witness Script Hash (P2WSH)
    OP_0 <32-byte hash>

  7. Pay to Tap Root (P2TR)
    OP_1 <32-byte hash>

  8. Data Output (OP_RETURN)
    OP_RETURN <data>

We will look at decoding our previous script data into these standard scripts in Rust.

Detecting the script type

At the time of writing there are 8 standard scripts which makes it very easy to identify the type of script by parsing the first or first and second OPCODE NOTE that the * in OP_PUSHBYTES_*means the number of bytes:

  1. P2PK — OP_PUSHBYTES_65

  2. P2PKH — OP_DUP

  3. P2MS — OP_1 — OP_16 OP_PUSHBYTES_*

  4. P2SH — OP_HASH160

  5. P2WPKH — OP_0 OP_PUSHBYTES_20

  6. P2WSH — OP_0 OP_PUSHBYTES_32

  7. P2TR — OP_1 OP_PUSHBYTES_32

  8. Data (OP_RETURN) — OP_RETURN

Now we will create a module called scripts in the scripts.rs file and import it to the main.rs file.

//... main.rs previous code here
mod scripts;
pub use scripts::*;

fn main() {
    // .. previous code from previous tutorial
}

In our scripts.rs file we will import standard library types for addition , I/O and error handling. We will convert all our errors to std::io::Error

use std::{
    io::{self, Cursor, Error, ErrorKind, Read},
    ops::Add,
};

We will create methods to parse the standard scriptSig inside the impl block of StandardScripts struct

/// Handles scriptSig parsing
#[derive(Debug, Clone, Copy)]
pub struct StandardScripts;

impl StandardScripts {}

We will parse based on the following:

If:

  • First OPCODE is OP_PUSHBYTES_65 then parse as P2PK

  • First OPCODE is OP_DUP then parse as P2PKH

  • First OPCODE is OP_RETURN then parse as Data(OP_RETURN)

  • First OPCODE is OP_0 and second OPCODE is OP_PUSHBYTES_20 then parse as P2WPKH

  • First OPCODE is OP_0 and second OPCODE is OP_PUSHBYTES_32 then parse as P2WSH

  • First OP_CODE is between OP_1 and OP_16 and second OPCODE is OP_PUSHBYTES_32 then parse as P2TR

  • First OP_CODE is between OP_1 and OP_16 and second OPCODE is OP_PUSHBYTES_* then try parsing as P2MS else return an std::io::Error with ErrorKind::InvalidData with a helpful error message.

Let’s define the OPCODES to parse

/// Supported OPCODEs for standard scripts in scriptSig
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[allow(non_camel_case_types)]
pub enum Opcode {
    OP_HASH160,
    OP_CHECKSIG,
    OP_EQUAL,
    OP_EQUALVERIFY,
    OP_CHECKMULTISIG,
    OP_DUP,
    OP_RETURN,
    OP_0,
    OP_1,
    /// Handles OP_2 to OP_16
    Num(u8),
    /// Handles all OP_PUSHBYTES_*
    PushBytes(u8),
    /// Useful in error handling for unsupported opcodes
    UnsupportedOpcode,
}

impl Opcode {
    /// Parse an opcode from a hex decoded byte
    pub fn from_byte(byte: u8) -> Self {
        match byte {
            169 => Self::OP_HASH160,
            /// All OP_PUSHBYTES_*
            1..=75 => Self::PushBytes(byte),
            172 => Self::OP_CHECKSIG,
            135 => Self::OP_EQUAL,
            136 => Self::OP_EQUALVERIFY,
            174 => Self::OP_CHECKMULTISIG,
            118 => Self::OP_DUP,
            106 => Self::OP_RETURN,
            0 => Self::OP_0,
            81 => Self::OP_1,
            /// All OP_2 - OP_16
            82..=96 => {
                let to_num = match byte {
                82 => 2u8,
                83 => 3,
                84 => 4,
                85 => 5,
                86 => 6,
                87 => 7,
                88 => 8,
                89 => 9,
                90 => 10,
                91 => 11,
                92 => 12,
                93 => 13,
                94 => 14,
                95 => 15,
                96 => 16,
                _ => return Self::UnsupportedOpcode,
            };
            Self::Num(to_num)
        }
        _ => Self::UnsupportedOpcode,
        }    
    }

    /// Handles reading OP_PUSHBYTES_*
    pub fn read_bytes(&self, bytes: &mut Cursor<&[u8]>) -> io::Result<Vec<u8>> {
        // Store all parsed bytes for `OP_PUSHBYTES_*`
        let mut buffer = Vec::<u8>::new();

        match self {
            Self::PushBytes(byte_len) => {
            // Gets the current position and adds the length of the
            let new_position = (bytes.position() as usize).add(*byte_len as usize);
            // Read the byte slice from the current cursor position the byte length
            buffer.extend_from_slice(&bytes.get_ref()[bytes.position() as usize..new_position]);
            // Set the cursor position to the previous cursor position + the byte length
            bytes.set_position(new_position as u64);

            Ok(buffer)
        }
        _ => Err(io::Error::new(
            ErrorKind::Unsupported,
            "This operation is not supported",
            )),
        }
    }
}

Next we implement the trait TryFrom for our Opcode enum so that we can convert it easily to a Rust String

impl TryFrom for String {
    // All errors are converted to std::io::Error
    type Error = io::Error;

    fn try_from(value: Opcode) -> Result<Self, Self::Error> {
        let opcode = match value {
            Opcode::OP_HASH160 => "OP_HASH160",
            Opcode::PushBytes(bytes_len) => {
                return Ok(String::from("OP_PUSHBYTES_").add(bytes_len.to_string().as_str()))
            }
            Opcode::OP_CHECKSIG => "OP_CHECKSIG",
            Opcode::OP_EQUAL => "OP_EQUAL",
            Opcode::OP_EQUALVERIFY => "OP_EQUALVERIFY",
            Opcode::OP_CHECKMULTISIG => "OP_CHECKMULTISIG",
            Opcode::OP_DUP => "OP_DUP",
            Opcode::OP_RETURN => "OP_RETURN",    
            Opcode::OP_0 => "OP_0",
            Opcode::OP_1 => "OP_1",
            Opcode::Num(value) => return Ok(String::from("OP_").add(value.to_string().as_str())),
            Opcode::UnsupportedOpcode => {
                return Err(io::Error::new(
                    ErrorKind::InvalidData,
                    "Unsupported Opcode. Opcode not part of Bitcoin Core standard scripts",
                    ))
                }
             };

         Ok(opcode.into())
    }
}

Next we create a struct with methods to build our scriptSig into a String

#[derive(Debug, Default)]
pub struct ScriptBuilder(Vec<String>);

impl ScriptBuilder {
    /// Initialize `Self` with defaults
    pub fn new() -> Self {
        Self::default()
    }

    /// This will receive an `Opcode` and convert it
    /// into a String since we implemented
    /// `TryFrom for String`
    pub fn push_opcode(&mut self, opcode: Opcode) -> io::Result<&mut Self> {
        let opcode_string: String = opcode.try_into()?;
        self.0.push(opcode_string);

        Ok(self)
    }

    /// This will convert our bytes into hex and
    /// push the opcode `OP_PUSHBYTES_*`
    pub fn push_bytes(&mut self, bytes: &[u8]) -> io::Result<&mut Self> {
        self.0.push(hex::encode(bytes));

        Ok(self)
    }

    /// Next we collect the Vector of Strings
    /// into one String
    pub fn build(self) -> String {
        self.0
            .into_iter()
            .map(|mut part| {
                part.push(' ');
                part
            })
            .collect::<String>()
            .trim()
            .into()
    }  
}

Next we implement the parsing methods for our StandardScripts struct

We implement the parse method to detect the scriptSig

impl StandardScripts {
    /// Decides which scriptSig to parse
    pub fn parse(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        // Get the first OPCODE
        let mut opcode_buffer = [0u8; 1];
        bytes.read_exact(&mut opcode_buffer)?;
        // Convert our byte into an `Opcode`
        let first_opcode = Opcode::from_byte(opcode_buffer[0]);

        match first_opcode {
            // If `OP_PUSHBYTES_65` then parse as P2PK
            Opcode::PushBytes(65) => Self::parse_p2pk(bytes),
            // If `OP_DUP` then parse as P2PKH
            Opcode::OP_DUP => Self::parse_p2pkh(bytes),
            // If `OP_HASH160` then parse as P2PK
            Opcode::OP_HASH160 => Self::parse_p2sh(bytes),
            // If `OP_RETURN` then parse as Data(OP_RETURN)
            Opcode::OP_RETURN => Self::parse_data(bytes),
            // If `OP_0` as first OPCODE and OP_PUSHBYTES_20 is second OPCODE then parse as P2WPKH
            // Else if `OP_0` as first OPCODE and OP_PUSHBYTES_32 is second OPCODE then parse as P2WSH
            // Else return an an error if `OP_0` is first OPCODE
            Opcode::OP_0 => {
                bytes.read_exact(&mut opcode_buffer)?;
                let second_opcode = Opcode::from_byte(opcode_buffer[0]);
                if second_opcode.eq(&Opcode::PushBytes(20)) {
                    Self::parse_p2wpkh(bytes)
                } else if second_opcode.eq(&Opcode::PushBytes(32)) {
                    Self::parse_p2wsh(bytes)
                } else {
                    return Self::to_io_error(
                    "Invalid Script. Expected OP_PUSHBYTES_20 or OP_PUSHBYTES_32 after OP_0",
                    );
                }
            }
            _ => {
                // If `OP_1` as first OPCODE and OP_PUSHBYTES_32 is second OPCODE then parse as P2TR
                // Else try parsing as P2MS

                bytes.read_exact(&mut opcode_buffer)?;
                let second_opcode = Opcode::from_byte(opcode_buffer[0]);

                if first_opcode.eq(&Opcode::OP_1) && second_opcode.eq(&Opcode::PushBytes(32)) {
                    Self::parse_p2tr(bytes)
                } else {
                    // Reset current position of cursor to the beginning
                    bytes.set_position(bytes.position() - 2);
                    Self::parse_p2ms(bytes)
                }
            }
        }
    }
}

Next, we create a method to handle errors since they are common whereby they return a std::io::Error with ErrorKind and custom message.

impl StandardScripts {
    // ..previous methods here

    // Error Handling returning an `io::Result` to avoid
    // having to add `Err()` whenever we call this method.
    // Our message is unique so we add that as argument
    pub fn to_io_error(message: &str) -> io::Result<String> {
        Err(io::Error::new(ErrorKind::InvalidData, message))
    }
}
  1. Method to parse P2PK
impl StandardScripts {
    /// Parse as P2PK
    pub fn parse_p2pk(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        // Cursor is already at second byte to we parse
        // 65 bytes from that position to get the
        // Uncompressed Public Key
        let mut public_key_bytes = [0u8; 65];
        bytes.read_exact(&mut public_key_bytes)?;
        // Next we parse OP_CHECKSIG
        let mut op_checksig_byte = [0u8; 1];
        bytes.read_exact(&mut op_checksig_byte)?;
        let op_checksig = Opcode::from_byte(op_checksig_byte[0]);

        if op_checksig.ne(&Opcode::OP_CHECKSIG) {
            return Err(Error::new(
                ErrorKind::InvalidData,
                "Invalid Data. Expected OP_CHECKSIG as last byte of the script.",
            ));
        }

        // Lastly, we build our script
        let mut script_builder = ScriptBuilder::new();
        script_builder
        .push_opcode(Opcode::PushBytes(65))?
        .push_bytes(&public_key_bytes)?
        .push_opcode(Opcode::OP_CHECKSIG)?;

        Ok(script_builder.build())
    }
}

2. Method to parse P2PKH

impl StandardScripts {
    // .. previous methods here
    /// Parse P2PKH
    pub fn parse_p2pkh(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        let mut opcode_buffer = [0u8; 1];

        bytes.read_exact(&mut opcode_buffer)?;
        // Parse second OPCODE as OP_HASH160
        let should_be_ophash160 = Opcode::from_byte(opcode_buffer[0]);
        if should_be_ophash160.ne(&Opcode::OP_HASH160) {
            return Err(Error::new(
                ErrorKind::InvalidData,
                "Invalid Data. Expected OP_HASH160 as second byte of the script.",
            ));
        }

        bytes.read_exact(&mut opcode_buffer)?;
            // Parse third OPCODE as `OP_PUSHBYTES_20`
            let should_be_op_pushbytes20 = Opcode::from_byte(opcode_buffer[0]);
            if should_be_op_pushbytes20.ne(&Opcode::PushBytes(20)) {
                return Err(Error::new(
                ErrorKind::InvalidData,
                    "Invalid Data. Expected OP_PUSHBYTES_20 as third byte of the script.",
                ));
             }

            // Get the 20 bytes of the Hash160
            let mut hash160_bytes = [0u8; 20];
            bytes.read_exact(&mut hash160_bytes)?;

            // Parse the next byte as OP_EQUALVERIFY
            bytes.read_exact(&mut opcode_buffer)?;
            let should_be_opequalverify = Opcode::from_byte(opcode_buffer[0]);
            if should_be_opequalverify.ne(&Opcode::OP_EQUALVERIFY) {
                return Err(Error::new(
                    ErrorKind::InvalidData,
                        "Invalid Data. Expected OP_EQUALVERIFY after reading 20 bytes after third byte of the script.",
                ));
            }

            // Parse the next byte as OP_CHECKSIG
            bytes.read_exact(&mut opcode_buffer)?;
            let should_be_opchecksing = Opcode::from_byte(opcode_buffer[0]);
            if should_be_opchecksing.ne(&Opcode::OP_CHECKSIG) {
                return Err(Error::new(
                    ErrorKind::InvalidData,
                    "Invalid Data. Expected OP_CHECKSIG after reading OP_EQUALVERIFY byte in the script.",
                ));
            }

            // Build our script into a Sctring
            let mut script_builder = ScriptBuilder::new();
            script_builder
                .push_opcode(Opcode::OP_DUP)?
                .push_opcode(Opcode::OP_HASH160)?
                .push_opcode(Opcode::PushBytes(20))?
                .push_bytes(&hash160_bytes)?
                .push_opcode(Opcode::OP_EQUALVERIFY)?
                .push_opcode(Opcode::OP_CHECKSIG)?;

                Ok(script_builder.build())
            }
}

3. Method to parse P2SH

impl StandardScripts {
// .. Previous methods here
   /// Parse P2SH
    pub fn parse_p2sh(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        let mut script_buffer = [0u8; 1];

        bytes.read_exact(&mut script_buffer)?;

        // Second OPCODE should be OP_PUSHBYTES_20
        let second_opcode = Opcode::from_byte(script_buffer[0]);
        if second_opcode.ne(&Opcode::PushBytes(20)) {
            return Self::to_io_error(
                "Invalid Data. Expected an OP_PUSHBYTES_20 opcode after OP_HASH160",
            );
        }

        // Read the 20 bytes of HASH160
        let mut bytes_20_buffer = [0u8; 20];
        bytes.read_exact(&mut bytes_20_buffer)?;

        bytes.read_exact(&mut script_buffer)?;
        let last_opcode = Opcode::from_byte(script_buffer[0]);
        if last_opcode.ne(&Opcode::OP_EQUAL) {
            return Self::to_io_error(
                "Invalid Data. Expected an OP_EQUAL opcode after reading 20 bytes",
            );
        }

        // Build the script into a String
        let mut script_builder = ScriptBuilder::new();
        script_builder
            .push_opcode(Opcode::OP_HASH160)?
            .push_opcode(Opcode::PushBytes(20))?
            .push_bytes(&bytes_20_buffer)?
            .push_opcode(Opcode::OP_EQUAL)?;

        Ok(script_builder.build())
    }
}

4. Parse Data(OP_RETURN)

impl StandardScripts {
    //.. Previous code here
    // Parse OP_RETURN
    pub fn parse_data(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        let mut script_buffer = [0u8; 1];

        bytes.read_exact(&mut script_buffer)?;
        // Get second OPCODE which is `OP_PUSHBYTES_*`
        let second_opcode = Opcode::from_byte(script_buffer[0]);
        /// Read the number of bytes specified by second OPCODE
        let data_bytes = second_opcode.read_bytes(bytes)?;

        let mut script_builder = ScriptBuilder::new();
        script_builder
        .push_opcode(Opcode::OP_RETURN)?
        .push_opcode(second_opcode)?
        .push_bytes(&data_bytes)?;

        Ok(script_builder.build())
    }
}

5. Parse P2WPKH

impl StandardScripts {
    //.. Previous code here
    /// Parse P2WPKH
    pub fn parse_p2wpkh(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        /// Read the next 20 bytes
        let mut pubkey_hash_bytes = [0u8; 20];
        bytes.read_exact(&mut pubkey_hash_bytes)?;

        let mut scripts = ScriptBuilder::new();
        scripts
        .push_opcode(Opcode::OP_0)?
        .push_opcode(Opcode::PushBytes(20))?
        .push_bytes(&pubkey_hash_bytes)?;

        Ok(scripts.build())
    }
}

6. Parse P2WSH

impl StandardScripts {
    //.. Previous code here
    /// Parse P2WSH
    pub fn parse_p2wsh(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        // Parse next 32 bytes
        let mut hash_bytes = [0u8; 32];
        bytes.read_exact(&mut hash_bytes)?;

        let mut scripts = ScriptBuilder::new();
        scripts
        .push_opcode(Opcode::OP_0)?
        .push_opcode(Opcode::PushBytes(32))?
        .push_bytes(&hash_bytes)?;

        Ok(scripts.build())
    }
}

7. Parse P2TR

impl StandardScripts {
    //.. Previous code here
    /// Parse P2TR
    pub fn parse_p2tr(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        // Parse next 32 bytes
        let mut hash_bytes = [0u8; 32];
        bytes.read_exact(&mut hash_bytes)?;

        let mut scripts = ScriptBuilder::new();
        scripts
        .push_opcode(Opcode::Num(1))?
        .push_opcode(Opcode::PushBytes(32))?
        .push_bytes(&hash_bytes)?;

        Ok(scripts.build())
    }
}

8. Parse P2MS

impl StandardScripts {
    // .. previous code here

    /// Parse a P2MS.
    /// Also checks to see if the number of public keys parsed is equal to number of public keys requires
    /// or if the parsed public keys are less than the threshold
    pub fn parse_p2ms(bytes: &mut Cursor<&[u8]>) -> io::Result<String> {
        let mut opcode_buffer = [0u8; 1];
        bytes.read_exact(&mut opcode_buffer)?;
        let threshold_opcode = Opcode::from_byte(opcode_buffer[0]);

        match threshold_opcode {
                Opcode::Num(_) | Opcode::OP_1 => {
                    let mut script_builder = ScriptBuilder::new();
                    script_builder.push_opcode(threshold_opcode)?;
                    // The number of public keys parsed
                    let mut pubkey_count = 0u8;
                    // The number of public keys specified in the scriptSig
                    let parsed_pubkey_count: u8;
                    let mut pushbytes_buffer = Vec::<u8>::new();

                    loop {
                        bytes.read_exact(&mut opcode_buffer)?;
                        let current_opcode = Opcode::from_byte(opcode_buffer[0]);

                        match current_opcode {
                            Opcode::Num(value) => {
                            parsed_pubkey_count = value;
                            script_builder.push_opcode(current_opcode)?;
                            //Break the loop if a `OP_1 to OP_16` is encountered
                            break;
                        }
                }
                Opcode::PushBytes(value) => {
                    let new_position = bytes.position() as usize + value as usize;
                    let read_bytes =
                    &bytes.get_ref()[bytes.position() as usize..new_position];
                    pushbytes_buffer.extend_from_slice(read_bytes);

                    script_builder
                        .push_opcode(current_opcode)?
                        .push_bytes(&pushbytes_buffer)?;

                    pushbytes_buffer.clear();
                    bytes.set_position(new_position as u64);
                    pubkey_count = pubkey_count.add(1);
                }
                _ => {
                    return Self::to_io_error(
                        "Invalid Script. Expected a PUSH_BYTES_* or OP_1..16",
                    )
                }
            }
        }

        if pubkey_count.ne(&parsed_pubkey_count) {
            return Self::to_io_error(
                "Invalid Script. The number of public keys for multisignature is less than or greater than the script requirements.",
            );
        }

        match threshold_opcode {
            Opcode::Num(threshold_inner) => {
                if parsed_pubkey_count.lt(&threshold_inner) {
                    return Self::to_io_error(
                        "Invalid Script. The number of public keys for multisignature is less the threshold.",
                    );
                }
            }
            _ => (),
        }

        // Parse next byte and check if it is OP_CHECKMULTISIG opcode
        bytes.read_exact(&mut opcode_buffer)?;
        let opcheck_multisig = Opcode::from_byte(opcode_buffer[0]);

        if opcheck_multisig.ne(&Opcode::OP_CHECKMULTISIG) {
            return Self::to_io_error(
                "Invalid Script. OP_CHECKMULTISIG opcode should be next",
            );
            }
            script_builder.push_opcode(Opcode::OP_CHECKMULTISIG)?;

            Ok(script_builder.build())
        }
        _ => Self::to_io_error("Invalid Script."),
        }
    }
}

Lastly, we need to test that our code works inside the main function in main.rs file and we assert that each outcome is successful using the .is_ok() method on std::io::Result

fn main() {
    // .. Previous code here

    let p2pk_bytes = hex!("410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ac");
    let mut p2pk = Cursor::new(p2pk_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2pk);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2pkh_bytes = hex!("76a914000000000000000000000000000000000000000088ac");
    let mut p2pkh = Cursor::new(p2pkh_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2pkh);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2sh_bytes = hex!("a914748284390f9e263a4b766a75d0633c50426eb87587");
    let mut p2sh = Cursor::new(p2sh_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2sh);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let op_return_bytes = hex!("6a0b68656c6c6f20776f726c64");
    let mut op_return = Cursor::new(op_return_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut op_return);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2wpkh_bytes = hex!("00140000000000000000000000000000000000000000");
    let mut p2wpkh = Cursor::new(p2wpkh_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2wpkh);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2wsh_bytes = hex!("00200000000000000000000000000000000000000000000000000000000000000000");
    let mut p2wsh = Cursor::new(p2wsh_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2wsh);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2tr_bytes = hex!("51200000000000000000000000000000000000000000000000000000000000000000");
    let mut p2tr = Cursor::new(p2tr_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2tr);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2ms_3_bytes = hex!("524104d81fd577272bbe73308c93009eec5dc9fc319fc1ee2e7066e17220a5d47a18314578be2faea34b9f1f8ca078f8621acd4bc22897b03daa422b9bf56646b342a24104ec3afff0b2b66e8152e9018fe3be3fc92b30bf886b3487a525997d00fd9da2d012dce5d5275854adc3106572a5d1e12d4211b228429f5a7b2f7ba92eb0475bb14104b49b496684b02855bc32f5daefa2e2e406db4418f3b86bca5195600951c7d918cdbe5e6d3736ec2abf2dd7610995c3086976b2c0c7b4e459d10b34a316d5a5e753ae");
    let mut p2ms_3 = Cursor::new(p2ms_3_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2ms_3);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());

    let p2ms_2_bytes = hex!("51210000000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000000000000000000000000000000000000000000052ae");
    let mut p2ms_2 = Cursor::new(p2ms_2_bytes.as_ref());
    let outcome = StandardScripts::parse(&mut p2ms_2);
    assert!(outcome.is_ok());
    dbg!(&outcome.unwrap());
}

That’s it. We have successfully parsed the input scriptSig part.

References

  1. Repository — https://github.com/448-OG/BitcoinTransactions

  2. https://wiki.bitcoinsv.io/index.php/Opcodes_used_in_Bitcoin_Script

  3. https://learnmeabitcoin.com/technical/transaction/input/scriptsig/

  4. Bitcoin scripts — https://github.com/jimmysong/programmingbitcoin/blob/master/ch13.asciidoc

More from this blog

4

448-OG Decentralized Infrastructure Blog

11 posts

Payments, Networking and Decentralized Infrastructure