pyth_lazer_sdk/
signature.rs

1use {
2    anchor_lang::{prelude::Clock, AccountDeserialize},
3    bytemuck::{cast_slice, checked::try_cast_slice, Pod, Zeroable},
4    byteorder::{ByteOrder, LE},
5    solana_program::{
6        account_info::AccountInfo,
7        ed25519_program,
8        program_error::ProgramError,
9        pubkey::PUBKEY_BYTES,
10        sysvar::{self, Sysvar},
11    },
12    thiserror::Error,
13};
14
15const ED25519_PROGRAM_INPUT_HEADER_LEN: usize = 2;
16
17const SIGNATURE_LEN: u16 = 64;
18const PUBKEY_LEN: u16 = 32;
19const MAGIC_LEN: u16 = 4;
20const MESSAGE_SIZE_LEN: u16 = 2;
21
22/// Part of the inputs to the built-in `ed25519_program` on Solana that represents a single
23/// signature verification request.
24///
25/// `ed25519_program` does not receive the signature data directly. Instead, it receives
26/// these fields that indicate the location of the signature data within data of other
27/// instructions within the same transaction.
28#[derive(Debug, Clone, Copy, Zeroable, Pod)]
29#[repr(C)]
30pub struct Ed25519SignatureOffsets {
31    /// Offset to the ed25519 signature within the instruction data.
32    pub signature_offset: u16,
33    /// Index of the instruction that contains the signature.
34    pub signature_instruction_index: u16,
35    /// Offset to the public key within the instruction data.
36    pub public_key_offset: u16,
37    /// Index of the instruction that contains the public key.
38    pub public_key_instruction_index: u16,
39    /// Offset to the signed payload within the instruction data.
40    pub message_data_offset: u16,
41    // Size of the signed payload.
42    pub message_data_size: u16,
43    /// Index of the instruction that contains the signed payload.
44    pub message_instruction_index: u16,
45}
46
47/// Sets up `Ed25519SignatureOffsets` for verifying the Pyth Lazer message signature.
48/// - `instruction_data` must be the *full* input data for your contract's instruction.
49/// - `instruction_index` is the index of that instruction within the transaction.
50/// - `starting_offset` is the offset of the Pyth Lazer message within the instruction data.
51///
52/// Panics if `starting_offset` is invalid or the `instruction_data` is not long enough to
53/// contain the message.
54pub fn signature_offsets(
55    instruction_data: &[u8],
56    instruction_index: u16,
57    starting_offset: u16,
58) -> Ed25519SignatureOffsets {
59    let signature_offset = starting_offset + MAGIC_LEN;
60    let public_key_offset = signature_offset + SIGNATURE_LEN;
61    let message_data_size_offset = public_key_offset + PUBKEY_LEN;
62    let message_data_offset = message_data_size_offset + MESSAGE_SIZE_LEN;
63    let message_data_size = LE::read_u16(
64        &instruction_data[message_data_size_offset.into()..message_data_offset.into()],
65    );
66    Ed25519SignatureOffsets {
67        signature_offset,
68        signature_instruction_index: instruction_index,
69        public_key_offset,
70        public_key_instruction_index: instruction_index,
71        message_data_offset,
72        message_data_size,
73        message_instruction_index: instruction_index,
74    }
75}
76
77/// Creates inputs to the built-in `ed25519_program` on Solana that verifies signatures.
78pub fn ed25519_program_args(signatures: &[Ed25519SignatureOffsets]) -> Vec<u8> {
79    let padding = 0u8;
80    let mut signature_args = vec![
81        signatures.len().try_into().expect("too many signatures"),
82        padding,
83    ];
84    signature_args.extend_from_slice(cast_slice(signatures));
85    signature_args
86}
87
88/// A message with a verified ed25519 signature.
89#[derive(Debug, Clone, Copy)]
90pub struct VerifiedMessage<'a> {
91    /// Public key that signed the message.
92    pub public_key: &'a [u8],
93    /// Signed message payload.
94    pub payload: &'a [u8],
95}
96
97#[derive(Debug, Error)]
98pub enum SignatureVerificationError {
99    #[error("ed25519 instruction must precede current instruction")]
100    Ed25519InstructionMustPrecedeCurrentInstruction,
101    #[error("load instruction at failed")]
102    LoadInstructionAtFailed(#[source] ProgramError),
103    #[error("load current index failed")]
104    LoadCurrentIndexFailed(#[source] ProgramError),
105    #[error("load current index failed")]
106    ClockGetFailed(#[source] ProgramError),
107    #[error("invalid ed25519 instruction program")]
108    InvalidEd25519InstructionProgramId,
109    #[error("invalid ed25519 instruction data length")]
110    InvalidEd25519InstructionDataLength,
111    #[error("invalid signature index")]
112    InvalidSignatureIndex,
113    #[error("invalid signature offset")]
114    InvalidSignatureOffset,
115    #[error("invalid public key offset")]
116    InvalidPublicKeyOffset,
117    #[error("invalid message offset")]
118    InvalidMessageOffset,
119    #[error("invalid message data size")]
120    InvalidMessageDataSize,
121    #[error("invalid instruction index")]
122    InvalidInstructionIndex,
123    #[error("message offset overflow")]
124    MessageOffsetOverflow,
125    #[error("format magic mismatch")]
126    FormatMagicMismatch,
127    #[error("invalid storage account id")]
128    InvalidStorageAccountId,
129    #[error("invalid storage data")]
130    InvalidStorageData,
131    #[error("not a trusted signer")]
132    NotTrustedSigner,
133}
134
135impl From<SignatureVerificationError> for ProgramError {
136    fn from(value: SignatureVerificationError) -> Self {
137        match value {
138            SignatureVerificationError::LoadInstructionAtFailed(e)
139            | SignatureVerificationError::ClockGetFailed(e)
140            | SignatureVerificationError::LoadCurrentIndexFailed(e) => e,
141            SignatureVerificationError::InvalidStorageData => ProgramError::InvalidAccountData,
142            SignatureVerificationError::NotTrustedSigner => ProgramError::MissingRequiredSignature,
143            _ => ProgramError::InvalidInstructionData,
144        }
145    }
146}
147
148/// Verifies a ed25519 signature on Solana by checking that the transaction contains
149/// a correct call to the built-in `ed25519_program`.
150///
151/// - `message_data` is the signed message that is being verified.
152/// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
153///   within the transaction. This instruction must precede the current instruction.
154/// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
155/// - `message_offset` is the offset of the signed message within the
156///   input data for the current instruction.
157pub fn verify_message<'a>(
158    pyth_storage_account: &AccountInfo,
159    instruction_sysvar: &AccountInfo,
160    message_data: &'a [u8],
161    ed25519_instruction_index: u16,
162    signature_index: u8,
163    message_offset: u16,
164) -> Result<VerifiedMessage<'a>, SignatureVerificationError> {
165    if pyth_storage_account.key != &pyth_lazer_solana_contract::storage::ID {
166        return Err(SignatureVerificationError::InvalidStorageAccountId);
167    }
168    let storage = {
169        let storage_data = pyth_storage_account.data.borrow();
170        let mut storage_data: &[u8] = *storage_data;
171        pyth_lazer_solana_contract::Storage::try_deserialize(&mut storage_data)
172            .map_err(|_| SignatureVerificationError::InvalidStorageData)?
173    };
174
175    const SOLANA_FORMAT_MAGIC_LE: u32 = 2182742457;
176
177    let self_instruction_index =
178        sysvar::instructions::load_current_index_checked(instruction_sysvar)
179            .map_err(SignatureVerificationError::LoadCurrentIndexFailed)?;
180
181    if ed25519_instruction_index >= self_instruction_index {
182        return Err(SignatureVerificationError::Ed25519InstructionMustPrecedeCurrentInstruction);
183    }
184
185    let instruction = sysvar::instructions::load_instruction_at_checked(
186        ed25519_instruction_index.into(),
187        instruction_sysvar,
188    )
189    .map_err(SignatureVerificationError::LoadInstructionAtFailed)?;
190
191    if instruction.program_id != ed25519_program::ID {
192        return Err(SignatureVerificationError::InvalidEd25519InstructionProgramId);
193    }
194    if instruction.data.len() < ED25519_PROGRAM_INPUT_HEADER_LEN {
195        return Err(SignatureVerificationError::InvalidEd25519InstructionDataLength);
196    }
197
198    let num_signatures = instruction.data[0];
199    if signature_index >= num_signatures {
200        return Err(SignatureVerificationError::InvalidSignatureIndex);
201    }
202    let args: &[Ed25519SignatureOffsets] =
203        try_cast_slice(&instruction.data[ED25519_PROGRAM_INPUT_HEADER_LEN..])
204            .map_err(|_| SignatureVerificationError::InvalidEd25519InstructionDataLength)?;
205
206    let args_len = args
207        .len()
208        .try_into()
209        .map_err(|_| SignatureVerificationError::InvalidEd25519InstructionDataLength)?;
210    if signature_index >= args_len {
211        return Err(SignatureVerificationError::InvalidSignatureIndex);
212    }
213    let offsets = &args[usize::from(signature_index)];
214
215    let expected_signature_offset = message_offset
216        .checked_add(MAGIC_LEN)
217        .ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
218    if offsets.signature_offset != expected_signature_offset {
219        return Err(SignatureVerificationError::InvalidSignatureOffset);
220    }
221
222    let magic = LE::read_u32(&message_data[..MAGIC_LEN.into()]);
223    if magic != SOLANA_FORMAT_MAGIC_LE {
224        return Err(SignatureVerificationError::FormatMagicMismatch);
225    }
226
227    let expected_public_key_offset = expected_signature_offset
228        .checked_add(SIGNATURE_LEN)
229        .ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
230    if offsets.public_key_offset != expected_public_key_offset {
231        return Err(SignatureVerificationError::InvalidPublicKeyOffset);
232    }
233
234    let expected_message_size_offset = expected_public_key_offset
235        .checked_add(PUBKEY_LEN)
236        .ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
237
238    let expected_message_data_offset = expected_message_size_offset
239        .checked_add(MESSAGE_SIZE_LEN)
240        .ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
241    if offsets.message_data_offset != expected_message_data_offset {
242        return Err(SignatureVerificationError::InvalidMessageOffset);
243    }
244
245    let expected_message_size = {
246        let start = usize::from(
247            expected_message_size_offset
248                .checked_sub(message_offset)
249                .unwrap(),
250        );
251        let end = usize::from(
252            expected_message_data_offset
253                .checked_sub(message_offset)
254                .unwrap(),
255        );
256        LE::read_u16(&message_data[start..end])
257    };
258    if offsets.message_data_size != expected_message_size {
259        return Err(SignatureVerificationError::InvalidMessageDataSize);
260    }
261    if offsets.signature_instruction_index != self_instruction_index
262        || offsets.public_key_instruction_index != self_instruction_index
263        || offsets.message_instruction_index != self_instruction_index
264    {
265        return Err(SignatureVerificationError::InvalidInstructionIndex);
266    }
267
268    let public_key = {
269        let start = usize::from(
270            expected_public_key_offset
271                .checked_sub(message_offset)
272                .unwrap(),
273        );
274        let end = start
275            .checked_add(PUBKEY_BYTES)
276            .ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
277        &message_data[start..end]
278    };
279    let now = Clock::get()
280        .map_err(SignatureVerificationError::ClockGetFailed)?
281        .unix_timestamp;
282    if !storage
283        .initialized_trusted_signers()
284        .iter()
285        .any(|s| s.pubkey.as_ref() == public_key && s.expires_at > now)
286    {
287        return Err(SignatureVerificationError::NotTrustedSigner);
288    }
289
290    let payload = {
291        let start = usize::from(
292            expected_message_data_offset
293                .checked_sub(message_offset)
294                .unwrap(),
295        );
296        let end = start
297            .checked_add(expected_message_size.into())
298            .ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
299        &message_data[start..end]
300    };
301
302    Ok(VerifiedMessage {
303        public_key,
304        payload,
305    })
306}