1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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()
}