From 3930cb865a866d162c7023129f0bd4318bcd1ed8 Mon Sep 17 00:00:00 2001 From: sakridge Date: Tue, 15 Sep 2020 18:23:21 -0700 Subject: [PATCH] Add keccak-secp256k1 instruction (#11839) * Implement keccak-secp256k1 instruction Verifies eth addreses with ecrecover function * Move secp256k1 test --- Cargo.lock | 100 ++++++++++++++++- Cargo.toml | 1 + cli/src/checks.rs | 2 +- core/src/banking_stage.rs | 42 ++++++- core/src/rpc.rs | 4 +- core/src/transaction_status_service.rs | 5 +- ledger/src/blockstore_processor.rs | 7 +- ledger/src/entry.rs | 41 +++++-- poh-bench/src/main.rs | 4 +- programs/bpf/Cargo.lock | 96 +++++++++++++++- programs/bpf/tests/programs.rs | 4 +- programs/secp256k1/Cargo.toml | 28 +++++ programs/secp256k1/src/lib.rs | 127 +++++++++++++++++++++ runtime/Cargo.toml | 1 + runtime/src/accounts.rs | 20 +++- runtime/src/accounts_db.rs | 2 +- runtime/src/bank.rs | 14 ++- runtime/src/builtins.rs | 17 ++- sdk/Cargo.toml | 6 + sdk/src/fee_calculator.rs | 88 ++++++++++++++- sdk/src/lib.rs | 3 + sdk/src/secp256k1.rs | 146 +++++++++++++++++++++++++ sdk/src/secp256k1_program.rs | 1 + sdk/src/transaction.rs | 23 ++++ stake-o-matic/src/main.rs | 2 +- 25 files changed, 732 insertions(+), 52 deletions(-) create mode 100644 programs/secp256k1/Cargo.toml create mode 100644 programs/secp256k1/src/lib.rs create mode 100644 sdk/src/secp256k1.rs create mode 100644 sdk/src/secp256k1_program.rs diff --git a/Cargo.lock b/Cargo.lock index 2575192d8..1c4e0e527 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,12 +289,22 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding", + "block-padding 0.1.5", "byte-tools", "byteorder", "generic-array 0.12.3", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.3", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -304,6 +314,12 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bs58" version = "0.3.1" @@ -630,6 +646,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-mac" version = "0.7.0" @@ -1326,6 +1348,17 @@ dependencies = [ "digest 0.8.1", ] +[[package]] +name = "hmac-drbg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +dependencies = [ + "digest 0.8.1", + "generic-array 0.12.3", + "hmac", +] + [[package]] name = "http" version = "0.1.21" @@ -1757,6 +1790,12 @@ dependencies = [ "ws", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1825,6 +1864,22 @@ dependencies = [ "libc", ] +[[package]] +name = "libsecp256k1" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +dependencies = [ + "arrayref", + "crunchy", + "digest 0.8.1", + "hmac-drbg", + "rand 0.7.3", + "sha2", + "subtle 2.2.2", + "typenum", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -2175,6 +2230,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.29" @@ -3127,10 +3188,10 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ - "block-buffer", + "block-buffer 0.7.3", "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -3145,10 +3206,22 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "block-buffer", + "block-buffer 0.7.3", "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", ] [[package]] @@ -4211,6 +4284,7 @@ dependencies = [ "solana-rayon-threadlimit", "solana-sdk 1.4.0", "solana-sdk-macro-frozen-abi 1.4.0", + "solana-secp256k1-program", "solana-stake-program", "solana-vote-program", "symlink", @@ -4278,11 +4352,13 @@ dependencies = [ "byteorder", "chrono", "curve25519-dalek", + "digest 0.9.0", "ed25519-dalek", "generic-array 0.14.3", "hex", "hmac", "itertools 0.9.0", + "libsecp256k1", "log 0.4.8", "memmap", "num-derive 0.3.0", @@ -4297,6 +4373,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2", + "sha3", "solana-crate-features 1.4.0", "solana-logger 1.4.0", "solana-sdk-macro 1.4.0", @@ -4353,6 +4430,19 @@ dependencies = [ "syn 1.0.27", ] +[[package]] +name = "solana-secp256k1-program" +version = "1.4.0" +dependencies = [ + "bincode", + "digest 0.9.0", + "libsecp256k1", + "rand 0.7.3", + "sha3", + "solana-logger 1.4.0", + "solana-sdk 1.4.0", +] + [[package]] name = "solana-stake-accounts" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 5e4ce8cfe..63d78a653 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "net-shaper", "notifier", "poh-bench", + "programs/secp256k1", "programs/bpf_loader", "programs/budget", "programs/config", diff --git a/cli/src/checks.rs b/cli/src/checks.rs index f1c651487..e84add1f2 100644 --- a/cli/src/checks.rs +++ b/cli/src/checks.rs @@ -67,7 +67,7 @@ pub fn check_account_for_multiple_fees_with_commitment( pub fn calculate_fee(fee_calculator: &FeeCalculator, messages: &[&Message]) -> u64 { messages .iter() - .map(|message| fee_calculator.calculate_fee(message)) + .map(|message| fee_calculator.calculate_fee(message, None)) .sum() } diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index a5db70c11..7ada12816 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -30,9 +30,10 @@ use solana_runtime::{ }; use solana_sdk::{ clock::{ - Slot, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY, + Epoch, Slot, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY, MAX_TRANSACTION_FORWARDING_DELAY_GPU, }, + genesis_config::ClusterType, poh_config::PohConfig, pubkey::Pubkey, timing::{duration_as_ms, timestamp}, @@ -724,6 +725,8 @@ impl BankingStage { fn transactions_from_packets( msgs: &Packets, transaction_indexes: &[usize], + cluster_type: ClusterType, + epoch: Epoch, ) -> (Vec, Vec) { let packets = Packets::new( transaction_indexes @@ -733,8 +736,27 @@ impl BankingStage { ); let transactions = Self::deserialize_transactions(&packets); + let maybe_secp_verified_transactions: Vec<_> = + if solana_sdk::secp256k1::is_enabled(cluster_type, epoch) { + transactions + .into_iter() + .map(|tx| { + if let Some(tx) = tx { + if tx.verify_precompiles().is_ok() { + Some(tx) + } else { + None + } + } else { + None + } + }) + .collect() + } else { + transactions + }; - Self::filter_transaction_indexes(transactions, &transaction_indexes) + Self::filter_transaction_indexes(maybe_secp_verified_transactions, &transaction_indexes) } /// This function filters pending packets that are still valid @@ -783,8 +805,12 @@ impl BankingStage { transaction_status_sender: Option, gossip_vote_sender: &ReplayVoteSender, ) -> (usize, usize, Vec) { - let (transactions, transaction_to_packet_indexes) = - Self::transactions_from_packets(msgs, &packet_indexes); + let (transactions, transaction_to_packet_indexes) = Self::transactions_from_packets( + msgs, + &packet_indexes, + bank.cluster_type(), + bank.epoch(), + ); debug!( "bank: {} filtered transactions {}", bank.slot(), @@ -833,8 +859,12 @@ impl BankingStage { } } - let (transactions, transaction_to_packet_indexes) = - Self::transactions_from_packets(msgs, &transaction_indexes); + let (transactions, transaction_to_packet_indexes) = Self::transactions_from_packets( + msgs, + &transaction_indexes, + bank.cluster_type(), + bank.epoch(), + ); let tx_count = transaction_to_packet_indexes.len(); diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 30f8a3f85..5b1d3a234 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -2968,7 +2968,7 @@ pub mod tests { let largest_accounts: Vec = serde_json::from_value(json["result"]["value"].clone()) .expect("actual response deserialization"); - assert_eq!(largest_accounts.len(), 19); + assert_eq!(largest_accounts.len(), 20); let req = r#"{"jsonrpc":"2.0","id":1,"method":"getLargestAccounts","params":[{"filter":"nonCirculating"}]}"#; let res = io.handle_request_sync(&req, meta); let json: Value = serde_json::from_str(&res.unwrap()).unwrap(); @@ -4065,7 +4065,7 @@ pub mod tests { ); // sendTransaction will fail due to insanity - bad_transaction.message.instructions[0].program_id_index = 255u8; + bad_transaction.message.instructions[0].program_id_index = 0u8; let recent_blockhash = bank_forks.read().unwrap().root_bank().last_blockhash(); bad_transaction.sign(&[&mint_keypair], recent_blockhash); let req = format!( diff --git a/core/src/transaction_status_service.rs b/core/src/transaction_status_service.rs index 59d954070..86e4a467e 100644 --- a/core/src/transaction_status_service.rs +++ b/core/src/transaction_status_service.rs @@ -71,7 +71,10 @@ impl TransactionStatusService { _ => bank.get_fee_calculator(&transaction.message().recent_blockhash), } .expect("FeeCalculator must exist"); - let fee = fee_calculator.calculate_fee(transaction.message()); + let fee = fee_calculator.calculate_fee( + transaction.message(), + solana_sdk::secp256k1::get_fee_config(bank.cluster_type(), bank.epoch()), + ); let (writable_keys, readonly_keys) = transaction.message.get_account_keys_by_lock_type(); blockstore diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index 20744666b..cf5ca3942 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -669,7 +669,12 @@ pub fn confirm_slot( let verifier = if !skip_verification { datapoint_debug!("verify-batch-size", ("size", num_entries as i64, i64)); - let entry_state = entries.start_verify(&progress.last_entry, recyclers.clone()); + let entry_state = entries.start_verify( + &progress.last_entry, + recyclers.clone(), + bank.cluster_type(), + bank.epoch(), + ); if entry_state.status() == EntryVerificationStatus::Failure { warn!("Ledger proof of history failed at slot: {}", slot); return Err(BlockError::InvalidEntryHash.into()); diff --git a/ledger/src/entry.rs b/ledger/src/entry.rs index d0c63bfd8..7da6307a9 100644 --- a/ledger/src/entry.rs +++ b/ledger/src/entry.rs @@ -17,6 +17,8 @@ use solana_perf::cuda_runtime::PinnedVec; use solana_perf::perf_libs; use solana_perf::recycler::Recycler; use solana_rayon_threadlimit::get_thread_count; +use solana_sdk::clock::Epoch; +use solana_sdk::genesis_config::ClusterType; use solana_sdk::hash::Hash; use solana_sdk::timing; use solana_sdk::transaction::Transaction; @@ -323,8 +325,13 @@ pub trait EntrySlice { fn verify_cpu(&self, start_hash: &Hash) -> EntryVerificationState; fn verify_cpu_generic(&self, start_hash: &Hash) -> EntryVerificationState; fn verify_cpu_x86_simd(&self, start_hash: &Hash, simd_len: usize) -> EntryVerificationState; - fn start_verify(&self, start_hash: &Hash, recyclers: VerifyRecyclers) - -> EntryVerificationState; + fn start_verify( + &self, + start_hash: &Hash, + recyclers: VerifyRecyclers, + cluster_type: ClusterType, + epoch: Epoch, + ) -> EntryVerificationState; fn verify(&self, start_hash: &Hash) -> bool; /// Checks that each entry tick has the correct number of hashes. Entry slices do not /// necessarily end in a tick, so `tick_hash_count` is used to carry over the hash count @@ -332,13 +339,18 @@ pub trait EntrySlice { fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool; /// Counts tick entries fn tick_count(&self) -> u64; - fn verify_transaction_signatures(&self) -> bool; + fn verify_transaction_signatures(&self, cluster_type: ClusterType, epoch: Epoch) -> bool; } impl EntrySlice for [Entry] { fn verify(&self, start_hash: &Hash) -> bool { - self.start_verify(start_hash, VerifyRecyclers::default()) - .finish_verify(self) + self.start_verify( + start_hash, + VerifyRecyclers::default(), + ClusterType::Development, + 0, + ) + .finish_verify(self) } fn verify_cpu_generic(&self, start_hash: &Hash) -> EntryVerificationState { @@ -485,13 +497,20 @@ impl EntrySlice for [Entry] { } } - fn verify_transaction_signatures(&self) -> bool { + fn verify_transaction_signatures(&self, cluster_type: ClusterType, epoch: Epoch) -> bool { PAR_THREAD_POOL.with(|thread_pool| { thread_pool.borrow().install(|| { self.par_iter().all(|e| { - e.transactions - .par_iter() - .all(|transaction| transaction.verify().is_ok()) + e.transactions.par_iter().all(|transaction| { + let sig_verify = transaction.verify().is_ok(); + if sig_verify + && solana_sdk::secp256k1::is_enabled(cluster_type, epoch) + && transaction.verify_precompiles().is_err() + { + return false; + } + sig_verify + }) }) }) }) @@ -501,9 +520,11 @@ impl EntrySlice for [Entry] { &self, start_hash: &Hash, recyclers: VerifyRecyclers, + cluster_type: ClusterType, + epoch: Epoch, ) -> EntryVerificationState { let start = Instant::now(); - let res = self.verify_transaction_signatures(); + let res = self.verify_transaction_signatures(cluster_type, epoch); let transaction_duration_us = timing::duration_as_us(&start.elapsed()); if !res { return EntryVerificationState { diff --git a/poh-bench/src/main.rs b/poh-bench/src/main.rs index 78d59a881..f462a5f22 100644 --- a/poh-bench/src/main.rs +++ b/poh-bench/src/main.rs @@ -2,7 +2,7 @@ use clap::{crate_description, crate_name, value_t, App, Arg}; use solana_ledger::entry::{self, create_ticks, init_poh, EntrySlice, VerifyRecyclers}; use solana_measure::measure::Measure; use solana_perf::perf_libs; -use solana_sdk::hash::hash; +use solana_sdk::{genesis_config::ClusterType, hash::hash}; fn main() { solana_logger::setup(); @@ -118,7 +118,7 @@ fn main() { let recyclers = VerifyRecyclers::default(); for _ in 0..iterations { assert!(ticks[..num_entries] - .start_verify(&start_hash, recyclers.clone()) + .start_verify(&start_hash, recyclers.clone(), ClusterType::Development, 0) .finish_verify(&ticks[..num_entries])); } time.stop(); diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 49b181eed..b1a522bc2 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -128,12 +128,22 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding", + "block-padding 0.1.5", "byte-tools", "byteorder 1.3.4", "generic-array 0.12.3", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.3", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -143,6 +153,12 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bs58" version = "0.3.1" @@ -340,6 +356,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-mac" version = "0.7.0" @@ -742,6 +764,17 @@ dependencies = [ "digest 0.8.1", ] +[[package]] +name = "hmac-drbg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +dependencies = [ + "digest 0.8.1", + "generic-array 0.12.3", + "hmac", +] + [[package]] name = "http" version = "0.2.1" @@ -912,6 +945,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -946,6 +985,22 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "libsecp256k1" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +dependencies = [ + "arrayref", + "crunchy", + "digest 0.8.1", + "hmac-drbg", + "rand", + "sha2", + "subtle 2.2.2", + "typenum", +] + [[package]] name = "lock_api" version = "0.3.4" @@ -1148,6 +1203,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "parking_lot" version = "0.9.0" @@ -1626,10 +1687,22 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "block-buffer", + "block-buffer 0.7.3", "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", ] [[package]] @@ -1940,6 +2013,7 @@ dependencies = [ "solana-rayon-threadlimit", "solana-sdk", "solana-sdk-macro-frozen-abi", + "solana-secp256k1-program", "solana-stake-program", "solana-vote-program", "symlink", @@ -1960,11 +2034,13 @@ dependencies = [ "byteorder 1.3.4", "chrono", "curve25519-dalek", + "digest 0.9.0", "ed25519-dalek", "generic-array 0.14.3", "hex", "hmac", "itertools", + "libsecp256k1", "log", "memmap", "num-derive 0.3.0", @@ -1979,6 +2055,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2", + "sha3", "solana-crate-features", "solana-logger", "solana-sdk-macro", @@ -2008,6 +2085,19 @@ dependencies = [ "syn 1.0.27", ] +[[package]] +name = "solana-secp256k1-program" +version = "1.4.0" +dependencies = [ + "bincode", + "digest 0.9.0", + "libsecp256k1", + "rand", + "sha3", + "solana-logger", + "solana-sdk", +] + [[package]] name = "solana-stake-program" version = "1.4.0" diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index fd6de6148..5bf1547d6 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -576,10 +576,10 @@ fn assert_instruction_count() { ("solana_bpf_rust_128bit", 543), ("solana_bpf_rust_alloc", 19082), ("solana_bpf_rust_dep_crate", 2), - ("solana_bpf_rust_external_spend", 465), + ("solana_bpf_rust_external_spend", 473), ("solana_bpf_rust_iter", 723), ("solana_bpf_rust_many_args", 231), - ("solana_bpf_rust_noop", 2209), + ("solana_bpf_rust_noop", 2217), ("solana_bpf_rust_param_passing", 54), ]); } diff --git a/programs/secp256k1/Cargo.toml b/programs/secp256k1/Cargo.toml new file mode 100644 index 000000000..77420d200 --- /dev/null +++ b/programs/secp256k1/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "solana-secp256k1-program" +description = "Blockchain, Rebuilt for Scale" +version = "1.4.0" +documentation = "https://docs.rs/solana" +homepage = "https://solana.com/" +readme = "README.md" +repository = "https://github.com/solana-labs/solana" +authors = ["Solana Maintainers "] +license = "Apache-2.0" +edition = "2018" +publish = false + +[dependencies] +solana-sdk = { path = "../../sdk", version = "1.4.0" } +libsecp256k1 = "0.3.5" +sha3 = "0.9.1" +digest = "0.9.0" +bincode = "1.3.1" +rand = "0.7.0" +solana-logger = { path = "../../logger", version = "1.4.0" } + +[lib] +crate-type = ["lib"] +name = "solana_secp256k1_program" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/secp256k1/src/lib.rs b/programs/secp256k1/src/lib.rs new file mode 100644 index 000000000..25e789299 --- /dev/null +++ b/programs/secp256k1/src/lib.rs @@ -0,0 +1,127 @@ +use solana_sdk::pubkey::Pubkey; +use solana_sdk::{ + account::KeyedAccount, + instruction::{Instruction, InstructionError}, +}; + +pub fn process_instruction( + _program_id: &Pubkey, + _keyed_accounts: &[KeyedAccount], + _data: &[u8], +) -> Result<(), InstructionError> { + // Should be already checked by now. + Ok(()) +} + +solana_sdk::declare_program!( + solana_sdk::secp256k1_program::ID, + solana_keccak_secp256k1_program, + process_instruction +); + +pub fn new_secp256k1_instruction( + priv_key: &secp256k1::SecretKey, + message_arr: &[u8], +) -> Instruction { + use digest::Digest; + use solana_sdk::secp256k1::{ + construct_eth_pubkey, SecpSignatureOffsets, SIGNATURE_OFFSETS_SERIALIZED_SIZE, + SIGNATURE_SERIALIZED_SIZE, + }; + + let secp_pubkey = secp256k1::PublicKey::from_secret_key(priv_key); + let eth_pubkey = construct_eth_pubkey(&secp_pubkey); + let mut hasher = sha3::Keccak256::new(); + hasher.update(&message_arr); + let message_hash = hasher.finalize(); + let mut message_hash_arr = [0u8; 32]; + message_hash_arr.copy_from_slice(&message_hash.as_slice()); + let message = secp256k1::Message::parse(&message_hash_arr); + let (signature, recovery_id) = secp256k1::sign(&message, priv_key); + let signature_arr = signature.serialize(); + assert_eq!(signature_arr.len(), SIGNATURE_SERIALIZED_SIZE); + + let mut instruction_data = vec![]; + let data_start = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE; + instruction_data.resize( + data_start + eth_pubkey.len() + signature_arr.len() + message_arr.len() + 1, + 0, + ); + let eth_address_offset = data_start; + instruction_data[eth_address_offset..eth_address_offset + eth_pubkey.len()] + .copy_from_slice(ð_pubkey); + + let signature_offset = data_start + eth_pubkey.len(); + instruction_data[signature_offset..signature_offset + signature_arr.len()] + .copy_from_slice(&signature_arr); + + instruction_data[signature_offset + signature_arr.len()] = recovery_id.serialize(); + + let message_data_offset = signature_offset + signature_arr.len() + 1; + instruction_data[message_data_offset..].copy_from_slice(message_arr); + + let num_signatures = 1; + instruction_data[0] = num_signatures; + let offsets = SecpSignatureOffsets { + signature_offset: signature_offset as u16, + signature_instruction_index: 0, + eth_address_offset: eth_address_offset as u16, + eth_address_instruction_index: 0, + message_data_offset: message_data_offset as u16, + message_data_size: message_arr.len() as u16, + message_instruction_index: 0, + }; + let writer = std::io::Cursor::new(&mut instruction_data[1..data_start]); + bincode::serialize_into(writer, &offsets).unwrap(); + + Instruction { + program_id: solana_sdk::secp256k1_program::id(), + accounts: vec![], + data: instruction_data, + } +} + +#[cfg(test)] +pub mod test { + use rand::{thread_rng, Rng}; + use solana_sdk::secp256k1::{SecpSignatureOffsets, SIGNATURE_OFFSETS_SERIALIZED_SIZE}; + use solana_sdk::{ + hash::Hash, + signature::{Keypair, Signer}, + transaction::Transaction, + }; + + #[test] + fn test_secp256k1() { + solana_logger::setup(); + let offsets = SecpSignatureOffsets::default(); + assert_eq!( + bincode::serialized_size(&offsets).unwrap() as usize, + SIGNATURE_OFFSETS_SERIALIZED_SIZE + ); + + let secp_privkey = secp256k1::SecretKey::random(&mut thread_rng()); + let message_arr = b"hello"; + let mut secp_instruction = super::new_secp256k1_instruction(&secp_privkey, message_arr); + let mint_keypair = Keypair::new(); + + let tx = Transaction::new_signed_with_payer( + &[secp_instruction.clone()], + Some(&mint_keypair.pubkey()), + &[&mint_keypair], + Hash::default(), + ); + + assert!(tx.verify_precompiles().is_ok()); + + let index = thread_rng().gen_range(0, secp_instruction.data.len()); + secp_instruction.data[index] = secp_instruction.data[index].wrapping_add(12); + let tx = Transaction::new_signed_with_payer( + &[secp_instruction], + Some(&mint_keypair.pubkey()), + &[&mint_keypair], + Hash::default(), + ); + assert!(tx.verify_precompiles().is_err()); + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 46d29d1a2..29182330a 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -42,6 +42,7 @@ solana-sdk = { path = "../sdk", version = "1.4.0" } solana-sdk-macro-frozen-abi = { path = "../sdk/macro-frozen-abi", version = "1.4.0" } solana-stake-program = { path = "../programs/stake", version = "1.4.0" } solana-vote-program = { path = "../programs/vote", version = "1.4.0" } +solana-secp256k1-program = { path = "../programs/secp256k1", version = "1.4.0" } symlink = "0.1.0" tar = "0.4.28" tempfile = "3.1.0" diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 7f4226517..99d9dda90 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -16,7 +16,7 @@ use rand::{thread_rng, Rng}; use rayon::slice::ParallelSliceMut; use solana_sdk::{ account::Account, - clock::Slot, + clock::{Epoch, Slot}, fee_calculator::FeeCalculator, genesis_config::ClusterType, hash::Hash, @@ -44,6 +44,9 @@ pub struct Accounts { /// my slot pub slot: Slot, + /// my epoch + pub epoch: Epoch, + /// Single global AccountsDB pub accounts_db: Arc, @@ -70,17 +73,19 @@ impl Accounts { pub fn new(paths: Vec, cluster_type: &ClusterType) -> Self { Self { slot: 0, + epoch: 0, accounts_db: Arc::new(AccountsDB::new(paths, cluster_type)), account_locks: Mutex::new(HashSet::new()), readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))), } } - pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self { + pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot, epoch: Epoch) -> Self { let accounts_db = parent.accounts_db.clone(); accounts_db.set_hash(slot, parent_slot); Self { slot, + epoch, accounts_db, account_locks: Mutex::new(HashSet::new()), readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))), @@ -90,6 +95,7 @@ impl Accounts { pub(crate) fn new_empty(accounts_db: AccountsDB) -> Self { Self { slot: 0, + epoch: 0, accounts_db: Arc::new(accounts_db), account_locks: Mutex::new(HashSet::new()), readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))), @@ -293,7 +299,13 @@ impl Accounts { .cloned(), }; let fee = if let Some(fee_calculator) = fee_calculator { - fee_calculator.calculate_fee(tx.message()) + fee_calculator.calculate_fee( + tx.message(), + solana_sdk::secp256k1::get_fee_config( + self.accounts_db.cluster_type.unwrap(), + self.epoch, + ), + ) } else { return (Err(TransactionError::BlockhashNotFound), hash_age_kind); }; @@ -993,7 +1005,7 @@ mod tests { ); let fee_calculator = FeeCalculator::new(10); - assert_eq!(fee_calculator.calculate_fee(tx.message()), 10); + assert_eq!(fee_calculator.calculate_fee(tx.message(), None), 10); let loaded_accounts = load_accounts_with_fee(tx, &accounts, &fee_calculator, &mut error_counters); diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index 89fbe7e5b..f84d0799e 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -418,7 +418,7 @@ pub struct AccountsDB { stats: AccountsStats, - cluster_type: Option, + pub cluster_type: Option, } #[derive(Debug, Default)] diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index b0293c74b..9ed39cb95 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -569,11 +569,15 @@ impl Bank { parent.freeze(); assert_ne!(slot, parent.slot()); + let epoch_schedule = parent.epoch_schedule; + let epoch = epoch_schedule.get_epoch(slot); + let rc = BankRc { accounts: Arc::new(Accounts::new_from_parent( &parent.rc.accounts, slot, parent.slot(), + epoch, )), parent: RwLock::new(Some(parent.clone())), slot, @@ -581,8 +585,6 @@ impl Bank { let src = StatusCacheRc { status_cache: parent.src.status_cache.clone(), }; - let epoch_schedule = parent.epoch_schedule; - let epoch = epoch_schedule.get_epoch(slot); let fee_rate_governor = FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count()); @@ -2057,7 +2059,10 @@ impl Bank { }; let fee_calculator = fee_calculator.ok_or(TransactionError::BlockhashNotFound)?; - let fee = fee_calculator.calculate_fee(tx.message()); + let fee = fee_calculator.calculate_fee( + tx.message(), + solana_sdk::secp256k1::get_fee_config(self.cluster_type(), self.epoch()), + ); let message = tx.message(); match *res { @@ -8387,7 +8392,8 @@ mod tests { .map(|_| bank.process_stale_slot_with_budget(0, force_to_return_alive_account)) .collect::>(); consumed_budgets.sort(); - assert_eq!(consumed_budgets, vec![0, 1, 9]); + // consumed_budgets represents the count of alive accounts in the three slots 0,1,2 + assert_eq!(consumed_budgets, vec![0, 1, 10]); } #[test] diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index d2f681022..d53a69307 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -63,6 +63,14 @@ pub fn get_builtins(cluster_type: ClusterType) -> Vec<(Builtin, Epoch)> { )]); } + let secp256k1_builtin = Builtin::new( + "secp256k1_program", + solana_sdk::secp256k1_program::id(), + Entrypoint::Program(solana_secp256k1_program::process_instruction), + ); + let secp_epoch = solana_sdk::secp256k1::is_enabled_epoch(cluster_type); + builtins.push((secp256k1_builtin, secp_epoch)); + builtins } @@ -101,6 +109,9 @@ mod tests { #[test] fn test_get_builtins() { + let mock_program_id = + Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(); + let (mut genesis_config, _mint_keypair) = create_genesis_config(100_000); genesis_config.cluster_type = ClusterType::Testnet; let bank0 = Arc::new(Bank::new(&genesis_config)); @@ -145,7 +156,7 @@ mod tests { solana_config_program::id(), solana_stake_program::id(), solana_vote_program::id(), - Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(), + mock_program_id, ] ); @@ -157,7 +168,7 @@ mod tests { solana_config_program::id(), solana_stake_program::id(), solana_vote_program::id(), - Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(), + mock_program_id, ] ); @@ -169,7 +180,7 @@ mod tests { solana_config_program::id(), solana_stake_program::id(), solana_vote_program::id(), - Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(), + mock_program_id, ] ); } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 2df5ea57e..9eb21f9cb 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -27,6 +27,9 @@ default = [ "ed25519-dalek", "solana-logger", "solana-crate-features", + "libsecp256k1", + "sha3", + "digest", ] [dependencies] @@ -60,6 +63,9 @@ solana-logger = { path = "../logger", version = "1.4.0", optional = true } solana-sdk-macro = { path = "macro", version = "1.4.0" } solana-sdk-macro-frozen-abi = { path = "macro-frozen-abi", version = "1.4.0" } rustversion = "1.0.3" +libsecp256k1 = { version = "0.3.5", optional = true } +sha3 = { version = "0.9.1", optional = true } +digest = { version = "0.9.0", optional = true } [dev-dependencies] curve25519-dalek = "2.1.0" diff --git a/sdk/src/fee_calculator.rs b/sdk/src/fee_calculator.rs index 2729bea43..8210d8439 100644 --- a/sdk/src/fee_calculator.rs +++ b/sdk/src/fee_calculator.rs @@ -1,5 +1,6 @@ use crate::clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT}; use crate::message::Message; +use crate::secp256k1_program; use log::*; #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)] @@ -18,6 +19,11 @@ impl Default for FeeCalculator { } } +#[derive(Clone)] +pub struct FeeConfig { + pub is_secp256k1_enabled: bool, +} + impl FeeCalculator { pub fn new(lamports_per_signature: u64) -> Self { Self { @@ -25,8 +31,27 @@ impl FeeCalculator { } } - pub fn calculate_fee(&self, message: &Message) -> u64 { - self.lamports_per_signature * u64::from(message.header.num_required_signatures) + // extra_config: None == everything enabled + pub fn calculate_fee(&self, message: &Message, extra_config: Option) -> u64 { + let is_secp256k1_enabled = match extra_config { + Some(config) => config.is_secp256k1_enabled, + None => true, + }; + let mut num_secp_signatures: u64 = 0; + if is_secp256k1_enabled { + for instruction in &message.instructions { + let program_index = instruction.program_id_index as usize; + // Transaction may not be sanitized here + if program_index < message.account_keys.len() { + let id = message.account_keys[program_index]; + if secp256k1_program::check_id(&id) && !instruction.data.is_empty() { + num_secp_signatures += instruction.data[0] as u64; + } + } + } + } + self.lamports_per_signature + * (u64::from(message.header.num_required_signatures) + num_secp_signatures) } } @@ -182,25 +207,76 @@ mod tests { #[test] fn test_fee_calculator_calculate_fee() { + let fee_config = Some(FeeConfig { + is_secp256k1_enabled: true, + }); // Default: no fee. let message = Message::default(); - assert_eq!(FeeCalculator::default().calculate_fee(&message), 0); + assert_eq!( + FeeCalculator::default().calculate_fee(&message, fee_config.clone()), + 0 + ); // No signature, no fee. - assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0); + assert_eq!(FeeCalculator::new(1).calculate_fee(&message, fee_config), 0); + let fee_config = Some(FeeConfig { + is_secp256k1_enabled: false, + }); // One signature, a fee. let pubkey0 = Pubkey::new(&[0; 32]); let pubkey1 = Pubkey::new(&[1; 32]); let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); let message = Message::new(&[ix0], Some(&pubkey0)); - assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2); + assert_eq!(FeeCalculator::new(2).calculate_fee(&message, fee_config), 2); // Two signatures, double the fee. let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1); let message = Message::new(&[ix0, ix1], Some(&pubkey0)); - assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4); + assert_eq!(FeeCalculator::new(2).calculate_fee(&message, None), 4); + } + + #[test] + fn test_fee_calculator_calculate_fee_secp256k1() { + use crate::instruction::Instruction; + let pubkey0 = Pubkey::new(&[0; 32]); + let pubkey1 = Pubkey::new(&[1; 32]); + let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); + let mut secp_instruction = Instruction { + program_id: crate::secp256k1_program::id(), + accounts: vec![], + data: vec![], + }; + let mut secp_instruction2 = Instruction { + program_id: crate::secp256k1_program::id(), + accounts: vec![], + data: vec![1], + }; + + let message = Message::new( + &[ + ix0.clone(), + secp_instruction.clone(), + secp_instruction2.clone(), + ], + Some(&pubkey0), + ); + let fee_config = Some(FeeConfig { + is_secp256k1_enabled: true, + }); + assert_eq!( + FeeCalculator::new(1).calculate_fee(&message, fee_config.clone()), + 2 + ); + + secp_instruction.data = vec![0]; + secp_instruction2.data = vec![10]; + let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0)); + assert_eq!( + FeeCalculator::new(1).calculate_fee(&message, fee_config), + 11 + ); } #[test] diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 2812b3547..e20a998af 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -37,6 +37,7 @@ pub mod pubkey; pub mod rent; pub mod rpc_port; pub mod sanitize; +pub mod secp256k1_program; pub mod short_vec; pub mod slot_hashes; pub mod slot_history; @@ -89,6 +90,8 @@ pub mod genesis_config; #[cfg(not(feature = "program"))] pub mod hard_forks; #[cfg(not(feature = "program"))] +pub mod secp256k1; +#[cfg(not(feature = "program"))] pub mod shred_version; #[cfg(not(feature = "program"))] pub mod signature; diff --git a/sdk/src/secp256k1.rs b/sdk/src/secp256k1.rs new file mode 100644 index 000000000..6eddcc7a8 --- /dev/null +++ b/sdk/src/secp256k1.rs @@ -0,0 +1,146 @@ +use crate::clock::{Epoch, GENESIS_EPOCH}; +use crate::fee_calculator::FeeConfig; +use crate::genesis_config::ClusterType; +use digest::Digest; +use serde_derive::{Deserialize, Serialize}; + +pub fn get_fee_config(cluster_type: ClusterType, epoch: Epoch) -> Option { + Some(FeeConfig { + is_secp256k1_enabled: is_enabled(cluster_type, epoch), + }) +} + +pub fn is_enabled_epoch(cluster_type: ClusterType) -> Epoch { + match cluster_type { + ClusterType::Development => GENESIS_EPOCH, + ClusterType::Testnet => u64::MAX, + ClusterType::MainnetBeta => u64::MAX, + ClusterType::Devnet => u64::MAX, + } +} + +pub fn is_enabled(cluster_type: ClusterType, epoch: Epoch) -> bool { + epoch >= is_enabled_epoch(cluster_type) +} + +#[derive(Debug)] +pub enum Secp256k1Error { + InvalidSignature, + InvalidRecoveryId, + InvalidDataOffsets, + InvalidInstructionDataSize, +} + +pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20; +pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; +pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11; + +pub fn construct_eth_pubkey(pubkey: &secp256k1::PublicKey) -> [u8; HASHED_PUBKEY_SERIALIZED_SIZE] { + let mut addr = [0u8; HASHED_PUBKEY_SERIALIZED_SIZE]; + addr.copy_from_slice(&sha3::Keccak256::digest(&pubkey.serialize()[1..])[12..]); + assert_eq!(addr.len(), HASHED_PUBKEY_SERIALIZED_SIZE); + addr +} + +#[derive(Default, Serialize, Deserialize, Debug)] +pub struct SecpSignatureOffsets { + pub signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes + pub signature_instruction_index: u8, + pub eth_address_offset: u16, // offset to eth_address of 20 bytes + pub eth_address_instruction_index: u8, + pub message_data_offset: u16, // offset to start of message data + pub message_data_size: u16, // size of message data + pub message_instruction_index: u8, +} + +fn get_data_slice<'a>( + instruction_datas: &'a [&[u8]], + instruction_index: u8, + offset_start: u16, + size: usize, +) -> Result<&'a [u8], Secp256k1Error> { + let signature_index = instruction_index as usize; + if signature_index > instruction_datas.len() { + return Err(Secp256k1Error::InvalidDataOffsets); + } + let signature_instruction = &instruction_datas[signature_index]; + let start = offset_start as usize; + let end = start + size; + if end > signature_instruction.len() { + return Err(Secp256k1Error::InvalidSignature); + } + + Ok(&instruction_datas[signature_index][start..end]) +} + +pub fn verify_eth_addresses( + data: &[u8], + instruction_datas: &[&[u8]], +) -> Result<(), Secp256k1Error> { + if data.is_empty() { + return Err(Secp256k1Error::InvalidInstructionDataSize); + } + let count = data[0] as usize; + let expected_data_size = 1 + count * SIGNATURE_OFFSETS_SERIALIZED_SIZE; + if data.len() < expected_data_size { + return Err(Secp256k1Error::InvalidInstructionDataSize); + } + for i in 0..count { + let start = 1 + i * SIGNATURE_OFFSETS_SERIALIZED_SIZE; + let end = start + SIGNATURE_OFFSETS_SERIALIZED_SIZE; + + let offsets: SecpSignatureOffsets = bincode::deserialize(&data[start..end]) + .map_err(|_| Secp256k1Error::InvalidSignature)?; + + // Parse out signature + let signature_index = offsets.signature_instruction_index as usize; + if signature_index > instruction_datas.len() { + return Err(Secp256k1Error::InvalidInstructionDataSize); + } + let signature_instruction = instruction_datas[signature_index]; + let sig_start = offsets.signature_offset as usize; + let sig_end = sig_start + SIGNATURE_SERIALIZED_SIZE; + if sig_end >= signature_instruction.len() { + return Err(Secp256k1Error::InvalidSignature); + } + let signature = + secp256k1::Signature::parse_slice(&signature_instruction[sig_start..sig_end]) + .map_err(|_| Secp256k1Error::InvalidSignature)?; + + let recovery_id = secp256k1::RecoveryId::parse(signature_instruction[sig_end]) + .map_err(|_| Secp256k1Error::InvalidRecoveryId)?; + + // Parse out pubkey + let eth_address_slice = get_data_slice( + &instruction_datas, + offsets.eth_address_instruction_index, + offsets.eth_address_offset, + HASHED_PUBKEY_SERIALIZED_SIZE, + )?; + + // Parse out message + let message_slice = get_data_slice( + &instruction_datas, + offsets.message_instruction_index, + offsets.message_data_offset, + offsets.message_data_size as usize, + )?; + + let mut hasher = sha3::Keccak256::new(); + hasher.update(message_slice); + let message_hash = hasher.finalize(); + + let pubkey = secp256k1::recover( + &secp256k1::Message::parse_slice(&message_hash).unwrap(), + &signature, + &recovery_id, + ) + .map_err(|_| Secp256k1Error::InvalidSignature)?; + let eth_address = construct_eth_pubkey(&pubkey); + + if eth_address_slice != eth_address { + return Err(Secp256k1Error::InvalidSignature); + } + } + Ok(()) +} diff --git a/sdk/src/secp256k1_program.rs b/sdk/src/secp256k1_program.rs new file mode 100644 index 000000000..ebce5835b --- /dev/null +++ b/sdk/src/secp256k1_program.rs @@ -0,0 +1 @@ +solana_sdk::declare_id!("KeccakSecp256k11111111111111111111111111111"); diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index 92a58be29..e9e61a853 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -1,6 +1,7 @@ //! Defines a Transaction type to package an atomic sequence of instructions. use crate::sanitize::{Sanitize, SanitizeError}; +use crate::secp256k1::verify_eth_addresses; use crate::{ hash::Hash, instruction::{CompiledInstruction, Instruction, InstructionError}, @@ -330,6 +331,28 @@ impl Transaction { } } + pub fn verify_precompiles(&self) -> Result<()> { + for instruction in &self.message().instructions { + // The Transaction may not be sanitized at this point + if instruction.program_id_index as usize >= self.message().account_keys.len() { + return Err(TransactionError::AccountNotFound); + } + let program_id = &self.message().account_keys[instruction.program_id_index as usize]; + if crate::secp256k1_program::check_id(program_id) { + let instruction_datas: Vec<_> = self + .message() + .instructions + .iter() + .map(|instruction| instruction.data.as_ref()) + .collect(); + let data = &instruction.data; + let e = verify_eth_addresses(data, &instruction_datas); + e.map_err(|_| TransactionError::InvalidAccountIndex)?; + } + } + Ok(()) + } + /// Get the positions of the pubkeys in `account_keys` associated with signing keypairs pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result>> { if self.message.account_keys.len() < self.message.header.num_required_signatures as usize { diff --git a/stake-o-matic/src/main.rs b/stake-o-matic/src/main.rs index 250d6f212..e1ea030b8 100644 --- a/stake-o-matic/src/main.rs +++ b/stake-o-matic/src/main.rs @@ -430,7 +430,7 @@ fn transact( info!("{} transactions to send", transactions.len()); let required_fee = transactions.iter().fold(0, |fee, (transaction, _)| { - fee + fee_calculator.calculate_fee(&transaction.message) + fee + fee_calculator.calculate_fee(&transaction.message, None) }); info!("Required fee: {} SOL", lamports_to_sol(required_fee)); if required_fee > authorized_staker_balance {