Skip to content

Commit a89055b

Browse files
committed
refactor(lazer): change verify_message to a contract call, add test
1 parent 1f38bdc commit a89055b

File tree

9 files changed

+5463
-765
lines changed

9 files changed

+5463
-765
lines changed

lazer/Cargo.lock

Lines changed: 5225 additions & 675 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lazer/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ resolver = "2"
33
members = [
44
"sdk/rust/protocol",
55
"contracts/solana/programs/pyth-lazer-solana-contract",
6-
"sdk/solana",
76
]
87

98
# TODO: only for solana programs

lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "pyth-lazer-solana-contract"
33
version = "0.1.0"
44
edition = "2021"
5-
description = "Pyth Lazer Solana contract."
5+
description = "Pyth Lazer Solana contract and SDK."
66
license = "Apache-2.0"
77
repository = "https://github.com/pyth-network/pyth-crosschain"
88

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

2121
[dependencies]
22+
pyth-lazer-protocol = { version = "0.1.0", path = "../../../../sdk/rust/protocol" }
23+
2224
anchor-lang = "0.30.1"
25+
bytemuck = "1.20.0"
26+
byteorder = "1.5.0"
27+
thiserror = "2.0.3"
28+
29+
[dev-dependencies]
30+
hex = "0.4.3"
31+
solana-program-test = "1.18.26"
32+
solana-sdk = "1.18.26"
33+
tokio = { version = "1.40.0", features = ["full"] }

lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
use {
2-
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES},
3-
std::mem::size_of,
4-
};
5-
6-
declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");
1+
mod signature;
72

83
pub mod storage {
94
use anchor_lang::prelude::{pubkey, Pubkey};
@@ -21,6 +16,18 @@ pub mod storage {
2116
}
2217
}
2318

19+
use {
20+
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES},
21+
std::mem::size_of,
22+
};
23+
24+
pub use {
25+
crate::signature::{ed25519_program_args, Ed25519SignatureOffsets},
26+
pyth_lazer_protocol as protocol,
27+
};
28+
29+
declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");
30+
2431
pub const MAX_NUM_TRUSTED_SIGNERS: usize = 2;
2532

2633
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, AnchorSerialize, AnchorDeserialize)]
@@ -54,6 +61,8 @@ pub const STORAGE_SEED: &[u8] = b"storage";
5461

5562
#[program]
5663
pub mod pyth_lazer_solana_contract {
64+
use signature::VerifiedMessage;
65+
5766
use super::*;
5867

5968
pub fn initialize(ctx: Context<Initialize>, top_authority: Pubkey) -> Result<()> {
@@ -102,6 +111,36 @@ pub mod pyth_lazer_solana_contract {
102111
.expect("num signers overflow");
103112
Ok(())
104113
}
114+
115+
/// Verifies a ed25519 signature on Solana by checking that the transaction contains
116+
/// a correct call to the built-in `ed25519_program`.
117+
///
118+
/// - `message_data` is the signed message that is being verified.
119+
/// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
120+
/// within the transaction. This instruction must precede the current instruction.
121+
/// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
122+
/// - `message_offset` is the offset of the signed message within the
123+
/// input data for the current instruction.
124+
pub fn verify_message(
125+
ctx: Context<VerifyMessage>,
126+
message_data: Vec<u8>,
127+
ed25519_instruction_index: u16,
128+
signature_index: u8,
129+
message_offset: u16,
130+
) -> Result<VerifiedMessage> {
131+
signature::verify_message(
132+
&ctx.accounts.storage,
133+
&ctx.accounts.sysvar,
134+
&message_data,
135+
ed25519_instruction_index,
136+
signature_index,
137+
message_offset,
138+
)
139+
.map_err(|err| {
140+
msg!("signature verification error: {:?}", err);
141+
err.into()
142+
})
143+
}
105144
}
106145

107146
#[derive(Accounts)]
@@ -130,3 +169,13 @@ pub struct Update<'info> {
130169
)]
131170
pub storage: Account<'info, Storage>,
132171
}
172+
173+
#[derive(Accounts)]
174+
pub struct VerifyMessage<'info> {
175+
#[account(
176+
seeds = [STORAGE_SEED],
177+
bump,
178+
)]
179+
pub storage: Account<'info, Storage>,
180+
pub sysvar: AccountInfo<'info>,
181+
}

lazer/sdk/solana/src/signature.rs renamed to lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/signature.rs

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use {
2-
anchor_lang::{prelude::Clock, AccountDeserialize},
2+
crate::Storage,
3+
anchor_lang::{
4+
prelude::{borsh, AccountInfo, Clock, ProgramError, Pubkey, SolanaSysvar},
5+
solana_program::{ed25519_program, pubkey::PUBKEY_BYTES, sysvar},
6+
AnchorDeserialize, AnchorSerialize,
7+
},
38
bytemuck::{cast_slice, checked::try_cast_slice, Pod, Zeroable},
49
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-
},
1210
thiserror::Error,
1311
};
1412

@@ -44,33 +42,32 @@ pub struct Ed25519SignatureOffsets {
4442
pub message_instruction_index: u16,
4543
}
4644

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.
54-
pub 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,
45+
impl Ed25519SignatureOffsets {
46+
/// Sets up `Ed25519SignatureOffsets` for verifying the Pyth Lazer message signature.
47+
/// - `message` is the Pyth Lazer message being sent.
48+
/// - `instruction_index` is the index of that instruction within the transaction.
49+
/// - `starting_offset` is the offset of the Pyth Lazer message within the instruction data.
50+
///
51+
/// Panics if `starting_offset` is invalid or the `instruction_data` is not long enough to
52+
/// contain the message.
53+
pub fn new(message: &[u8], instruction_index: u16, starting_offset: u16) -> Self {
54+
let signature_offset = starting_offset + MAGIC_LEN;
55+
let public_key_offset = signature_offset + SIGNATURE_LEN;
56+
let message_data_size_offset = public_key_offset + PUBKEY_LEN;
57+
let message_data_offset = message_data_size_offset + MESSAGE_SIZE_LEN;
58+
let message_data_size = LE::read_u16(
59+
&message[(message_data_size_offset - starting_offset).into()
60+
..(message_data_offset - starting_offset).into()],
61+
);
62+
Ed25519SignatureOffsets {
63+
signature_offset,
64+
signature_instruction_index: instruction_index,
65+
public_key_offset,
66+
public_key_instruction_index: instruction_index,
67+
message_data_offset,
68+
message_data_size,
69+
message_instruction_index: instruction_index,
70+
}
7471
}
7572
}
7673

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

8885
/// A message with a verified ed25519 signature.
89-
#[derive(Debug, Clone, Copy)]
90-
pub struct VerifiedMessage<'a> {
86+
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
87+
pub struct VerifiedMessage {
9188
/// Public key that signed the message.
92-
pub public_key: &'a [u8],
89+
pub public_key: Pubkey,
9390
/// Signed message payload.
94-
pub payload: &'a [u8],
91+
pub payload: Vec<u8>,
9592
}
9693

9794
#[derive(Debug, Error)]
@@ -145,6 +142,12 @@ impl From<SignatureVerificationError> for ProgramError {
145142
}
146143
}
147144

145+
impl From<SignatureVerificationError> for anchor_lang::error::Error {
146+
fn from(value: SignatureVerificationError) -> Self {
147+
ProgramError::from(value).into()
148+
}
149+
}
150+
148151
/// Verifies a ed25519 signature on Solana by checking that the transaction contains
149152
/// a correct call to the built-in `ed25519_program`.
150153
///
@@ -154,24 +157,14 @@ impl From<SignatureVerificationError> for ProgramError {
154157
/// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
155158
/// - `message_offset` is the offset of the signed message within the
156159
/// input data for the current instruction.
157-
pub fn verify_message<'a>(
158-
pyth_storage_account: &AccountInfo,
160+
pub fn verify_message(
161+
storage: &Storage,
159162
instruction_sysvar: &AccountInfo,
160-
message_data: &'a [u8],
163+
message_data: &[u8],
161164
ed25519_instruction_index: u16,
162165
signature_index: u8,
163166
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-
167+
) -> Result<VerifiedMessage, SignatureVerificationError> {
175168
const SOLANA_FORMAT_MAGIC_LE: u32 = 2182742457;
176169

177170
let self_instruction_index =
@@ -300,7 +293,7 @@ pub fn verify_message<'a>(
300293
};
301294

302295
Ok(VerifiedMessage {
303-
public_key,
304-
payload,
296+
public_key: Pubkey::new_from_array(public_key.try_into().unwrap()),
297+
payload: payload.to_vec(),
305298
})
306299
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use {
2+
anchor_lang::{prelude::AccountMeta, InstructionData},
3+
pyth_lazer_solana_contract::ed25519_program_args,
4+
solana_program_test::ProgramTest,
5+
solana_sdk::{
6+
ed25519_program, instruction::Instruction, signer::Signer, system_program, sysvar,
7+
transaction::Transaction,
8+
},
9+
std::env,
10+
};
11+
12+
#[tokio::test]
13+
async fn test1() {
14+
if env::var("SBF_OUT_DIR").is_err() {
15+
env::set_var(
16+
"SBF_OUT_DIR",
17+
format!(
18+
"{}/../../../../target/sbf-solana-solana/release",
19+
env::var("CARGO_MANIFEST_DIR").unwrap()
20+
),
21+
);
22+
}
23+
println!("if add_program fails, run `cargo build-sbf` first.");
24+
let program_test = ProgramTest::new(
25+
"pyth_lazer_solana_contract",
26+
pyth_lazer_solana_contract::ID,
27+
None,
28+
);
29+
let (mut banks_client, payer, recent_blockhash) = program_test.start().await;
30+
31+
let mut transaction_init_contract = Transaction::new_with_payer(
32+
&[Instruction::new_with_bytes(
33+
pyth_lazer_solana_contract::ID,
34+
&pyth_lazer_solana_contract::instruction::Initialize {
35+
top_authority: payer.pubkey(),
36+
}
37+
.data(),
38+
vec![
39+
AccountMeta::new(payer.pubkey(), true),
40+
AccountMeta::new(pyth_lazer_solana_contract::storage::ID, false),
41+
AccountMeta::new_readonly(system_program::ID, false),
42+
],
43+
)],
44+
Some(&payer.pubkey()),
45+
);
46+
transaction_init_contract.sign(&[&payer], recent_blockhash);
47+
banks_client
48+
.process_transaction(transaction_init_contract)
49+
.await
50+
.unwrap();
51+
52+
let verifying_key =
53+
hex::decode("74313a6525edf99936aa1477e94c72bc5cc617b21745f5f03296f3154461f214").unwrap();
54+
let message = hex::decode(
55+
"b9011a82e5cddee2c1bd364c8c57e1c98a6a28d194afcad410ff412226c8b2ae931ff59a57147cb47c7307\
56+
afc2a0a1abec4dd7e835a5b7113cf5aeac13a745c6bed6c60074313a6525edf99936aa1477e94c72bc5cc61\
57+
7b21745f5f03296f3154461f2141c0075d3c7931c9773f30a240600010102000000010000e1f50500000000",
58+
)
59+
.unwrap();
60+
61+
let mut transaction_set_trusted = Transaction::new_with_payer(
62+
&[Instruction::new_with_bytes(
63+
pyth_lazer_solana_contract::ID,
64+
&pyth_lazer_solana_contract::instruction::Update {
65+
trusted_signer: verifying_key.try_into().unwrap(),
66+
expires_at: i64::MAX,
67+
}
68+
.data(),
69+
vec![
70+
AccountMeta::new(payer.pubkey(), true),
71+
AccountMeta::new(pyth_lazer_solana_contract::storage::ID, false),
72+
],
73+
)],
74+
Some(&payer.pubkey()),
75+
);
76+
transaction_set_trusted.sign(&[&payer], recent_blockhash);
77+
banks_client
78+
.process_transaction(transaction_set_trusted)
79+
.await
80+
.unwrap();
81+
82+
// Instruction #0 will be ed25519 instruction;
83+
// Instruction #1 will be our contract instruction.
84+
let instruction_index = 1;
85+
// 8 bytes for Anchor header, 4 bytes for Vec length.
86+
let message_offset = 12;
87+
let ed25519_args = dbg!(pyth_lazer_solana_contract::Ed25519SignatureOffsets::new(
88+
&message,
89+
instruction_index,
90+
message_offset,
91+
));
92+
93+
println!("ok1");
94+
let mut transaction_verify = Transaction::new_with_payer(
95+
&[
96+
Instruction::new_with_bytes(
97+
ed25519_program::ID,
98+
&ed25519_program_args(&[ed25519_args]),
99+
vec![],
100+
),
101+
Instruction::new_with_bytes(
102+
pyth_lazer_solana_contract::ID,
103+
&pyth_lazer_solana_contract::instruction::VerifyMessage {
104+
message_data: message,
105+
ed25519_instruction_index: 0,
106+
signature_index: 0,
107+
message_offset,
108+
}
109+
.data(),
110+
vec![
111+
AccountMeta::new_readonly(pyth_lazer_solana_contract::storage::ID, false),
112+
AccountMeta::new_readonly(sysvar::instructions::ID, false),
113+
],
114+
),
115+
],
116+
Some(&payer.pubkey()),
117+
);
118+
transaction_verify.sign(&[&payer], recent_blockhash);
119+
banks_client
120+
.process_transaction(transaction_verify)
121+
.await
122+
.unwrap();
123+
}

lazer/sdk/solana/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)