[solana] Post update atomic (#1210)
* Post update atomic * Checkpoint cli * Fix bug * Atomic * Cleanup * Cleanup * Cleanup * Cleanup * Comment * Add wormhole errors * refactor trim signatures
This commit is contained in:
parent
2d5e030149
commit
3458dd5b7d
|
@ -3027,6 +3027,7 @@ dependencies = [
|
|||
"solana-sdk",
|
||||
"tokio",
|
||||
"wormhole-core-bridge-solana",
|
||||
"wormhole-raw-vaas",
|
||||
"wormhole-sdk",
|
||||
]
|
||||
|
||||
|
|
|
@ -40,6 +40,17 @@ pub enum Action {
|
|||
#[clap(short = 'p', long, help = "Payload from Hermes")]
|
||||
payload: String,
|
||||
},
|
||||
#[clap(about = "Post a price update from Hermes to Solana in one transaction")]
|
||||
PostPriceUpdateAtomic {
|
||||
#[clap(short = 'p', long, help = "Payload from Hermes")]
|
||||
payload: String,
|
||||
#[clap(
|
||||
short = 'n',
|
||||
default_value = "5",
|
||||
help = "Number of signatures to verify. If n >= 5 this will fail because of the transaction size limit."
|
||||
)]
|
||||
n_signatures: usize,
|
||||
},
|
||||
#[clap(
|
||||
about = "Initialize a wormhole receiver contract by sequentially replaying the guardian set updates"
|
||||
)]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#![deny(warnings)]
|
||||
|
||||
|
||||
pub mod cli;
|
||||
|
||||
|
||||
use {
|
||||
anchor_client::anchor_lang::{
|
||||
InstructionData,
|
||||
|
@ -14,7 +15,10 @@ use {
|
|||
Action,
|
||||
Cli,
|
||||
},
|
||||
pyth_solana_receiver::state::config::DataSource,
|
||||
pyth_solana_receiver::{
|
||||
state::config::DataSource,
|
||||
PostUpdatesAtomicParams,
|
||||
},
|
||||
pythnet_sdk::wire::v1::{
|
||||
AccumulatorUpdateData,
|
||||
MerklePriceUpdate,
|
||||
|
@ -102,6 +106,25 @@ fn main() -> Result<()> {
|
|||
&merkle_price_updates[0],
|
||||
)?;
|
||||
}
|
||||
Action::PostPriceUpdateAtomic {
|
||||
payload,
|
||||
n_signatures,
|
||||
} => {
|
||||
let rpc_client = RpcClient::new(url);
|
||||
let payer =
|
||||
read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
|
||||
|
||||
let (vaa, merkle_price_updates) = deserialize_accumulator_update_data(&payload)?;
|
||||
|
||||
process_post_price_update_atomic(
|
||||
&rpc_client,
|
||||
&vaa,
|
||||
n_signatures,
|
||||
&wormhole,
|
||||
&payer,
|
||||
&merkle_price_updates[0],
|
||||
)?;
|
||||
}
|
||||
|
||||
Action::InitializeWormholeReceiver {} => {
|
||||
let rpc_client = RpcClient::new(url);
|
||||
|
@ -140,6 +163,7 @@ fn main() -> Result<()> {
|
|||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
Action::InitializePythReceiver {
|
||||
fee,
|
||||
emitter,
|
||||
|
@ -221,6 +245,55 @@ pub fn process_upgrade_guardian_set(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_post_price_update_atomic(
|
||||
rpc_client: &RpcClient,
|
||||
vaa: &[u8],
|
||||
n_signatures: usize,
|
||||
wormhole: &Pubkey,
|
||||
payer: &Keypair,
|
||||
merkle_price_update: &MerklePriceUpdate,
|
||||
) -> Result<Pubkey> {
|
||||
let price_update_keypair = Keypair::new();
|
||||
|
||||
let (mut header, body): (Header, Body<&RawMessage>) = serde_wormhole::from_slice(vaa).unwrap();
|
||||
trim_signatures(&mut header, n_signatures);
|
||||
|
||||
let request_compute_units_instruction: Instruction =
|
||||
ComputeBudgetInstruction::set_compute_unit_limit(400_000);
|
||||
|
||||
|
||||
let post_update_accounts = pyth_solana_receiver::accounts::PostUpdatesAtomic::populate(
|
||||
payer.pubkey(),
|
||||
price_update_keypair.pubkey(),
|
||||
*wormhole,
|
||||
header.guardian_set_index,
|
||||
)
|
||||
.to_account_metas(None);
|
||||
|
||||
let post_update_instruction = Instruction {
|
||||
program_id: pyth_solana_receiver::id(),
|
||||
accounts: post_update_accounts,
|
||||
data: pyth_solana_receiver::instruction::PostUpdatesAtomic {
|
||||
params: PostUpdatesAtomicParams {
|
||||
merkle_price_update: merkle_price_update.clone(),
|
||||
vaa: serde_wormhole::to_vec(&(header, body)).unwrap(),
|
||||
},
|
||||
}
|
||||
.data(),
|
||||
};
|
||||
|
||||
process_transaction(
|
||||
rpc_client,
|
||||
vec![request_compute_units_instruction, post_update_instruction],
|
||||
&vec![payer, &price_update_keypair],
|
||||
)?;
|
||||
Ok(price_update_keypair.pubkey())
|
||||
}
|
||||
|
||||
fn trim_signatures(header: &mut Header, n_signatures: usize) {
|
||||
header.signatures = header.signatures[..(n_signatures)].to_vec();
|
||||
}
|
||||
|
||||
fn deserialize_guardian_set(buf: &mut &[u8], legacy_guardian_set: bool) -> Result<GuardianSet> {
|
||||
if !legacy_guardian_set {
|
||||
// Skip anchor discriminator
|
||||
|
|
|
@ -21,6 +21,7 @@ pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk", version = "2.0.0" }
|
|||
solana-program = "1.16.20"
|
||||
byteorder = "1.4.3"
|
||||
wormhole-core-bridge-solana = {git = "https://github.com/guibescos/wormhole", branch = "variable-sigs"}
|
||||
wormhole-raw-vaas = {version = "0.0.1-alpha.1", features = ["ruint", "on-chain"], default-features = false }
|
||||
wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" }
|
||||
serde_wormhole = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1"}
|
||||
|
||||
|
|
|
@ -28,4 +28,15 @@ pub enum ReceiverError {
|
|||
NonexistentGovernanceAuthorityTransferRequest,
|
||||
#[msg("Funds are insufficient to pay the receiving fee")]
|
||||
InsufficientFunds,
|
||||
// Wormhole errors
|
||||
#[msg("Invalid VAA version")]
|
||||
InvalidVaaVersion,
|
||||
#[msg("Guardian set version in the VAA doesn't match the guardian set passed")]
|
||||
GuardianSetMismatch,
|
||||
#[msg("Guardian index exceeds the number of guardians in the set")]
|
||||
InvalidGuardianIndex,
|
||||
#[msg("A VAA signature is invalid")]
|
||||
InvalidSignature,
|
||||
#[msg("The recovered guardian public key doesn't match the guardian set")]
|
||||
InvalidGuardianKeyRecovery,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,12 @@ use {
|
|||
},
|
||||
},
|
||||
serde_wormhole::RawMessage,
|
||||
solana_program::system_instruction,
|
||||
solana_program::{
|
||||
keccak,
|
||||
program_memory::sol_memcpy,
|
||||
secp256k1_recover::secp256k1_recover,
|
||||
system_instruction,
|
||||
},
|
||||
state::{
|
||||
config::{
|
||||
Config,
|
||||
|
@ -26,7 +31,17 @@ use {
|
|||
},
|
||||
price_update::PriceUpdateV1,
|
||||
},
|
||||
wormhole_core_bridge_solana::state::EncodedVaa,
|
||||
wormhole_core_bridge_solana::{
|
||||
sdk::legacy::AccountVariant,
|
||||
state::{
|
||||
EncodedVaa,
|
||||
GuardianSet,
|
||||
},
|
||||
},
|
||||
wormhole_raw_vaas::{
|
||||
GuardianSetSig,
|
||||
Vaa,
|
||||
},
|
||||
wormhole_sdk::vaa::{
|
||||
Body,
|
||||
Header,
|
||||
|
@ -36,7 +51,6 @@ use {
|
|||
pub mod error;
|
||||
pub mod state;
|
||||
|
||||
|
||||
declare_id!("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
|
||||
|
||||
#[program]
|
||||
|
@ -90,80 +104,115 @@ pub mod pyth_solana_receiver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Post a price update using an encoded_vaa account and a MerklePriceUpdate calldata.
|
||||
/// This should be called after the client has already verified the Vaa via the Wormhole contract.
|
||||
/// Check out target_chains/solana/cli/src/main.rs for an example of how to do this.
|
||||
#[allow(unused_variables)]
|
||||
pub fn post_updates(ctx: Context<PostUpdates>, price_update: MerklePriceUpdate) -> Result<()> {
|
||||
|
||||
/// Post a price update using a VAA and a MerklePriceUpdate.
|
||||
/// This function allows you to post a price update in a single transaction.
|
||||
/// Compared to post_updates, it is less secure since you won't be able to verify all guardian signatures if you use this function because of transaction size limitations.
|
||||
/// Typically, you can fit 5 guardian signatures in a transaction that uses this.
|
||||
pub fn post_updates_atomic(
|
||||
ctx: Context<PostUpdatesAtomic>,
|
||||
params: PostUpdatesAtomicParams,
|
||||
) -> Result<()> {
|
||||
// This section is borrowed from https://github.com/wormhole-foundation/wormhole/blob/wen/solana-rewrite/solana/programs/core-bridge/src/processor/parse_and_verify_vaa/verify_encoded_vaa_v1.rs#L59
|
||||
let vaa = Vaa::parse(¶ms.vaa).map_err(|_| ReceiverError::DeserializeVaaFailed)?;
|
||||
// Must be V1.
|
||||
require_eq!(vaa.version(), 1, ReceiverError::InvalidVaaVersion);
|
||||
|
||||
// Make sure the encoded guardian set index agrees with the guardian set account's index.
|
||||
let guardian_set = ctx.accounts.guardian_set.inner();
|
||||
require_eq!(
|
||||
vaa.guardian_set_index(),
|
||||
guardian_set.index,
|
||||
ReceiverError::GuardianSetMismatch
|
||||
);
|
||||
|
||||
// Do we have enough signatures for quorum?
|
||||
let guardian_keys = &guardian_set.keys;
|
||||
|
||||
// Generate the same message hash (using keccak) that the Guardians used to generate their
|
||||
// signatures. This message hash will be hashed again to produce the digest for
|
||||
// `secp256k1_recover`.
|
||||
let digest = keccak::hash(keccak::hash(vaa.body().as_ref()).as_ref());
|
||||
|
||||
let mut last_guardian_index = None;
|
||||
for sig in vaa.signatures() {
|
||||
// We do not allow for non-increasing guardian signature indices.
|
||||
let index = usize::from(sig.guardian_index());
|
||||
if let Some(last_index) = last_guardian_index {
|
||||
require!(index > last_index, ReceiverError::InvalidGuardianIndex);
|
||||
}
|
||||
|
||||
// Does this guardian index exist in this guardian set?
|
||||
let guardian_pubkey = guardian_keys
|
||||
.get(index)
|
||||
.ok_or_else(|| error!(ReceiverError::InvalidGuardianIndex))?;
|
||||
|
||||
// Now verify that the signature agrees with the expected Guardian's pubkey.
|
||||
verify_guardian_signature(&sig, guardian_pubkey, digest.as_ref())?;
|
||||
|
||||
last_guardian_index = Some(index);
|
||||
}
|
||||
// End borrowed section
|
||||
|
||||
let config = &ctx.accounts.config;
|
||||
let payer = &ctx.accounts.payer;
|
||||
let encoded_vaa = &ctx.accounts.encoded_vaa;
|
||||
let treasury = &ctx.accounts.treasury;
|
||||
let price_update_account = &mut ctx.accounts.price_update_account;
|
||||
|
||||
if payer.lamports()
|
||||
< Rent::get()?
|
||||
.minimum_balance(0)
|
||||
.saturating_add(config.single_update_fee_in_lamports)
|
||||
{
|
||||
return err!(ReceiverError::InsufficientFunds);
|
||||
let vaa_components = VaaComponents {
|
||||
verified_signatures: vaa.signature_count(),
|
||||
emitter_address: vaa.body().emitter_address(),
|
||||
emitter_chain: vaa.body().emitter_chain(),
|
||||
};
|
||||
|
||||
let transfer_instruction = system_instruction::transfer(
|
||||
payer.key,
|
||||
treasury.key,
|
||||
config.single_update_fee_in_lamports,
|
||||
);
|
||||
anchor_lang::solana_program::program::invoke(
|
||||
&transfer_instruction,
|
||||
&[
|
||||
ctx.accounts.payer.to_account_info(),
|
||||
ctx.accounts.treasury.to_account_info(),
|
||||
],
|
||||
post_price_update_from_vaa(
|
||||
config,
|
||||
payer,
|
||||
treasury,
|
||||
price_update_account,
|
||||
&vaa_components,
|
||||
vaa.payload().as_ref(),
|
||||
¶ms.merkle_price_update,
|
||||
)?;
|
||||
|
||||
let (header, body): (Header, Body<&RawMessage>) =
|
||||
serde_wormhole::from_slice(&ctx.accounts.encoded_vaa.buf).unwrap();
|
||||
|
||||
let valid_data_source = config.valid_data_sources.iter().any(|x| {
|
||||
*x == DataSource {
|
||||
chain: body.emitter_chain.into(),
|
||||
emitter: Pubkey::from(body.emitter_address.0),
|
||||
}
|
||||
});
|
||||
if !valid_data_source {
|
||||
return err!(ReceiverError::InvalidDataSource);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let wormhole_message = WormholeMessage::try_from_bytes(body.payload)
|
||||
.map_err(|_| ReceiverError::InvalidWormholeMessage)?;
|
||||
let root: MerkleRoot<Keccak160> = MerkleRoot::new(match wormhole_message.payload {
|
||||
WormholePayload::Merkle(merkle_root) => merkle_root.root,
|
||||
});
|
||||
/// Post a price update using an encoded_vaa account and a MerklePriceUpdate calldata.
|
||||
/// This should be called after the client has already verified the Vaa via the Wormhole contract.
|
||||
/// Check out target_chains/solana/cli/src/main.rs for an example of how to do this.
|
||||
pub fn post_updates(ctx: Context<PostUpdates>, price_update: MerklePriceUpdate) -> Result<()> {
|
||||
let config = &ctx.accounts.config;
|
||||
let payer: &Signer<'_> = &ctx.accounts.payer;
|
||||
let encoded_vaa = &ctx.accounts.encoded_vaa;
|
||||
let treasury: &AccountInfo<'_> = &ctx.accounts.treasury;
|
||||
let price_update_account: &mut Account<'_, PriceUpdateV1> =
|
||||
&mut ctx.accounts.price_update_account;
|
||||
|
||||
if !root.check(price_update.proof, price_update.message.as_ref()) {
|
||||
return err!(ReceiverError::InvalidPriceUpdate);
|
||||
}
|
||||
let (_, body): (Header, Body<&RawMessage>) =
|
||||
serde_wormhole::from_slice(&encoded_vaa.buf).unwrap();
|
||||
|
||||
let message = from_slice::<byteorder::BE, Message>(price_update.message.as_ref())
|
||||
.map_err(|_| ReceiverError::DeserializeMessageFailed)?;
|
||||
let vaa_components = VaaComponents {
|
||||
verified_signatures: encoded_vaa.header.verified_signatures,
|
||||
emitter_address: body.emitter_address.0,
|
||||
emitter_chain: body.emitter_chain.into(),
|
||||
};
|
||||
|
||||
match message {
|
||||
Message::PriceFeedMessage(price_feed_message) => {
|
||||
price_update_account.write_authority = payer.key();
|
||||
price_update_account.verified_signatures = encoded_vaa.header.verified_signatures;
|
||||
price_update_account.price_message = price_feed_message;
|
||||
}
|
||||
Message::TwapMessage(twap_message) => {
|
||||
return err!(ReceiverError::UnsupportedMessageType);
|
||||
}
|
||||
}
|
||||
post_price_update_from_vaa(
|
||||
config,
|
||||
payer,
|
||||
treasury,
|
||||
price_update_account,
|
||||
&vaa_components,
|
||||
body.payload,
|
||||
&price_update,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub const CONFIG_SEED: &str = "config";
|
||||
pub const TREASURY_SEED: &str = "treasury";
|
||||
|
||||
|
@ -199,7 +248,6 @@ pub struct AuthorizeGovernanceAuthorityTransfer<'info> {
|
|||
pub config: Account<'info, Config>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PostUpdates<'info> {
|
||||
#[account(mut)]
|
||||
|
@ -217,6 +265,35 @@ pub struct PostUpdates<'info> {
|
|||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PostUpdatesAtomic<'info> {
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
#[account(
|
||||
seeds = [
|
||||
GuardianSet::SEED_PREFIX,
|
||||
guardian_set.inner().index.to_be_bytes().as_ref()
|
||||
],
|
||||
seeds::program = config.wormhole,
|
||||
bump,
|
||||
owner = config.wormhole)]
|
||||
pub guardian_set: Account<'info, AccountVariant<GuardianSet>>,
|
||||
#[account(seeds = [CONFIG_SEED.as_ref()], bump)]
|
||||
pub config: Account<'info, Config>,
|
||||
#[account(mut, seeds = [TREASURY_SEED.as_ref()], bump)]
|
||||
/// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it.
|
||||
pub treasury: AccountInfo<'info>,
|
||||
#[account(init, payer = payer, space = PriceUpdateV1::LEN)]
|
||||
pub price_update_account: Account<'info, PriceUpdateV1>,
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)]
|
||||
pub struct PostUpdatesAtomicParams {
|
||||
pub vaa: Vec<u8>,
|
||||
pub merkle_price_update: MerklePriceUpdate,
|
||||
}
|
||||
|
||||
impl crate::accounts::Initialize {
|
||||
pub fn populate(payer: &Pubkey) -> Self {
|
||||
let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
|
||||
|
@ -228,6 +305,36 @@ impl crate::accounts::Initialize {
|
|||
}
|
||||
}
|
||||
|
||||
impl crate::accounts::PostUpdatesAtomic {
|
||||
pub fn populate(
|
||||
payer: Pubkey,
|
||||
price_update_account: Pubkey,
|
||||
wormhole_address: Pubkey,
|
||||
guardian_set_index: u32,
|
||||
) -> Self {
|
||||
let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
|
||||
let treasury = Pubkey::find_program_address(&[TREASURY_SEED.as_ref()], &crate::ID).0;
|
||||
|
||||
let guardian_set = Pubkey::find_program_address(
|
||||
&[
|
||||
GuardianSet::SEED_PREFIX,
|
||||
guardian_set_index.to_be_bytes().as_ref(),
|
||||
],
|
||||
&wormhole_address,
|
||||
)
|
||||
.0;
|
||||
|
||||
crate::accounts::PostUpdatesAtomic {
|
||||
payer,
|
||||
guardian_set,
|
||||
config,
|
||||
treasury,
|
||||
price_update_account,
|
||||
system_program: system_program::ID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::accounts::PostUpdates {
|
||||
pub fn populate(payer: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey) -> Self {
|
||||
let config = Pubkey::find_program_address(&[CONFIG_SEED.as_ref()], &crate::ID).0;
|
||||
|
@ -243,3 +350,106 @@ impl crate::accounts::PostUpdates {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VaaComponents {
|
||||
verified_signatures: u8,
|
||||
emitter_address: [u8; 32],
|
||||
emitter_chain: u16,
|
||||
}
|
||||
|
||||
fn post_price_update_from_vaa<'info>(
|
||||
config: &Account<'info, Config>,
|
||||
payer: &Signer<'info>,
|
||||
treasury: &AccountInfo<'info>,
|
||||
price_update_account: &mut Account<'_, PriceUpdateV1>,
|
||||
vaa_components: &VaaComponents,
|
||||
vaa_payload: &[u8],
|
||||
price_update: &MerklePriceUpdate,
|
||||
) -> Result<()> {
|
||||
if payer.lamports()
|
||||
< Rent::get()?
|
||||
.minimum_balance(0)
|
||||
.saturating_add(config.single_update_fee_in_lamports)
|
||||
{
|
||||
return err!(ReceiverError::InsufficientFunds);
|
||||
};
|
||||
|
||||
let transfer_instruction = system_instruction::transfer(
|
||||
payer.key,
|
||||
treasury.key,
|
||||
config.single_update_fee_in_lamports,
|
||||
);
|
||||
anchor_lang::solana_program::program::invoke(
|
||||
&transfer_instruction,
|
||||
&[payer.to_account_info(), treasury.to_account_info()],
|
||||
)?;
|
||||
|
||||
let valid_data_source = config.valid_data_sources.iter().any(|x| {
|
||||
*x == DataSource {
|
||||
chain: vaa_components.emitter_chain,
|
||||
emitter: Pubkey::from(vaa_components.emitter_address),
|
||||
}
|
||||
});
|
||||
if !valid_data_source {
|
||||
return err!(ReceiverError::InvalidDataSource);
|
||||
}
|
||||
|
||||
let wormhole_message = WormholeMessage::try_from_bytes(vaa_payload)
|
||||
.map_err(|_| ReceiverError::InvalidWormholeMessage)?;
|
||||
let root: MerkleRoot<Keccak160> = MerkleRoot::new(match wormhole_message.payload {
|
||||
WormholePayload::Merkle(merkle_root) => merkle_root.root,
|
||||
});
|
||||
|
||||
if !root.check(price_update.proof.clone(), price_update.message.as_ref()) {
|
||||
return err!(ReceiverError::InvalidPriceUpdate);
|
||||
}
|
||||
|
||||
let message = from_slice::<byteorder::BE, Message>(price_update.message.as_ref())
|
||||
.map_err(|_| ReceiverError::DeserializeMessageFailed)?;
|
||||
|
||||
match message {
|
||||
Message::PriceFeedMessage(price_feed_message) => {
|
||||
price_update_account.write_authority = payer.key();
|
||||
price_update_account.verified_signatures = vaa_components.verified_signatures;
|
||||
price_update_account.price_message = price_feed_message;
|
||||
}
|
||||
Message::TwapMessage(_) => {
|
||||
return err!(ReceiverError::UnsupportedMessageType);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
* Borrowed from https://github.com/wormhole-foundation/wormhole/blob/wen/solana-rewrite/solana/programs/core-bridge/src/processor/parse_and_verify_vaa/verify_encoded_vaa_v1.rs#L121
|
||||
*/
|
||||
fn verify_guardian_signature(
|
||||
sig: &GuardianSetSig,
|
||||
guardian_pubkey: &[u8; 20],
|
||||
digest: &[u8],
|
||||
) -> Result<()> {
|
||||
// Recover using `solana_program::secp256k1_recover`. Public key recovery costs 25k compute
|
||||
// units. And hashing this public key to recover the Ethereum public key costs about 13k.
|
||||
let recovered = {
|
||||
// Recover EC public key (64 bytes).
|
||||
let pubkey = secp256k1_recover(digest, sig.recovery_id(), &sig.rs())
|
||||
.map_err(|_| ReceiverError::InvalidSignature)?;
|
||||
|
||||
// The Ethereum public key is the last 20 bytes of keccak hashed public key above.
|
||||
let hashed = keccak::hash(&pubkey.to_bytes());
|
||||
|
||||
let mut eth_pubkey = [0; 20];
|
||||
sol_memcpy(&mut eth_pubkey, &hashed.0[12..], 20);
|
||||
|
||||
eth_pubkey
|
||||
};
|
||||
|
||||
// The recovered public key should agree with the Guardian's public key at this index.
|
||||
require!(
|
||||
recovered == *guardian_pubkey,
|
||||
ReceiverError::InvalidGuardianKeyRecovery
|
||||
);
|
||||
|
||||
// Done.
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue