Parsing Input scriptSig in Bitcoin Transactions Using Rust Programming Language
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.
- 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
Pay to Public Key (P2PK) — This is rarely used.
<Public Key> OP_CHECKSIGPay to Public Key Hash (P2PKH)
OP_DUP OP_HASH160 <PublicKey Hash> OP_EQUAL OP_CHECKSIGPay to Multi-signature (P2MS) — limited to 15 keys
M <Public Key 1> <Public Key 2> … <Public Key N> N OP_CHECKMULTISIG— whereMis the threshold andNis the number of signaturesPay to Script Hash (P2SH)
OP_HASH160 <20 bytes of the hash> OP_EQUALPay to Witness Public Key Hash (P2WPKH)
OP_0 *<20-byte hash>*Pay to Witness Script Hash (P2WSH)
OP_0 <32-byte hash>Pay to Tap Root (P2TR)
OP_1 <32-byte hash>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:
P2PK —
OP_PUSHBYTES_65P2PKH —
OP_DUPP2MS —
OP_1 — OP_16 OP_PUSHBYTES_*P2SH —
OP_HASH160P2WPKH —
OP_0 OP_PUSHBYTES_20P2WSH —
OP_0 OP_PUSHBYTES_32P2TR —
OP_1 OP_PUSHBYTES_32Data (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_65then parse asP2PKFirst OPCODE is
OP_DUPthen parse asP2PKHFirst OPCODE is
OP_RETURNthen parse asData(OP_RETURN)First OPCODE is
OP_0and second OPCODE isOP_PUSHBYTES_20then parse asP2WPKHFirst OPCODE is
OP_0and second OPCODE isOP_PUSHBYTES_32then parse asP2WSHFirst OP_CODE is between
OP_1andOP_16and second OPCODE isOP_PUSHBYTES_32then parse asP2TRFirst OP_CODE is between
OP_1andOP_16and second OPCODE isOP_PUSHBYTES_* then try parsing asP2MSelse return anstd::io::ErrorwithErrorKind::InvalidDatawith 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))
}
}
- 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.

