solana-transaction-annotation 0.1.0-beta.1

Utilities for working with Solana transaction annotations on chain
Documentation
use anchor_lang::prelude::*;
use solana_program::{ed25519_program, instruction::Instruction, sysvar};

pub struct Annotation {
    pub signer: Pubkey,
    pub data: Vec<u8>,
}

#[derive(Debug)]
pub enum ParseError {
    /// Failed to load an instruction at the given index
    Ed25519InstructionNotFound,
    /// Instruction at the given index is not an Ed25519 instruction
    Ed25519InstructionInvalidProgramId,
    /// Ed25519 instruction data is invalid
    Ed25519InstructionInvalidData,
    /// Ed25519 instruction doesn't verify exactly one signature
    Ed25519InstructionInvalidSigCount,
}

impl Annotation {
    /// Parses an annotation from an Ed25519 instruction. The Ed25519 instruction must verify
    /// exactly one signature.
    pub fn parse(
        instructions: &AccountInfo<'_>,
        ed25519_instruction_index: u16,
    ) -> std::result::Result<Annotation, ParseError> {
        let ed25519_ix = sysvar::instructions::load_instruction_at_checked(
            ed25519_instruction_index.into(),
            instructions,
        )
        .map_err(|_| ParseError::Ed25519InstructionNotFound)?;

        if ed25519_ix.program_id != ed25519_program::ID {
            return Err(ParseError::Ed25519InstructionInvalidProgramId);
        }

        const STRUCT_SIZE: usize = std::mem::size_of::<Ed25519InstructionData>();

        let ed25519_data_len = ed25519_ix.data.len();
        if ed25519_data_len < STRUCT_SIZE {
            return Err(ParseError::Ed25519InstructionInvalidData);
        }
        let ed25519_data_slice = &ed25519_ix.data[..STRUCT_SIZE];
        let ed25519_data: Ed25519InstructionData = unsafe {
            (ed25519_data_slice.as_ptr() as *const Ed25519InstructionData).read_unaligned()
        };

        // Require Ed25519 instruction to check exactly one signature
        if ed25519_data.signatures_count != 1 {
            return Err(ParseError::Ed25519InstructionInvalidSigCount);
        }

        if ed25519_data.public_key_instruction_index == u16::MAX
            && ed25519_data.message_instruction_index == u16::MAX
        {
            // Both public key and message are included in the Ed25519 instruction itself
            return Ok(Annotation {
                // Should never fail because the Ed25519 program has already verified that the public key is valid
                signer: parse_public_key_may_panic(&ed25519_ix, &ed25519_data),
                data: parse_message(&ed25519_ix, &ed25519_data),
            });
        }

        if ed25519_data.public_key_instruction_index != ed25519_data.message_instruction_index {
            if ed25519_data.public_key_instruction_index != u16::MAX {
                // Message is included in the Ed25519 instruction, but public key is included in another instruction
                let other_ix = sysvar::instructions::load_instruction_at_checked(
                    ed25519_data.public_key_instruction_index.into(),
                    instructions,
                )
                .unwrap(); // Should never fail because the Ed25519 program has already verified that the instruction exists
                return Ok(Annotation {
                    // Should never fail because the Ed25519 program has already verified that the public key is valid
                    signer: parse_public_key_may_panic(&other_ix, &ed25519_data),
                    data: parse_message(&ed25519_ix, &ed25519_data),
                });
            } else {
                // Public key is included in the Ed25519 instruction, but message is included in another instruction
                let other_ix = sysvar::instructions::load_instruction_at_checked(
                    ed25519_data.message_instruction_index.into(),
                    instructions,
                )
                .unwrap(); // Should never fail because the Ed25519 program has already verified that the instruction exists
                return Ok(Annotation {
                    // Should never fail because the Ed25519 program has already verified that the public key is valid
                    signer: parse_public_key_may_panic(&ed25519_ix, &ed25519_data),
                    data: parse_message(&other_ix, &ed25519_data),
                });
            }
        }

        // Public key and message are included in the same instruction outside of the Ed25519 instruction
        let other_ix = sysvar::instructions::load_instruction_at_checked(
            ed25519_data.public_key_instruction_index.into(),
            instructions,
        )
        .unwrap(); // Should never fail because the Ed25519 program has already verified that the instruction exists
        Ok(Annotation {
            // Should never fail because the Ed25519 program has already verified that the public key is valid
            signer: parse_public_key_may_panic(&other_ix, &ed25519_data),
            data: parse_message(&other_ix, &ed25519_data),
        })
    }
}

#[repr(packed)]
struct Ed25519InstructionData {
    signatures_count: u8,
    _padding_byte: u8,
    _signature_offset: u16,
    _signature_instruction_index: u16,
    public_key_offset: u16,
    public_key_instruction_index: u16,
    message_data_offset: u16,
    message_data_size: u16,
    message_instruction_index: u16,
}

fn parse_public_key_may_panic(
    instruction: &Instruction,
    ed25519_data: &Ed25519InstructionData,
) -> Pubkey {
    const PUBKEY_LEN: usize = 32;
    let offset = ed25519_data.public_key_offset as usize;
    Pubkey::try_from(instruction.data[offset..offset + PUBKEY_LEN].to_vec()).unwrap()
}

fn parse_message(instruction: &Instruction, ed25519_data: &Ed25519InstructionData) -> Vec<u8> {
    let offset = ed25519_data.message_data_offset as usize;
    let size = ed25519_data.message_data_size as usize;
    instruction.data[offset..offset + size].to_vec()
}