ordinals-parser 0.1.0

A lightweight parser for Bitcoin Ordinals inscriptions
Documentation
use std::collections::BTreeMap;

use bitcoin::script::{Instruction, Script};
use bitcoin::opcodes::all::{OP_PUSHBYTES_0, OP_IF, OP_ENDIF};
use bitcoin::{Transaction, Witness};

use crate::error::{Error, Result};
use crate::inscription::Inscription;

/// The Ordinals protocol identifier
pub const PROTOCOL_ID: [u8; 7] = *b"ord\x01\0\0\0";

/// The tag for identifying the inscription body
pub const BODY_TAG: [u8; 0] = [];

/// Helper function to check if an instruction is an empty push
fn is_empty_push(instruction: &Instruction) -> bool {
    match instruction {
        Instruction::PushBytes(bytes) => bytes.is_empty(),
        Instruction::Op(OP_PUSHBYTES_0) => true,
        _ => false,
    }
}

/// Parse a bitcoin transaction's witness data to find inscriptions
pub fn parse_transaction_inscriptions(tx: &Transaction) -> Vec<Inscription> {
    tx.input
        .iter()
        .enumerate()
        .flat_map(|(input_index, input)| {
            parse_input_inscriptions(input_index, &input.witness)
        })
        .collect()
}

/// Determines if the script at the ith position in the witness corresponds to an input with inscriptions
pub fn is_inscription_input(witness: &Witness, i: usize) -> bool {
    // Get the script at position i in the witness
    if witness.len() <= i {
        eprintln!("[DEBUG:is_inscription_input] Witness length {} is less than requested index {}", witness.len(), i);
        return false;
    }
    
    // Convert the witness stack element to a Script
    let script_bytes = witness.nth(i).unwrap();
    eprintln!("[DEBUG:is_inscription_input] Script bytes length: {}", script_bytes.len());
    let script = Script::from_bytes(script_bytes);
    
    // Create an iterator over the script's instructions
    let instructions_result = script.instructions().collect::<std::result::Result<Vec<_>, _>>();
    let mut instructions = match instructions_result {
        Ok(inst) => {
            eprintln!("[DEBUG:is_inscription_input] Successfully parsed {} script instructions", inst.len());
            inst.into_iter()
        },
        Err(e) => {
            eprintln!("[DEBUG:is_inscription_input] Failed to parse script instructions: {:?}", e);
            return false;
        }
    };
    
    // Check for OP_0
    match instructions.next() {
        Some(Instruction::Op(OP_PUSHBYTES_0)) => {
            eprintln!("[DEBUG:is_inscription_input] Found OP_0");
        },
        other => {
            eprintln!("[DEBUG:is_inscription_input] Expected OP_0, got {:?}", other);
            return false;
        }
    }
    
    // Check for OP_IF
    match instructions.next() {
        Some(Instruction::Op(OP_IF)) => {
            eprintln!("[DEBUG:is_inscription_input] Found OP_IF");
        },
        other => {
            eprintln!("[DEBUG:is_inscription_input] Expected OP_IF, got {:?}", other);
            return false;
        }
    }
    
    // Check for the protocol identifier
    match instructions.next() {
        Some(Instruction::PushBytes(bytes)) if bytes.as_bytes() == PROTOCOL_ID => {
            eprintln!("[DEBUG:is_inscription_input] Found protocol identifier");
        },
        other => {
            eprintln!("[DEBUG:is_inscription_input] Expected protocol identifier, got {:?}", other);
            return false;
        }
    }
    
    eprintln!("[DEBUG:is_inscription_input] Script contains a valid inscription pattern");
    true
}

/// Parse inscriptions from a witness
pub fn parse_input_inscriptions(input_index: usize, witness: &Witness) -> Vec<Inscription> {
    // Check if witness is empty
    if witness.len() == 0 {
        return Vec::new();
    }
    
    let mut all_inscriptions = Vec::new();
    
    // Process each witness item
    for witness_index in 0..witness.len() {
        // Convert the witness stack element to a Script
        if let Some(script_bytes) = witness.nth(witness_index) {
            let script = Script::from_bytes(script_bytes);
            
            // Get the script instructions
            let instructions_result = script.instructions().collect::<std::result::Result<Vec<_>, _>>();
            let instructions = match instructions_result {
                Ok(instructions) => instructions,
                Err(_) => continue,
            };
            
            // Check for modern inscription format (P2TR taproot)
            if instructions.len() >= 5 {
                let mut contains_ord_marker = false;
                let mut content_type = None;
                let mut content_data = None;
                
                // Check for the sequence that typically appears in inscriptions:
                // OP_0 OP_IF "ord" OP_1 "content-type" "body"...
                for i in 0..instructions.len() - 3 {
                    match &instructions[i..i+4] {
                        // Look for pattern: PushBytes([]) OP_IF PushBytes("ord") PushBytes([1])
                        [Instruction::PushBytes(empty), Instruction::Op(OP_IF), 
                         Instruction::PushBytes(ord_marker), Instruction::PushBytes(_version)]
                         if empty.is_empty() && 
                            (ord_marker.as_bytes() == b"ord" || ord_marker.as_bytes() == PROTOCOL_ID) => {
                            
                            contains_ord_marker = true;
                            
                            // Check for content type in the next instruction
                            if i + 4 < instructions.len() {
                                if let Instruction::PushBytes(ct) = &instructions[i+4] {
                                    content_type = Some(ct.as_bytes().to_vec());
                                    
                                    // Check for content data in the following instructions
                                    if i + 6 < instructions.len() && is_empty_push(&instructions[i+5]) {
                                        if let Instruction::PushBytes(data) = &instructions[i+6] {
                                            content_data = Some(data.as_bytes().to_vec());
                                        }
                                    }
                                }
                            }
                            break;
                        }
                        _ => {}
                    }
                }
                
                if contains_ord_marker {
                    let inscription = Inscription {
                        content_type,
                        body: content_data,
                        ..Default::default()
                    };
                    all_inscriptions.push(inscription);
                    continue;
                }
            }
            
            // Standard envelope processing approach
            let mut envelopes = Vec::new();
            let mut in_envelope = false;
            let mut current_instructions = Vec::new();
            
            // Scan for pattern: OP_0 OP_IF <protocol_id> ... OP_ENDIF
            for instruction in instructions.iter() {
                match (&instruction, in_envelope) {
                    (Instruction::Op(OP_PUSHBYTES_0), false) => {
                        in_envelope = true;
                        current_instructions.push(instruction.clone());
                    }
                    (Instruction::Op(OP_IF), true) => {
                        current_instructions.push(instruction.clone());
                    }
                    (Instruction::Op(OP_ENDIF), true) => {
                        in_envelope = false;
                        current_instructions.push(instruction.clone());
                        if !current_instructions.is_empty() {
                            if let Ok(inscription) = parse_envelope(input_index, &current_instructions) {
                                envelopes.push(inscription);
                            }
                            current_instructions = Vec::new();
                        }
                    }
                    (_, true) => {
                        current_instructions.push(instruction.clone());
                    }
                    _ => {
                        // Skip instructions outside of an envelope
                    }
                }
            }
            
            // Handle case where the envelope wasn't properly closed
            if in_envelope && !current_instructions.is_empty() {
                if let Ok(inscription) = parse_envelope(input_index, &current_instructions) {
                    envelopes.push(inscription);
                }
            }
            
            all_inscriptions.extend(envelopes);
        }
    }
    
    all_inscriptions
}

/// Parse an envelope as an inscription
pub fn parse_envelope(_input_index: usize, instructions: &[Instruction]) -> Result<Inscription> {
    let mut instructions = instructions.iter();

    // The current instruction should be OP_0
    match instructions.next() {
        Some(Instruction::Op(OP_PUSHBYTES_0)) => {}
        _ => return Err(Error::ScriptError("Expected OP_0".to_string())),
    }

    // The next instruction should be OP_IF
    match instructions.next() {
        Some(Instruction::Op(OP_IF)) => {}
        _ => return Err(Error::ScriptError("Expected OP_IF".to_string())),
    }

    // The next instruction should be the protocol identifier
    match instructions.next() {
        Some(Instruction::PushBytes(protocol_id)) if protocol_id.as_bytes() == PROTOCOL_ID => {}
        _ => return Err(Error::ScriptError("Invalid protocol ID".to_string())),
    }

    let mut fields = BTreeMap::new();
    let mut field = None;
    let mut body = Vec::new();
    let mut unrecognized_even_field = false;
    let duplicate_field = false;
    let mut incomplete_field = false;

    for instruction in instructions {
        match instruction {
            Instruction::PushBytes(bytes) => {
                match field.take() {
                    Some(tag) if tag != BODY_TAG => {
                        fields
                            .entry(tag)
                            .or_insert_with(Vec::new)
                            .push(bytes.as_bytes());
                    }
                    Some(_) => {
                        body.push(bytes.as_bytes());
                    }
                    None => {
                        if bytes.as_bytes() == BODY_TAG {
                            field = Some(bytes.as_bytes());
                        } else if bytes.len() == 1 && bytes.as_bytes()[0] % 2 == 1 {
                            field = Some(bytes.as_bytes());
                        } else {
                            unrecognized_even_field = true;
                        }
                    }
                }
            }
            Instruction::Op(OP_ENDIF) => {
                if field.is_some() {
                    incomplete_field = true;
                }
                field = None;
                break;
            }
            _ => {
                return Err(Error::ScriptError("Invalid instruction".to_string()));
            }
        }
    }

    if field.is_some() {
        incomplete_field = true;
    }

    let mut inscription = Inscription {
        unrecognized_even_field,
        duplicate_field,
        incomplete_field,
        ..Default::default()
    };

    // Get content type (tag = 1)
    if let Some(value) = parse_tag_with_value(&mut fields, &[1]) {
        inscription.content_type = Some(value);
    }

    // Get content encoding (tag = 9)
    if let Some(value) = parse_tag_with_value(&mut fields, &[9]) {
        inscription.content_encoding = Some(value);
    }

    // Get delegate (tag = 11)
    if let Some(value) = parse_tag_with_value(&mut fields, &[11]) {
        inscription.delegate = Some(value);
    }

    // Get metadata (tag = 5)
    if let Some(value) = parse_tag_with_value(&mut fields, &[5]) {
        inscription.metadata = Some(value);
    }

    // Get metaprotocol (tag = 7)
    if let Some(value) = parse_tag_with_value(&mut fields, &[7]) {
        inscription.metaprotocol = Some(value);
    }

    // Get parents (tag = 3)
    inscription.parents = parse_array_tag_with_values(&mut fields, &[3]);

    // Get pointer (tag = 2)
    if let Some(value) = parse_tag_with_value(&mut fields, &[2]) {
        inscription.pointer = Some(value);
    }

    // Get properties (tag = 17)
    if let Some(value) = parse_tag_with_value(&mut fields, &[17]) {
        inscription.properties = Some(value);
    }

    // Get rune (tag = 13)
    if let Some(value) = parse_tag_with_value(&mut fields, &[13]) {
        inscription.rune = Some(value);
    }

    if !body.is_empty() {
        inscription.body = Some(body.concat());
    }

    Ok(inscription)
}

// Helper function for parsing tagged values
fn parse_tag_with_value(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, tag: &[u8]) -> Option<Vec<u8>> {
    let values = fields.get_mut(tag)?;
    
    if values.is_empty() {
        return None;
    }
    
    let value = values.remove(0).to_vec();
    
    if values.is_empty() {
        fields.remove(tag);
    }
    
    Some(value)
}

// Helper function for parsing arrays of tagged values
fn parse_array_tag_with_values(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, tag: &[u8]) -> Vec<Vec<u8>> {
    fields
        .remove(tag)
        .unwrap_or_default()
        .into_iter()
        .map(|v| v.to_vec())
        .collect()
}