Add keccak-secp256k1 instruction (#11839)
* Implement keccak-secp256k1 instruction Verifies eth addreses with ecrecover function * Move secp256k1 test
This commit is contained in:
parent
7237e7065f
commit
3930cb865a
|
@ -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"
|
||||
|
|
|
@ -36,6 +36,7 @@ members = [
|
|||
"net-shaper",
|
||||
"notifier",
|
||||
"poh-bench",
|
||||
"programs/secp256k1",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/config",
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Transaction>, Vec<usize>) {
|
||||
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<TransactionStatusSender>,
|
||||
gossip_vote_sender: &ReplayVoteSender,
|
||||
) -> (usize, usize, Vec<usize>) {
|
||||
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();
|
||||
|
||||
|
|
|
@ -2968,7 +2968,7 @@ pub mod tests {
|
|||
let largest_accounts: Vec<RpcAccountBalance> =
|
||||
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!(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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 <maintainers@solana.foundation>"]
|
||||
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"]
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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<AccountsDB>,
|
||||
|
||||
|
@ -70,17 +73,19 @@ impl Accounts {
|
|||
pub fn new(paths: Vec<PathBuf>, 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);
|
||||
|
|
|
@ -418,7 +418,7 @@ pub struct AccountsDB {
|
|||
|
||||
stats: AccountsStats,
|
||||
|
||||
cluster_type: Option<ClusterType>,
|
||||
pub cluster_type: Option<ClusterType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
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]
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<FeeConfig>) -> 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]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<FeeConfig> {
|
||||
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(())
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
solana_sdk::declare_id!("KeccakSecp256k11111111111111111111111111111");
|
|
@ -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<Vec<Option<usize>>> {
|
||||
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue