Add keccak-secp256k1 instruction (#11839)

* Implement keccak-secp256k1 instruction

Verifies eth addreses with ecrecover function

* Move secp256k1 test
This commit is contained in:
sakridge 2020-09-15 18:23:21 -07:00 committed by GitHub
parent 7237e7065f
commit 3930cb865a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 732 additions and 52 deletions

100
Cargo.lock generated
View File

@ -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"

View File

@ -36,6 +36,7 @@ members = [
"net-shaper",
"notifier",
"poh-bench",
"programs/secp256k1",
"programs/bpf_loader",
"programs/budget",
"programs/config",

View File

@ -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()
}

View File

@ -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();

View File

@ -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!(

View File

@ -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

View File

@ -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());

View File

@ -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 {

View File

@ -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();

View File

@ -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"

View File

@ -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),
]);
}

View File

@ -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"]

View File

@ -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(&eth_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());
}
}

View File

@ -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"

View File

@ -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);

View File

@ -418,7 +418,7 @@ pub struct AccountsDB {
stats: AccountsStats,
cluster_type: Option<ClusterType>,
pub cluster_type: Option<ClusterType>,
}
#[derive(Debug, Default)]

View File

@ -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]

View File

@ -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,
]
);
}

View File

@ -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"

View File

@ -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]

View File

@ -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;

146
sdk/src/secp256k1.rs Normal file
View File

@ -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(())
}

View File

@ -0,0 +1 @@
solana_sdk::declare_id!("KeccakSecp256k11111111111111111111111111111");

View File

@ -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 {

View File

@ -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 {