use {
anchor_lang::{prelude::Clock, AccountDeserialize},
bytemuck::{cast_slice, checked::try_cast_slice, Pod, Zeroable},
byteorder::{ByteOrder, LE},
solana_program::{
account_info::AccountInfo,
ed25519_program,
program_error::ProgramError,
pubkey::PUBKEY_BYTES,
sysvar::{self, Sysvar},
},
thiserror::Error,
};
const ED25519_PROGRAM_INPUT_HEADER_LEN: usize = 2;
const SIGNATURE_LEN: u16 = 64;
const PUBKEY_LEN: u16 = 32;
const MAGIC_LEN: u16 = 4;
const MESSAGE_SIZE_LEN: u16 = 2;
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
pub struct Ed25519SignatureOffsets {
pub signature_offset: u16,
pub signature_instruction_index: u16,
pub public_key_offset: u16,
pub public_key_instruction_index: u16,
pub message_data_offset: u16,
pub message_data_size: u16,
pub message_instruction_index: u16,
}
pub fn signature_offsets(
instruction_data: &[u8],
instruction_index: u16,
starting_offset: u16,
) -> Ed25519SignatureOffsets {
let signature_offset = starting_offset + MAGIC_LEN;
let public_key_offset = signature_offset + SIGNATURE_LEN;
let message_data_size_offset = public_key_offset + PUBKEY_LEN;
let message_data_offset = message_data_size_offset + MESSAGE_SIZE_LEN;
let message_data_size = LE::read_u16(
&instruction_data[message_data_size_offset.into()..message_data_offset.into()],
);
Ed25519SignatureOffsets {
signature_offset,
signature_instruction_index: instruction_index,
public_key_offset,
public_key_instruction_index: instruction_index,
message_data_offset,
message_data_size,
message_instruction_index: instruction_index,
}
}
pub fn ed25519_program_args(signatures: &[Ed25519SignatureOffsets]) -> Vec<u8> {
let padding = 0u8;
let mut signature_args = vec![
signatures.len().try_into().expect("too many signatures"),
padding,
];
signature_args.extend_from_slice(cast_slice(signatures));
signature_args
}
#[derive(Debug, Clone, Copy)]
pub struct VerifiedMessage<'a> {
pub public_key: &'a [u8],
pub payload: &'a [u8],
}
#[derive(Debug, Error)]
pub enum SignatureVerificationError {
#[error("ed25519 instruction must precede current instruction")]
Ed25519InstructionMustPrecedeCurrentInstruction,
#[error("load instruction at failed")]
LoadInstructionAtFailed(#[source] ProgramError),
#[error("load current index failed")]
LoadCurrentIndexFailed(#[source] ProgramError),
#[error("load current index failed")]
ClockGetFailed(#[source] ProgramError),
#[error("invalid ed25519 instruction program")]
InvalidEd25519InstructionProgramId,
#[error("invalid ed25519 instruction data length")]
InvalidEd25519InstructionDataLength,
#[error("invalid signature index")]
InvalidSignatureIndex,
#[error("invalid signature offset")]
InvalidSignatureOffset,
#[error("invalid public key offset")]
InvalidPublicKeyOffset,
#[error("invalid message offset")]
InvalidMessageOffset,
#[error("invalid message data size")]
InvalidMessageDataSize,
#[error("invalid instruction index")]
InvalidInstructionIndex,
#[error("message offset overflow")]
MessageOffsetOverflow,
#[error("format magic mismatch")]
FormatMagicMismatch,
#[error("invalid storage account id")]
InvalidStorageAccountId,
#[error("invalid storage data")]
InvalidStorageData,
#[error("not a trusted signer")]
NotTrustedSigner,
}
impl From<SignatureVerificationError> for ProgramError {
fn from(value: SignatureVerificationError) -> Self {
match value {
SignatureVerificationError::LoadInstructionAtFailed(e)
| SignatureVerificationError::ClockGetFailed(e)
| SignatureVerificationError::LoadCurrentIndexFailed(e) => e,
SignatureVerificationError::InvalidStorageData => ProgramError::InvalidAccountData,
SignatureVerificationError::NotTrustedSigner => ProgramError::MissingRequiredSignature,
_ => ProgramError::InvalidInstructionData,
}
}
}
pub fn verify_message<'a>(
pyth_storage_account: &AccountInfo,
instruction_sysvar: &AccountInfo,
message_data: &'a [u8],
ed25519_instruction_index: u16,
signature_index: u8,
message_offset: u16,
) -> Result<VerifiedMessage<'a>, SignatureVerificationError> {
if pyth_storage_account.key != &pyth_lazer_solana_contract::storage::ID {
return Err(SignatureVerificationError::InvalidStorageAccountId);
}
let storage = {
let storage_data = pyth_storage_account.data.borrow();
let mut storage_data: &[u8] = *storage_data;
pyth_lazer_solana_contract::Storage::try_deserialize(&mut storage_data)
.map_err(|_| SignatureVerificationError::InvalidStorageData)?
};
const SOLANA_FORMAT_MAGIC_LE: u32 = 2182742457;
let self_instruction_index =
sysvar::instructions::load_current_index_checked(instruction_sysvar)
.map_err(SignatureVerificationError::LoadCurrentIndexFailed)?;
if ed25519_instruction_index >= self_instruction_index {
return Err(SignatureVerificationError::Ed25519InstructionMustPrecedeCurrentInstruction);
}
let instruction = sysvar::instructions::load_instruction_at_checked(
ed25519_instruction_index.into(),
instruction_sysvar,
)
.map_err(SignatureVerificationError::LoadInstructionAtFailed)?;
if instruction.program_id != ed25519_program::ID {
return Err(SignatureVerificationError::InvalidEd25519InstructionProgramId);
}
if instruction.data.len() < ED25519_PROGRAM_INPUT_HEADER_LEN {
return Err(SignatureVerificationError::InvalidEd25519InstructionDataLength);
}
let num_signatures = instruction.data[0];
if signature_index >= num_signatures {
return Err(SignatureVerificationError::InvalidSignatureIndex);
}
let args: &[Ed25519SignatureOffsets] =
try_cast_slice(&instruction.data[ED25519_PROGRAM_INPUT_HEADER_LEN..])
.map_err(|_| SignatureVerificationError::InvalidEd25519InstructionDataLength)?;
let args_len = args
.len()
.try_into()
.map_err(|_| SignatureVerificationError::InvalidEd25519InstructionDataLength)?;
if signature_index >= args_len {
return Err(SignatureVerificationError::InvalidSignatureIndex);
}
let offsets = &args[usize::from(signature_index)];
let expected_signature_offset = message_offset
.checked_add(MAGIC_LEN)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
if offsets.signature_offset != expected_signature_offset {
return Err(SignatureVerificationError::InvalidSignatureOffset);
}
let magic = LE::read_u32(&message_data[..MAGIC_LEN.into()]);
if magic != SOLANA_FORMAT_MAGIC_LE {
return Err(SignatureVerificationError::FormatMagicMismatch);
}
let expected_public_key_offset = expected_signature_offset
.checked_add(SIGNATURE_LEN)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
if offsets.public_key_offset != expected_public_key_offset {
return Err(SignatureVerificationError::InvalidPublicKeyOffset);
}
let expected_message_size_offset = expected_public_key_offset
.checked_add(PUBKEY_LEN)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
let expected_message_data_offset = expected_message_size_offset
.checked_add(MESSAGE_SIZE_LEN)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
if offsets.message_data_offset != expected_message_data_offset {
return Err(SignatureVerificationError::InvalidMessageOffset);
}
let expected_message_size = {
let start = usize::from(
expected_message_size_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = usize::from(
expected_message_data_offset
.checked_sub(message_offset)
.unwrap(),
);
LE::read_u16(&message_data[start..end])
};
if offsets.message_data_size != expected_message_size {
return Err(SignatureVerificationError::InvalidMessageDataSize);
}
if offsets.signature_instruction_index != self_instruction_index
|| offsets.public_key_instruction_index != self_instruction_index
|| offsets.message_instruction_index != self_instruction_index
{
return Err(SignatureVerificationError::InvalidInstructionIndex);
}
let public_key = {
let start = usize::from(
expected_public_key_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = start
.checked_add(PUBKEY_BYTES)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
&message_data[start..end]
};
let now = Clock::get()
.map_err(SignatureVerificationError::ClockGetFailed)?
.unix_timestamp;
if !storage
.initialized_trusted_signers()
.iter()
.any(|s| s.pubkey.as_ref() == public_key && s.expires_at > now)
{
return Err(SignatureVerificationError::NotTrustedSigner);
}
let payload = {
let start = usize::from(
expected_message_data_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = start
.checked_add(expected_message_size.into())
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
&message_data[start..end]
};
Ok(VerifiedMessage {
public_key,
payload,
})
}