wormhole/solana/bridge/program/src/api/verify_signature.rs

238 lines
7.2 KiB
Rust

use solitaire::*;
use crate::{
accounts::{
GuardianSet,
GuardianSetDerivationData,
SignatureSet,
SignatureSetDerivationData,
},
error::Error::{
GuardianSetMismatch,
InstructionAtWrongIndex,
InvalidHash,
InvalidSecpInstruction,
},
MAX_LEN_GUARDIAN_KEYS,
};
use byteorder::ByteOrder;
use sha3::Digest;
use solana_program::program_error::ProgramError;
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
use std::io::Write;
#[derive(FromAccounts)]
pub struct VerifySignatures<'b> {
/// Payer for account creation
pub payer: Mut<Signer<Info<'b>>>,
/// Guardian set of the signatures
pub guardian_set: GuardianSet<'b, { AccountState::Initialized }>,
/// Signature Account
pub signature_set: Mut<SignatureSet<'b, { AccountState::MaybeInitialized }>>,
/// Instruction reflection account (special sysvar)
pub instruction_acc: Info<'b>,
}
impl<'b> InstructionContext<'b> for VerifySignatures<'b> {
}
impl From<&VerifySignatures<'_>> for GuardianSetDerivationData {
fn from(data: &VerifySignatures<'_>) -> Self {
GuardianSetDerivationData {
index: data.guardian_set.index,
}
}
}
impl From<[u8; 32]> for SignatureSetDerivationData {
fn from(hash: [u8; 32]) -> Self {
SignatureSetDerivationData { hash }
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct VerifySignaturesData {
/// Guardian set of the signatures
pub hash: [u8; 32],
/// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS],
}
/// 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: &ExecutionContext,
accs: &mut VerifySignatures,
data: VerifySignaturesData,
) -> Result<()> {
accs.guardian_set
.verify_derivation(ctx.program_id, &(&*accs).into())?;
let sig_infos: Vec<SigInfo> = 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();
let current_instruction = solana_program::sysvar::instructions::load_current_index(
&accs.instruction_acc.try_borrow_mut_data()?,
);
if current_instruction == 0 {
return Err(InstructionAtWrongIndex.into());
}
// The previous ix must be a secp verification instruction
let secp_ix_index = (current_instruction - 1) as u8;
let secp_ix = solana_program::sysvar::instructions::load_instruction_at(
secp_ix_index as usize,
&accs.instruction_acc.try_borrow_mut_data()?,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
// Check that the instruction is actually for the secp program
if secp_ix.program_id != solana_program::secp256k1_program::id() {
return Err(InvalidSecpInstruction.into());
}
let secp_data_len = secp_ix.data.len();
if secp_data_len < 2 {
return Err(InvalidSecpInstruction.into());
}
let sig_len = secp_ix.data[0];
let mut index = 1;
let mut secp_ixs: Vec<SecpInstructionPart> = Vec::with_capacity(sig_len as usize);
for i in 0..sig_len {
let sig_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let sig_ix = secp_ix.data[index];
index += 1;
let address_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let address_ix = secp_ix.data[index];
index += 1;
let msg_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_size = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_ix = secp_ix.data[index];
index += 1;
if address_ix != secp_ix_index || msg_ix != secp_ix_index || sig_ix != secp_ix_index {
return Err(InvalidSecpInstruction.into());
}
let address: &[u8] = &secp_ix.data[address_offset..address_offset + 20];
let signature: &[u8] = &secp_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(InvalidSecpInstruction.into());
}
}
secp_ixs.push(SecpInstructionPart {
address,
signature,
msg_offset,
msg_size,
});
}
if sig_infos.len() != secp_ixs.len() {
return Err(ProgramError::InvalidArgument.into());
}
// Extract message which is encoded in Solana Secp256k1 instruction data.
let message = &secp_ix.data
[secp_ixs[0].msg_offset as usize..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
// Hash the message part, which contains the serialized VAA body.
let msg_hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
if let Err(e) = h.write(message) {
return Err(e.into());
};
h.finalize().into()
};
if msg_hash != data.hash {
return Err(InvalidHash.into());
}
// Confirm at this point that the derivation succeeds, we didn't have a signature set with the
// correct hash until this point.
accs.signature_set
.verify_derivation(ctx.program_id, &msg_hash.into())?;
if !accs.signature_set.is_initialized() {
accs.signature_set.signatures = vec![[0u8; 65]; 19];
accs.signature_set.guardian_set_index = accs.guardian_set.index;
accs.signature_set.hash = data.hash;
accs.signature_set
.create(&msg_hash.into(), ctx, accs.payer.key, Exempt)?;
} else {
// If the account already existed, check that the parameters match
if accs.signature_set.guardian_set_index != accs.guardian_set.index {
return Err(GuardianSetMismatch.into());
}
if accs.signature_set.hash != data.hash {
return Err(InvalidHash.into());
}
}
// Write sigs of checked addresses into sig_state
for s in sig_infos {
if s.signer_index > accs.guardian_set.num_guardians() {
return Err(ProgramError::InvalidArgument.into());
}
if s.sig_index + 1 > sig_len {
return Err(ProgramError::InvalidArgument.into());
}
let key = accs.guardian_set.keys[s.signer_index as usize];
// Check key in ix
if key != secp_ixs[s.sig_index as usize].address {
return Err(ProgramError::InvalidArgument.into());
}
// Overwritten content should be zeros except double signs by the signer or harmless replays
accs.signature_set.signatures[s.signer_index as usize]
.copy_from_slice(&secp_ixs[s.sig_index as usize].signature);
}
Ok(())
}