Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,900 changes: 5,225 additions & 675 deletions lazer/Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion lazer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ resolver = "2"
members = [
"sdk/rust/protocol",
"contracts/solana/programs/pyth-lazer-solana-contract",
"sdk/solana",
]

# TODO: only for solana programs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "pyth-lazer-solana-contract"
version = "0.1.0"
edition = "2021"
description = "Pyth Lazer Solana contract."
description = "Pyth Lazer Solana contract and SDK."
license = "Apache-2.0"
repository = "https://github.com/pyth-network/pyth-crosschain"

Expand All @@ -19,4 +19,15 @@ no-log-ix-name = []
idl-build = ["anchor-lang/idl-build"]

[dependencies]
pyth-lazer-protocol = { version = "0.1.0", path = "../../../../sdk/rust/protocol" }

anchor-lang = "0.30.1"
bytemuck = "1.20.0"
byteorder = "1.5.0"
thiserror = "2.0.3"

[dev-dependencies]
hex = "0.4.3"
solana-program-test = "1.18.26"
solana-sdk = "1.18.26"
tokio = { version = "1.40.0", features = ["full"] }
120 changes: 105 additions & 15 deletions lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
mod signature;

use {
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES},
crate::signature::VerifiedMessage,
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES, system_program},
std::mem::size_of,
};

declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");

pub mod storage {
use anchor_lang::prelude::{pubkey, Pubkey};

pub const ID: Pubkey = pubkey!("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");
pub use {
crate::signature::{ed25519_program_args, Ed25519SignatureOffsets},
pyth_lazer_protocol as protocol,
};

#[test]
fn test_storage_id() {
use {crate::STORAGE_SEED, anchor_lang::prelude::Pubkey};
declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");

assert_eq!(
Pubkey::find_program_address(&[STORAGE_SEED], &super::ID).0,
ID
);
}
pub const STORAGE_ID: Pubkey = pubkey!("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");
pub const TREASURY_ID: Pubkey = pubkey!("EN4aB3soE5iuCG2fGj2r5fksh4kLRVPV8g7N86vXm8WM");

#[test]
fn test_ids() {
assert_eq!(
Pubkey::find_program_address(&[STORAGE_SEED], &ID).0,
STORAGE_ID
);
assert_eq!(
Pubkey::find_program_address(&[TREASURY_SEED], &ID).0,
TREASURY_ID
);
}

pub const MAX_NUM_TRUSTED_SIGNERS: usize = 2;
Expand All @@ -37,12 +44,14 @@ impl TrustedSignerInfo {
pub struct Storage {
pub top_authority: Pubkey,
pub num_trusted_signers: u8,
pub single_update_fee_in_lamports: u64,
pub trusted_signers: [TrustedSignerInfo; MAX_NUM_TRUSTED_SIGNERS],
}

impl Storage {
const SERIALIZED_LEN: usize = PUBKEY_BYTES
+ size_of::<u8>()
+ size_of::<u64>()
+ TrustedSignerInfo::SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS;

pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] {
Expand All @@ -51,13 +60,15 @@ impl Storage {
}

pub const STORAGE_SEED: &[u8] = b"storage";
pub const TREASURY_SEED: &[u8] = b"treasury";

#[program]
pub mod pyth_lazer_solana_contract {
use super::*;

pub fn initialize(ctx: Context<Initialize>, top_authority: Pubkey) -> Result<()> {
ctx.accounts.storage.top_authority = top_authority;
ctx.accounts.storage.single_update_fee_in_lamports = 1;
Ok(())
}

Expand Down Expand Up @@ -102,6 +113,47 @@ pub mod pyth_lazer_solana_contract {
.expect("num signers overflow");
Ok(())
}

/// Verifies a ed25519 signature on Solana by checking that the transaction contains
/// a correct call to the built-in `ed25519_program`.
///
/// - `message_data` is the signed message that is being verified.
/// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
/// within the transaction. This instruction must precede the current instruction.
/// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
/// - `message_offset` is the offset of the signed message within the
/// input data for the current instruction.
pub fn verify_message(
ctx: Context<VerifyMessage>,
message_data: Vec<u8>,
ed25519_instruction_index: u16,
signature_index: u8,
message_offset: u16,
) -> Result<VerifiedMessage> {
system_program::transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.treasury.to_account_info(),
},
),
ctx.accounts.storage.single_update_fee_in_lamports,
)?;

signature::verify_message(
&ctx.accounts.storage,
&ctx.accounts.instructions_sysvar,
&message_data,
ed25519_instruction_index,
signature_index,
message_offset,
)
.map_err(|err| {
msg!("signature verification error: {:?}", err);
err.into()
})
}
}

#[derive(Accounts)]
Expand All @@ -116,6 +168,18 @@ pub struct Initialize<'info> {
bump,
)]
pub storage: Account<'info, Storage>,
#[account(
init,
payer = payer,
space = 0,
owner = system_program::ID,
seeds = [TREASURY_SEED],
bump,
)]
/// CHECK: this is a system program account but using anchor's `SystemAccount`
/// results in invalid output from the Accounts proc macro. No extra checks
/// are necessary because all necessary constraints are specified in the attribute.
pub treasury: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}

Expand All @@ -130,3 +194,29 @@ pub struct Update<'info> {
)]
pub storage: Account<'info, Storage>,
}

#[derive(Accounts)]
pub struct VerifyMessage<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
seeds = [STORAGE_SEED],
bump,
)]
pub storage: Account<'info, Storage>,
#[account(
mut,
owner = system_program::ID,
seeds = [TREASURY_SEED],
bump,
)]
/// CHECK: this is a system program account but using anchor's `SystemAccount`
/// results in invalid output from the Accounts proc macro. No extra checks
/// are necessary because all necessary constraints are specified in the attribute.
pub treasury: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: account ID is checked in Solana SDK during calls
/// (e.g. in `sysvar::instructions::load_instruction_at_checked`).
/// This account is not usable with anchor's `Program` account type because it's not executable.
pub instructions_sysvar: AccountInfo<'info>,
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use {
anchor_lang::{prelude::Clock, AccountDeserialize},
crate::Storage,
anchor_lang::{
prelude::{borsh, AccountInfo, Clock, ProgramError, Pubkey, SolanaSysvar},
solana_program::{ed25519_program, pubkey::PUBKEY_BYTES, sysvar},
AnchorDeserialize, AnchorSerialize,
},
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,
};

Expand Down Expand Up @@ -44,33 +42,32 @@ pub struct Ed25519SignatureOffsets {
pub message_instruction_index: u16,
}

/// Sets up `Ed25519SignatureOffsets` for verifying the Pyth Lazer message signature.
/// - `instruction_data` must be the *full* input data for your contract's instruction.
/// - `instruction_index` is the index of that instruction within the transaction.
/// - `starting_offset` is the offset of the Pyth Lazer message within the instruction data.
///
/// Panics if `starting_offset` is invalid or the `instruction_data` is not long enough to
/// contain the message.
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,
impl Ed25519SignatureOffsets {
/// Sets up `Ed25519SignatureOffsets` for verifying the Pyth Lazer message signature.
/// - `message` is the Pyth Lazer message being sent.
/// - `instruction_index` is the index of that instruction within the transaction.
/// - `starting_offset` is the offset of the Pyth Lazer message within the instruction data.
///
/// Panics if `starting_offset` is invalid or the `instruction_data` is not long enough to
/// contain the message.
pub fn new(message: &[u8], instruction_index: u16, starting_offset: u16) -> Self {
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(
&message[(message_data_size_offset - starting_offset).into()
..(message_data_offset - starting_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,
}
}
}

Expand All @@ -86,12 +83,12 @@ pub fn ed25519_program_args(signatures: &[Ed25519SignatureOffsets]) -> Vec<u8> {
}

/// A message with a verified ed25519 signature.
#[derive(Debug, Clone, Copy)]
pub struct VerifiedMessage<'a> {
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
pub struct VerifiedMessage {
/// Public key that signed the message.
pub public_key: &'a [u8],
pub public_key: Pubkey,
/// Signed message payload.
pub payload: &'a [u8],
pub payload: Vec<u8>,
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -145,6 +142,12 @@ impl From<SignatureVerificationError> for ProgramError {
}
}

impl From<SignatureVerificationError> for anchor_lang::error::Error {
fn from(value: SignatureVerificationError) -> Self {
ProgramError::from(value).into()
}
}

/// Verifies a ed25519 signature on Solana by checking that the transaction contains
/// a correct call to the built-in `ed25519_program`.
///
Expand All @@ -154,28 +157,18 @@ impl From<SignatureVerificationError> for ProgramError {
/// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
/// - `message_offset` is the offset of the signed message within the
/// input data for the current instruction.
pub fn verify_message<'a>(
pyth_storage_account: &AccountInfo,
instruction_sysvar: &AccountInfo,
message_data: &'a [u8],
pub fn verify_message(
storage: &Storage,
instructions_sysvar: &AccountInfo,
message_data: &[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)?
};

) -> Result<VerifiedMessage, SignatureVerificationError> {
const SOLANA_FORMAT_MAGIC_LE: u32 = 2182742457;

let self_instruction_index =
sysvar::instructions::load_current_index_checked(instruction_sysvar)
sysvar::instructions::load_current_index_checked(instructions_sysvar)
.map_err(SignatureVerificationError::LoadCurrentIndexFailed)?;

if ed25519_instruction_index >= self_instruction_index {
Expand All @@ -184,7 +177,7 @@ pub fn verify_message<'a>(

let instruction = sysvar::instructions::load_instruction_at_checked(
ed25519_instruction_index.into(),
instruction_sysvar,
instructions_sysvar,
)
.map_err(SignatureVerificationError::LoadInstructionAtFailed)?;

Expand Down Expand Up @@ -300,7 +293,7 @@ pub fn verify_message<'a>(
};

Ok(VerifiedMessage {
public_key,
payload,
public_key: Pubkey::new_from_array(public_key.try_into().unwrap()),
payload: payload.to_vec(),
})
}
Loading
Loading