From ed98d9d3465644c55f9028c170851f491e861abf Mon Sep 17 00:00:00 2001 From: Reisen Date: Tue, 27 Apr 2021 12:45:02 +0000 Subject: [PATCH] Extract as much non-Anchor specifics into modules Change-Id: I380560c6a8c240c95ee6eda93e27c7248b2cd361 --- .../programs/anchor-bridge/src/account.rs | 7 + .../programs/anchor-bridge/src/api.rs | 4 + .../src/api/verify_signatures.rs | 138 +++++++++++++++ .../programs/anchor-bridge/src/lib.rs | 162 ++---------------- 4 files changed, 163 insertions(+), 148 deletions(-) create mode 100644 solana/anchor-bridge/programs/anchor-bridge/src/account.rs create mode 100644 solana/anchor-bridge/programs/anchor-bridge/src/api.rs create mode 100644 solana/anchor-bridge/programs/anchor-bridge/src/api/verify_signatures.rs diff --git a/solana/anchor-bridge/programs/anchor-bridge/src/account.rs b/solana/anchor-bridge/programs/anchor-bridge/src/account.rs new file mode 100644 index 000000000..4229f431b --- /dev/null +++ b/solana/anchor-bridge/programs/anchor-bridge/src/account.rs @@ -0,0 +1,7 @@ +use anchor_lang::{prelude::*, solana_program}; + +#[account] +pub struct BridgeInfo {} + +#[account] +pub struct GuardianSetInfo {} diff --git a/solana/anchor-bridge/programs/anchor-bridge/src/api.rs b/solana/anchor-bridge/programs/anchor-bridge/src/api.rs new file mode 100644 index 000000000..de5b4a5a4 --- /dev/null +++ b/solana/anchor-bridge/programs/anchor-bridge/src/api.rs @@ -0,0 +1,4 @@ +pub mod verify_signatures; + +// Re-expose underlying module functions and data, for consuming APIs to use. +pub use verify_signatures::*; diff --git a/solana/anchor-bridge/programs/anchor-bridge/src/api/verify_signatures.rs b/solana/anchor-bridge/programs/anchor-bridge/src/api/verify_signatures.rs new file mode 100644 index 000000000..85689bf8b --- /dev/null +++ b/solana/anchor-bridge/programs/anchor-bridge/src/api/verify_signatures.rs @@ -0,0 +1,138 @@ +use crate::{accounts, VerifySig, VerifySigsData}; +use anchor_lang::{prelude::*, solana_program}; +use byteorder::ByteOrder; +use sha3::Digest; +use std::io::Write; + +pub const MIN_SECP_PROGRAM_DATA_LEN: usize = 3; + +/// SigInfo contains metadata about signers in a VerifySignature ix +struct SigInfo { + /// index of the signer in the guardianset + signer_index: u8, + /// index of the signature in the secp instruction + sig_index: u8, +} + +struct SecpInstructionPart<'a> { + address: &'a [u8], + signature: &'a [u8], + msg_offset: u16, + msg_size: u16, +} + +pub fn verify_signatures(ctx: Context, data: VerifySigsData) -> ProgramResult { + let sig_infos: Vec = data + .signers + .iter() + .enumerate() + .filter_map(|(i, p)| { + if *p == -1 { + return None; + } + + return Some(SigInfo { + sig_index: *p as u8, + signer_index: i as u8, + }); + }) + .collect(); + + // We check this manually because the type-level checks are + // not available for Instructions yet. See the VerifySig + // struct for more info. + let ix_acc = &ctx.accounts.instruction_sysvar; + if *ix_acc.key != solana_program::sysvar::instructions::id() { + return Err(ProgramError::Custom(42)); + } + + let current_ix_idx = + solana_program::sysvar::instructions::load_current_index(&ix_acc.try_borrow_data()?); + + if current_ix_idx == 0 { + return Err(ProgramError::InvalidInstructionData); + } + + // Retrieve the previous instruction + let prev_ix_idx = (current_ix_idx - 1) as u8; + let prev_ix = solana_program::sysvar::instructions::load_instruction_at( + prev_ix_idx as usize, + &ix_acc.try_borrow_mut_data()?, + ) + .map_err(|_e| ProgramError::InvalidAccountData)?; + + // Does prev_ix call the right program? + if prev_ix.program_id != solana_program::secp256k1_program::id() { + return Err(ProgramError::InvalidArgument); + } + + // Is the data correctly sized? + let prev_data_len = prev_ix.data.len(); + if prev_data_len < MIN_SECP_PROGRAM_DATA_LEN { + return Err(ProgramError::InvalidAccountData); + } + + // Parse the instruction data for verification + let sig_len = prev_ix.data[0]; + let mut index = 1; + + let mut secp_ixs: Vec = Vec::with_capacity(sig_len as usize); + for i in 0..sig_len { + let sig_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]) as usize; + index += 2; + let sig_ix = prev_ix.data[index]; + index += 1; + let address_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]) as usize; + index += 2; + let address_ix = prev_ix.data[index]; + index += 1; + let msg_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]); + index += 2; + let msg_size = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]); + index += 2; + let msg_ix = prev_ix.data[index]; + index += 1; + + if address_ix != prev_ix_idx || msg_ix != prev_ix_idx || sig_ix != prev_ix_idx { + return Err(ProgramError::InvalidArgument); + } + + let address: &[u8] = &prev_ix.data[address_offset..address_offset + 20]; + let signature: &[u8] = &prev_ix.data[sig_offset..sig_offset + 65]; + + // Make sure that all messages are equal + if i > 0 { + if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size { + return Err(ProgramError::InvalidArgument); + } + } + secp_ixs.push(SecpInstructionPart { + address, + signature, + msg_offset, + msg_size, + }); + } + + if sig_infos.len() != secp_ixs.len() { + return Err(ProgramError::InvalidArgument); + } + + // Check message + let message = &prev_ix.data + [secp_ixs[0].msg_offset as usize..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize]; + + let mut h = sha3::Keccak256::default(); + if let Err(_) = h.write(message) { + return Err(ProgramError::InvalidArgument); + }; + let msg_hash: [u8; 32] = h.finalize().into(); + if msg_hash != data.hash { + return Err(ProgramError::InvalidArgument); + } + + // ------ 8>< *SNIP <>8 -------- + // In original bridge program specific bridge state checks follow + + Ok(()) +} diff --git a/solana/anchor-bridge/programs/anchor-bridge/src/lib.rs b/solana/anchor-bridge/programs/anchor-bridge/src/lib.rs index 711ecc3e3..343400c12 100644 --- a/solana/anchor-bridge/programs/anchor-bridge/src/lib.rs +++ b/solana/anchor-bridge/programs/anchor-bridge/src/lib.rs @@ -1,30 +1,12 @@ use anchor_lang::{prelude::*, solana_program}; -use byteorder::ByteOrder; -use sha3::Digest; -use std::io::Write; +mod account; +mod api; -#[account] -struct BridgeInfo {} - -#[account] -struct GuardianSetInfo {} - -#[derive(Accounts)] -pub struct VerifySig<'info> { - bridge: AccountInfo<'info>, - system: AccountInfo<'info>, - // TODO: Make this a `Sysvar<'info, Instructions>` like you can do - // with `Rent` when `Sysvar` gets implemented for `Instructions` inside Solana. - instruction_sysvar: AccountInfo<'info>, - bridge_info: ProgramState<'info, BridgeInfo>, - sig_info: AccountInfo<'info>, - guardian_set_info: ProgramState<'info, GuardianSetInfo>, - payer_info: AccountInfo<'info>, -} +use account::BridgeInfo; +use account::GuardianSetInfo; pub const MAX_LEN_GUARDIAN_KEYS: usize = 20; -pub const MIN_SECP_PROGRAM_DATA_LEN: usize = 2; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)] pub struct VerifySigsData { @@ -36,19 +18,15 @@ pub struct VerifySigsData { pub initial_creation: bool, } -/// SigInfo contains metadata about signers in a VerifySignature ix -struct SigInfo { - /// index of the signer in the guardianset - signer_index: u8, - /// index of the signature in the secp instruction - sig_index: u8, -} - -struct SecpInstructionPart<'a> { - address: &'a [u8], - signature: &'a [u8], - msg_offset: u16, - msg_size: u16, +#[derive(Accounts)] +pub struct VerifySig<'info> { + pub bridge: AccountInfo<'info>, + pub system: AccountInfo<'info>, + pub instruction_sysvar: AccountInfo<'info>, + pub bridge_info: ProgramState<'info, BridgeInfo>, + pub sig_info: AccountInfo<'info>, + pub guardian_set_info: ProgramState<'info, GuardianSetInfo>, + pub payer_info: AccountInfo<'info>, } #[program] @@ -56,118 +34,6 @@ pub mod anchor_bridge { use super::*; pub fn verify_signatures(ctx: Context, data: VerifySigsData) -> ProgramResult { - let sig_infos: Vec = data - .signers - .iter() - .enumerate() - .filter_map(|(i, p)| { - if *p == -1 { - return None; - } - - return Some(SigInfo { - sig_index: *p as u8, - signer_index: i as u8, - }); - }) - .collect(); - - // We check this manually because the type-level checks are - // not available for Instructions yet. See the VerifySig - // struct for more info. - let ix_acc = &ctx.accounts.instruction_sysvar; - if *ix_acc.key != solana_program::sysvar::instructions::id() { - return Err(ProgramError::Custom(42)); - } - - let current_ix_idx = - solana_program::sysvar::instructions::load_current_index(&ix_acc.try_borrow_data()?); - - if current_ix_idx == 0 { - return Err(ProgramError::InvalidInstructionData); - } - - // Retrieve the previous instruction - let prev_ix_idx = (current_ix_idx - 1) as u8; - let prev_ix = solana_program::sysvar::instructions::load_instruction_at( - prev_ix_idx as usize, - &ix_acc.try_borrow_mut_data()?, - ) - .map_err(|_e| ProgramError::InvalidAccountData)?; - - // Does prev_ix call the right program? - if prev_ix.program_id != solana_program::secp256k1_program::id() { - return Err(ProgramError::InvalidArgument); - } - - // Is the data correctly sized? - let prev_data_len = prev_ix.data.len(); - if prev_data_len < MIN_SECP_PROGRAM_DATA_LEN { - return Err(ProgramError::InvalidAccountData); - } - - // Parse the instruction data for verification - let sig_len = prev_ix.data[0]; - let mut index = 1; - - let mut secp_ixs: Vec = Vec::with_capacity(sig_len as usize); - for i in 0..sig_len { - let sig_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]) as usize; - index += 2; - let sig_ix = prev_ix.data[index]; - index += 1; - let address_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]) as usize; - index += 2; - let address_ix = prev_ix.data[index]; - index += 1; - let msg_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]); - index += 2; - let msg_size = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]); - index += 2; - let msg_ix = prev_ix.data[index]; - index += 1; - - if address_ix != prev_ix_idx || msg_ix != prev_ix_idx || sig_ix != prev_ix_idx { - return Err(ProgramError::InvalidArgument); - } - - let address: &[u8] = &prev_ix.data[address_offset..address_offset + 20]; - let signature: &[u8] = &prev_ix.data[sig_offset..sig_offset + 65]; - - // Make sure that all messages are equal - if i > 0 { - if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size { - return Err(ProgramError::InvalidArgument); - } - } - secp_ixs.push(SecpInstructionPart { - address, - signature, - msg_offset, - msg_size, - }); - } - - if sig_infos.len() != secp_ixs.len() { - return Err(ProgramError::InvalidArgument); - } - - // Check message - let message = &prev_ix.data[secp_ixs[0].msg_offset as usize - ..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize]; - - let mut h = sha3::Keccak256::default(); - if let Err(_) = h.write(message) { - return Err(ProgramError::InvalidArgument); - }; - let msg_hash: [u8; 32] = h.finalize().into(); - if msg_hash != data.hash { - return Err(ProgramError::InvalidArgument); - } - - // ------ 8>< *SNIP <>8 -------- - // In original bridge program specific bridge state checks follow - - Ok(()) + api::verify_signatures(ctx, data) } }