diff --git a/Cargo.lock b/Cargo.lock index 114a996fa8bfcc..71c50200fe8f2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,33 @@ dependencies = [ "tokio", ] +[[package]] +name = "agave-precompiles" +version = "2.2.6" +dependencies = [ + "bincode", + "bytemuck", + "digest 0.10.7", + "ed25519-dalek", + "hex", + "lazy_static", + "libsecp256k1", + "openssl", + "rand 0.7.3", + "sha3", + "solana-ed25519-program", + "solana-feature-set", + "solana-instruction", + "solana-keccak-hasher", + "solana-logger", + "solana-message", + "solana-precompile-error", + "solana-pubkey", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-secp256r1-program", +] + [[package]] name = "agave-reserved-account-keys" version = "2.2.6" @@ -6573,6 +6600,7 @@ dependencies = [ name = "solana-bpf-loader-program" version = "2.2.6" dependencies = [ + "agave-precompiles", "assert_matches", "bincode", "criterion", @@ -6605,7 +6633,6 @@ dependencies = [ "solana-measure", "solana-packet", "solana-poseidon", - "solana-precompiles", "solana-program", "solana-program-entrypoint", "solana-program-memory", @@ -7467,9 +7494,9 @@ dependencies = [ [[package]] name = "solana-ed25519-program" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0c4dfce08d71d8f1e9b7d1b4e2c7101a8109903ad481acbbc1119a73d459f2" +checksum = "9d0fc717048fdbe5d2ee7d673d73e6a30a094002f4a29ca7630ac01b6bddec04" dependencies = [ "bytemuck", "bytemuck_derive", @@ -7663,9 +7690,9 @@ dependencies = [ [[package]] name = "solana-feature-set" -version = "2.2.1" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e1d3b52b4a014efeaaab67f14e40af3972a4be61c523d612860db8e3145529" +checksum = "92f6c09cc41059c0e03ccbee7f5d4cc0a315d68ef0d59b67eb90246adfd8cc35" dependencies = [ "ahash 0.8.11", "lazy_static", @@ -8812,6 +8839,7 @@ dependencies = [ name = "solana-program-runtime" version = "2.2.6" dependencies = [ + "agave-precompiles", "assert_matches", "base64 0.22.1", "bincode", @@ -8835,7 +8863,6 @@ dependencies = [ "solana-log-collector", "solana-measure", "solana-metrics", - "solana-precompiles", "solana-pubkey", "solana-rent", "solana-sbpf", @@ -9302,6 +9329,7 @@ dependencies = [ name = "solana-runtime" version = "2.2.6" dependencies = [ + "agave-precompiles", "agave-reserved-account-keys", "agave-transaction-view", "ahash 0.8.11", @@ -9579,9 +9607,9 @@ dependencies = [ [[package]] name = "solana-secp256r1-program" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ea9282950921611bd9e0200da7236fbb1d4f8388942f8451bd55e9f3cb228f" +checksum = "5cda2aa1bbaceda14763c4f142a00b486f2f262cfd901bd0410649ad0404d5f7" dependencies = [ "bytemuck", "openssl", @@ -9971,6 +9999,7 @@ dependencies = [ name = "solana-svm" version = "2.2.6" dependencies = [ + "agave-precompiles", "agave-reserved-account-keys", "ahash 0.8.11", "assert_matches", @@ -10014,7 +10043,6 @@ dependencies = [ "solana-native-token", "solana-nonce", "solana-nonce-account", - "solana-precompiles", "solana-program", "solana-program-runtime", "solana-pubkey", diff --git a/Cargo.toml b/Cargo.toml index 9d29dbe8f6f807..9c4f7440015fdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ members = [ "poh", "poh-bench", "poseidon", + "precompiles", "program-runtime", "program-test", "programs/address-lookup-table", @@ -176,6 +177,7 @@ Inflector = "0.11.4" axum = "0.7.9" agave-banking-stage-ingress-types = { path = "banking-stage-ingress-types", version = "=2.2.6" } agave-feature-set = { path = "feature-set", version = "=2.2.6" } +agave-precompiles = { path = "precompiles", version = "=2.2.6" } agave-reserved-account-keys = { path = "reserved-account-keys", version = "=2.2.6" } agave-transaction-view = { path = "transaction-view", version = "=2.2.6" } aquamarine = "0.6.0" @@ -407,7 +409,7 @@ solana-decode-error = "=2.2.1" solana-define-syscall = "=2.2.1" solana-derivation-path = "=2.2.1" solana-download-utils = { path = "download-utils", version = "=2.2.6" } -solana-ed25519-program = "=2.2.1" +solana-ed25519-program = "=2.2.2" solana-entry = { path = "entry", version = "=2.2.6" } solana-feature-set-interface = "=4.0.0" solana-program-entrypoint = "=2.2.1" @@ -419,7 +421,7 @@ solana-example-mocks = "=2.2.1" solana-faucet = { path = "faucet", version = "=2.2.6" } solana-feature-gate-client = "0.0.2" solana-feature-gate-interface = "=2.2.1" -solana-feature-set = "=2.2.1" +solana-feature-set = "=2.2.4" # will be removed solana-fee-calculator = "=2.2.1" solana-fee = { path = "fee", version = "=2.2.6" } solana-fee-structure = "=2.2.1" @@ -471,7 +473,6 @@ solana-poh = { path = "poh", version = "=2.2.6" } solana-poh-config = "=2.2.1" solana-poseidon = { path = "poseidon", version = "=2.2.6" } solana-precompile-error = "=2.2.1" -solana-precompiles = "=2.2.1" solana-presigner = "=2.2.1" solana-program = { version = "=2.2.1", default-features = false } solana-program-error = "=2.2.1" @@ -491,7 +492,7 @@ solana-rent-collector = "=2.2.1" solana-rent-debits = "=2.2.1" solana-reward-info = "=2.2.1" solana-sanitize = "=2.2.1" -solana-secp256r1-program = "=2.2.1" +solana-secp256r1-program = "=2.2.2" solana-seed-derivable = "=2.2.1" solana-seed-phrase = "=2.2.1" solana-serde = "=2.2.1" diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml new file mode 100644 index 00000000000000..f10d74d4eb9de4 --- /dev/null +++ b/precompiles/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "agave-precompiles" +description = "Solana precompiled programs." +documentation = "https://docs.rs/agave-precompiles" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +bincode = { workspace = true } +bytemuck = { workspace = true } +digest = { workspace = true } +ed25519-dalek = { workspace = true } +lazy_static = { workspace = true } +libsecp256k1 = { workspace = true } +openssl = { workspace = true } +sha3 = { workspace = true } +solana-ed25519-program = { workspace = true } +solana-feature-set = { workspace = true } +solana-message = { workspace = true } +solana-precompile-error = { workspace = true } +solana-pubkey = { workspace = true } +solana-sdk-ids = { workspace = true } +solana-secp256k1-program = { workspace = true, features = ["serde"] } +solana-secp256r1-program = { workspace = true, features = ["openssl-vendored"] } + +[dev-dependencies] +hex = { workspace = true } +rand0-7 = { workspace = true } +solana-instruction = { workspace = true } +solana-keccak-hasher = { workspace = true } +solana-logger = { workspace = true } +solana-secp256k1-program = { workspace = true, features = ["bincode"] } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] + +[lints] +workspace = true diff --git a/precompiles/src/ed25519.rs b/precompiles/src/ed25519.rs new file mode 100644 index 00000000000000..9febaf7c8376dc --- /dev/null +++ b/precompiles/src/ed25519.rs @@ -0,0 +1,446 @@ +use { + ed25519_dalek::{ed25519::signature::Signature, Verifier}, + solana_ed25519_program::{ + Ed25519SignatureOffsets, PUBKEY_SERIALIZED_SIZE, SIGNATURE_OFFSETS_SERIALIZED_SIZE, + SIGNATURE_OFFSETS_START, SIGNATURE_SERIALIZED_SIZE, + }, + solana_feature_set::{ed25519_precompile_verify_strict, FeatureSet}, + solana_precompile_error::PrecompileError, +}; + +pub fn verify( + data: &[u8], + instruction_datas: &[&[u8]], + feature_set: &FeatureSet, +) -> Result<(), PrecompileError> { + if data.len() < SIGNATURE_OFFSETS_START { + return Err(PrecompileError::InvalidInstructionDataSize); + } + let num_signatures = data[0] as usize; + if num_signatures == 0 && data.len() > SIGNATURE_OFFSETS_START { + return Err(PrecompileError::InvalidInstructionDataSize); + } + let expected_data_size = num_signatures + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(SIGNATURE_OFFSETS_START); + // We do not check or use the byte at data[1] + if data.len() < expected_data_size { + return Err(PrecompileError::InvalidInstructionDataSize); + } + for i in 0..num_signatures { + let start = i + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(SIGNATURE_OFFSETS_START); + let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE); + + // bytemuck wants structures aligned + let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end]) + .map_err(|_| PrecompileError::InvalidDataOffsets)?; + + // Parse out signature + let signature = get_data_slice( + data, + instruction_datas, + offsets.signature_instruction_index, + offsets.signature_offset, + SIGNATURE_SERIALIZED_SIZE, + )?; + + let signature = + Signature::from_bytes(signature).map_err(|_| PrecompileError::InvalidSignature)?; + + // Parse out pubkey + let pubkey = get_data_slice( + data, + instruction_datas, + offsets.public_key_instruction_index, + offsets.public_key_offset, + PUBKEY_SERIALIZED_SIZE, + )?; + + let publickey = ed25519_dalek::PublicKey::from_bytes(pubkey) + .map_err(|_| PrecompileError::InvalidPublicKey)?; + + // Parse out message + let message = get_data_slice( + data, + instruction_datas, + offsets.message_instruction_index, + offsets.message_data_offset, + offsets.message_data_size as usize, + )?; + + if feature_set.is_active(&ed25519_precompile_verify_strict::id()) { + publickey + .verify_strict(message, &signature) + .map_err(|_| PrecompileError::InvalidSignature)?; + } else { + publickey + .verify(message, &signature) + .map_err(|_| PrecompileError::InvalidSignature)?; + } + } + Ok(()) +} + +fn get_data_slice<'a>( + data: &'a [u8], + instruction_datas: &'a [&[u8]], + instruction_index: u16, + offset_start: u16, + size: usize, +) -> Result<&'a [u8], PrecompileError> { + let instruction = if instruction_index == u16::MAX { + data + } else { + let signature_index = instruction_index as usize; + if signature_index >= instruction_datas.len() { + return Err(PrecompileError::InvalidDataOffsets); + } + instruction_datas[signature_index] + }; + + let start = offset_start as usize; + let end = start.saturating_add(size); + if end > instruction.len() { + return Err(PrecompileError::InvalidDataOffsets); + } + + Ok(&instruction[start..end]) +} + +#[cfg(test)] +pub mod tests { + use { + super::*, + bytemuck::bytes_of, + ed25519_dalek::Signer as EdSigner, + hex, + rand0_7::{thread_rng, Rng}, + solana_ed25519_program::{ + new_ed25519_instruction, offsets_to_ed25519_instruction, DATA_START, + }, + solana_feature_set::FeatureSet, + solana_instruction::Instruction, + }; + + pub fn new_ed25519_instruction_raw( + pubkey: &[u8], + signature: &[u8], + message: &[u8], + ) -> Instruction { + assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE); + assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE); + + let mut instruction_data = Vec::with_capacity( + DATA_START + .saturating_add(SIGNATURE_SERIALIZED_SIZE) + .saturating_add(PUBKEY_SERIALIZED_SIZE) + .saturating_add(message.len()), + ); + + let num_signatures: u8 = 1; + let public_key_offset = DATA_START; + let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE); + let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE); + + // add padding byte so that offset structure is aligned + instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0])); + + let offsets = Ed25519SignatureOffsets { + signature_offset: signature_offset as u16, + signature_instruction_index: u16::MAX, + public_key_offset: public_key_offset as u16, + public_key_instruction_index: u16::MAX, + message_data_offset: message_data_offset as u16, + message_data_size: message.len() as u16, + message_instruction_index: u16::MAX, + }; + + instruction_data.extend_from_slice(bytes_of(&offsets)); + + debug_assert_eq!(instruction_data.len(), public_key_offset); + + instruction_data.extend_from_slice(pubkey); + + debug_assert_eq!(instruction_data.len(), signature_offset); + + instruction_data.extend_from_slice(signature); + + debug_assert_eq!(instruction_data.len(), message_data_offset); + + instruction_data.extend_from_slice(message); + + Instruction { + program_id: solana_sdk_ids::ed25519_program::id(), + accounts: vec![], + data: instruction_data, + } + } + + fn test_case( + num_signatures: u16, + offsets: &Ed25519SignatureOffsets, + ) -> Result<(), PrecompileError> { + assert_eq!( + bytemuck::bytes_of(offsets).len(), + SIGNATURE_OFFSETS_SERIALIZED_SIZE + ); + + let mut instruction_data = vec![0u8; DATA_START]; + instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&num_signatures)); + instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(offsets)); + + verify( + &instruction_data, + &[&[0u8; 100]], + &FeatureSet::all_enabled(), + ) + } + + #[test] + fn test_invalid_offsets() { + solana_logger::setup(); + + let mut instruction_data = vec![0u8; DATA_START]; + let offsets = Ed25519SignatureOffsets::default(); + instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&1u16)); + instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(&offsets)); + instruction_data.truncate(instruction_data.len() - 1); + + assert_eq!( + verify( + &instruction_data, + &[&[0u8; 100]], + &FeatureSet::all_enabled(), + ), + Err(PrecompileError::InvalidInstructionDataSize) + ); + + let offsets = Ed25519SignatureOffsets { + signature_instruction_index: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + message_instruction_index: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + public_key_instruction_index: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_message_data_offsets() { + let offsets = Ed25519SignatureOffsets { + message_data_offset: 99, + message_data_size: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = Ed25519SignatureOffsets { + message_data_offset: 100, + message_data_size: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + message_data_offset: 100, + message_data_size: 1000, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + message_data_offset: u16::MAX, + message_data_size: u16::MAX, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_pubkey_offset() { + let offsets = Ed25519SignatureOffsets { + public_key_offset: u16::MAX, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + public_key_offset: 100 - PUBKEY_SERIALIZED_SIZE as u16 + 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_signature_offset() { + let offsets = Ed25519SignatureOffsets { + signature_offset: u16::MAX, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_ed25519() { + solana_logger::setup(); + + let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng()); + let message_arr = b"hello"; + let mut instruction = new_ed25519_instruction(&privkey, message_arr); + let feature_set = FeatureSet::all_enabled(); + + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + let index = loop { + let index = thread_rng().gen_range(0, instruction.data.len()); + // byte 1 is not used, so this would not cause the verify to fail + if index != 1 { + break index; + } + }; + + instruction.data[index] = instruction.data[index].wrapping_add(12); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_err()); + } + + #[test] + fn test_offsets_to_ed25519_instruction() { + solana_logger::setup(); + + let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng()); + let messages: [&[u8]; 3] = [b"hello", b"IBRL", b"goodbye"]; + let data_start = + messages.len() * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START; + let mut data_offset = data_start + PUBKEY_SERIALIZED_SIZE; + let (offsets, messages): (Vec<_>, Vec<_>) = messages + .into_iter() + .map(|message| { + let signature_offset = data_offset; + let message_data_offset = signature_offset + SIGNATURE_SERIALIZED_SIZE; + data_offset += SIGNATURE_SERIALIZED_SIZE + message.len(); + + let offsets = Ed25519SignatureOffsets { + signature_offset: signature_offset as u16, + signature_instruction_index: u16::MAX, + public_key_offset: data_start as u16, + public_key_instruction_index: u16::MAX, + message_data_offset: message_data_offset as u16, + message_data_size: message.len() as u16, + message_instruction_index: u16::MAX, + }; + + (offsets, message) + }) + .unzip(); + + let mut instruction = offsets_to_ed25519_instruction(&offsets); + + let pubkey = privkey.public.as_ref(); + instruction.data.extend_from_slice(pubkey); + + for message in messages { + let signature = privkey.sign(message).to_bytes(); + instruction.data.extend_from_slice(&signature); + instruction.data.extend_from_slice(message); + } + + let feature_set = FeatureSet::all_enabled(); + + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + let index = loop { + let index = thread_rng().gen_range(0, instruction.data.len()); + // byte 1 is not used, so this would not cause the verify to fail + if index != 1 { + break index; + } + }; + + instruction.data[index] = instruction.data[index].wrapping_add(12); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_err()); + } + + #[test] + fn test_ed25519_malleability() { + solana_logger::setup(); + + // sig created via ed25519_dalek: both pass + let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng()); + let message_arr = b"hello"; + let instruction = new_ed25519_instruction(&privkey, message_arr); + + let feature_set = FeatureSet::default(); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + let feature_set = FeatureSet::all_enabled(); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + // malleable sig: verify_strict does NOT pass + // for example, test number 5: + // https://github.com/C2SP/CCTV/tree/main/ed25519 + // R has low order (in fact R == 0) + let pubkey = + &hex::decode("10eb7c3acfb2bed3e0d6ab89bf5a3d6afddd1176ce4812e38d9fd485058fdb1f") + .unwrap(); + let signature = &hex::decode("00000000000000000000000000000000000000000000000000000000000000009472a69cd9a701a50d130ed52189e2455b23767db52cacb8716fb896ffeeac09").unwrap(); + let message = b"ed25519vectors 3"; + let instruction = new_ed25519_instruction_raw(pubkey, signature, message); + + let feature_set = FeatureSet::default(); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + // verify_strict does NOT pass + let feature_set = FeatureSet::all_enabled(); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_err()); + } +} diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs new file mode 100644 index 00000000000000..180e285d21990e --- /dev/null +++ b/precompiles/src/lib.rs @@ -0,0 +1,121 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +use { + lazy_static::lazy_static, + solana_feature_set::{enable_secp256r1_precompile, FeatureSet}, + solana_message::compiled_instruction::CompiledInstruction, + solana_precompile_error::PrecompileError, + solana_pubkey::Pubkey, +}; + +pub mod ed25519; +pub mod secp256k1; +pub mod secp256r1; + +/// All precompiled programs must implement the `Verify` function +pub type Verify = fn(&[u8], &[&[u8]], &FeatureSet) -> std::result::Result<(), PrecompileError>; + +/// Information on a precompiled program +pub struct Precompile { + /// Program id + pub program_id: Pubkey, + /// Feature to enable on, `None` indicates always enabled + pub feature: Option, + /// Verification function + pub verify_fn: Verify, +} +impl Precompile { + /// Creates a new `Precompile` + pub fn new(program_id: Pubkey, feature: Option, verify_fn: Verify) -> Self { + Precompile { + program_id, + feature, + verify_fn, + } + } + /// Check if a program id is this precompiled program + pub fn check_id(&self, program_id: &Pubkey, is_enabled: F) -> bool + where + F: Fn(&Pubkey) -> bool, + { + self.feature + .is_none_or(|ref feature_id| is_enabled(feature_id)) + && self.program_id == *program_id + } + /// Verify this precompiled program + pub fn verify( + &self, + data: &[u8], + instruction_datas: &[&[u8]], + feature_set: &FeatureSet, + ) -> std::result::Result<(), PrecompileError> { + (self.verify_fn)(data, instruction_datas, feature_set) + } +} + +lazy_static! { + /// The list of all precompiled programs + static ref PRECOMPILES: Vec = vec![ + Precompile::new( + solana_sdk_ids::secp256k1_program::id(), + None, // always enabled + secp256k1::verify, + ), + Precompile::new( + solana_sdk_ids::ed25519_program::id(), + None, // always enabled + ed25519::verify, + ), + Precompile::new( + solana_sdk_ids::secp256r1_program::id(), + Some(enable_secp256r1_precompile::id()), + secp256r1::verify, + ) + ]; +} + +/// Check if a program is a precompiled program +pub fn is_precompile(program_id: &Pubkey, is_enabled: F) -> bool +where + F: Fn(&Pubkey) -> bool, +{ + PRECOMPILES + .iter() + .any(|precompile| precompile.check_id(program_id, |feature_id| is_enabled(feature_id))) +} + +/// Find an enabled precompiled program +pub fn get_precompile(program_id: &Pubkey, is_enabled: F) -> Option<&Precompile> +where + F: Fn(&Pubkey) -> bool, +{ + PRECOMPILES + .iter() + .find(|precompile| precompile.check_id(program_id, |feature_id| is_enabled(feature_id))) +} + +pub fn get_precompiles<'a>() -> &'a [Precompile] { + &PRECOMPILES +} + +/// Check that a program is precompiled and if so verify it +pub fn verify_if_precompile( + program_id: &Pubkey, + precompile_instruction: &CompiledInstruction, + all_instructions: &[CompiledInstruction], + feature_set: &FeatureSet, +) -> Result<(), PrecompileError> { + for precompile in PRECOMPILES.iter() { + if precompile.check_id(program_id, |feature_id| feature_set.is_active(feature_id)) { + let instruction_datas: Vec<_> = all_instructions + .iter() + .map(|instruction| instruction.data.as_ref()) + .collect(); + return precompile.verify( + &precompile_instruction.data, + &instruction_datas, + feature_set, + ); + } + } + Ok(()) +} diff --git a/precompiles/src/secp256k1.rs b/precompiles/src/secp256k1.rs new file mode 100644 index 00000000000000..6e2512fd5eee43 --- /dev/null +++ b/precompiles/src/secp256k1.rs @@ -0,0 +1,387 @@ +use { + digest::Digest, + solana_feature_set::FeatureSet, + solana_precompile_error::PrecompileError, + solana_secp256k1_program::{ + construct_eth_pubkey, SecpSignatureOffsets, HASHED_PUBKEY_SERIALIZED_SIZE, + SIGNATURE_OFFSETS_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE, + }, +}; + +/// Verifies the signatures specified in the secp256k1 instruction data. +/// +/// This is the same as the verification routine executed by the runtime's secp256k1 native program, +/// and is primarily of use to the runtime. +/// +/// `data` is the secp256k1 program's instruction data. `instruction_datas` is +/// the full slice of instruction datas for all instructions in the transaction, +/// including the secp256k1 program's instruction data. +/// +/// `feature_set` is the set of active Solana features. It is used to enable or +/// disable a few minor additional checks that were activated on chain +/// subsequent to the addition of the secp256k1 native program. For many +/// purposes passing `FeatureSet::all_enabled()` is reasonable. +pub fn verify( + data: &[u8], + instruction_datas: &[&[u8]], + _feature_set: &FeatureSet, +) -> Result<(), PrecompileError> { + if data.is_empty() { + return Err(PrecompileError::InvalidInstructionDataSize); + } + let count = data[0] as usize; + if count == 0 && data.len() > 1 { + // count is zero but the instruction data indicates that is probably not + // correct, fail the instruction to catch probable invalid secp256k1 + // instruction construction. + return Err(PrecompileError::InvalidInstructionDataSize); + } + let expected_data_size = count + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(1); + if data.len() < expected_data_size { + return Err(PrecompileError::InvalidInstructionDataSize); + } + for i in 0..count { + let start = i + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(1); + let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE); + + let offsets: SecpSignatureOffsets = bincode::deserialize(&data[start..end]) + .map_err(|_| PrecompileError::InvalidSignature)?; + + // Parse out signature + let signature_index = offsets.signature_instruction_index as usize; + if signature_index >= instruction_datas.len() { + return Err(PrecompileError::InvalidInstructionDataSize); + } + let signature_instruction = instruction_datas[signature_index]; + let sig_start = offsets.signature_offset as usize; + let sig_end = sig_start.saturating_add(SIGNATURE_SERIALIZED_SIZE); + if sig_end >= signature_instruction.len() { + return Err(PrecompileError::InvalidSignature); + } + + let signature = libsecp256k1::Signature::parse_standard_slice( + &signature_instruction[sig_start..sig_end], + ) + .map_err(|_| PrecompileError::InvalidSignature)?; + + let recovery_id = libsecp256k1::RecoveryId::parse(signature_instruction[sig_end]) + .map_err(|_| PrecompileError::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 = libsecp256k1::recover( + &libsecp256k1::Message::parse_slice(&message_hash).unwrap(), + &signature, + &recovery_id, + ) + .map_err(|_| PrecompileError::InvalidSignature)?; + let eth_address = construct_eth_pubkey(&pubkey); + + if eth_address_slice != eth_address { + return Err(PrecompileError::InvalidSignature); + } + } + Ok(()) +} + +fn get_data_slice<'a>( + instruction_datas: &'a [&[u8]], + instruction_index: u8, + offset_start: u16, + size: usize, +) -> Result<&'a [u8], PrecompileError> { + let signature_index = instruction_index as usize; + if signature_index >= instruction_datas.len() { + return Err(PrecompileError::InvalidDataOffsets); + } + let signature_instruction = &instruction_datas[signature_index]; + let start = offset_start as usize; + let end = start.saturating_add(size); + if end > signature_instruction.len() { + return Err(PrecompileError::InvalidSignature); + } + + Ok(&instruction_datas[signature_index][start..end]) +} + +#[cfg(test)] +pub mod tests { + use { + super::*, + rand0_7::{thread_rng, Rng}, + solana_feature_set::FeatureSet, + solana_keccak_hasher as keccak, + solana_secp256k1_program::{new_secp256k1_instruction, DATA_START}, + }; + + fn test_case( + num_signatures: u8, + offsets: &SecpSignatureOffsets, + ) -> Result<(), PrecompileError> { + let mut instruction_data = vec![0u8; DATA_START]; + instruction_data[0] = num_signatures; + let writer = std::io::Cursor::new(&mut instruction_data[1..]); + bincode::serialize_into(writer, &offsets).unwrap(); + let feature_set = FeatureSet::all_enabled(); + verify(&instruction_data, &[&[0u8; 100]], &feature_set) + } + + #[test] + fn test_invalid_offsets() { + solana_logger::setup(); + + let mut instruction_data = vec![0u8; DATA_START]; + let offsets = SecpSignatureOffsets::default(); + instruction_data[0] = 1; + let writer = std::io::Cursor::new(&mut instruction_data[1..]); + bincode::serialize_into(writer, &offsets).unwrap(); + instruction_data.truncate(instruction_data.len() - 1); + let feature_set = FeatureSet::all_enabled(); + + assert_eq!( + verify(&instruction_data, &[&[0u8; 100]], &feature_set), + Err(PrecompileError::InvalidInstructionDataSize) + ); + + let offsets = SecpSignatureOffsets { + signature_instruction_index: 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidInstructionDataSize) + ); + + let offsets = SecpSignatureOffsets { + message_instruction_index: 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = SecpSignatureOffsets { + eth_address_instruction_index: 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_message_data_offsets() { + let offsets = SecpSignatureOffsets { + message_data_offset: 99, + message_data_size: 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = SecpSignatureOffsets { + message_data_offset: 100, + message_data_size: 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = SecpSignatureOffsets { + message_data_offset: 100, + message_data_size: 1000, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = SecpSignatureOffsets { + message_data_offset: u16::MAX, + message_data_size: u16::MAX, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + } + + #[test] + fn test_eth_offset() { + let offsets = SecpSignatureOffsets { + eth_address_offset: u16::MAX, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = SecpSignatureOffsets { + eth_address_offset: 100 - HASHED_PUBKEY_SERIALIZED_SIZE as u16 + 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + } + + #[test] + fn test_signature_offset() { + let offsets = SecpSignatureOffsets { + signature_offset: u16::MAX, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = SecpSignatureOffsets { + signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1, + ..SecpSignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + } + + #[test] + fn test_count_is_zero_but_sig_data_exists() { + solana_logger::setup(); + + let mut instruction_data = vec![0u8; DATA_START]; + let offsets = SecpSignatureOffsets::default(); + instruction_data[0] = 0; + let writer = std::io::Cursor::new(&mut instruction_data[1..]); + bincode::serialize_into(writer, &offsets).unwrap(); + let feature_set = FeatureSet::all_enabled(); + + assert_eq!( + verify(&instruction_data, &[&[0u8; 100]], &feature_set), + Err(PrecompileError::InvalidInstructionDataSize) + ); + } + + #[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 = libsecp256k1::SecretKey::random(&mut thread_rng()); + let message_arr = b"hello"; + let mut instruction = new_secp256k1_instruction(&secp_privkey, message_arr); + let feature_set = solana_feature_set::FeatureSet::all_enabled(); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + let index = thread_rng().gen_range(0, instruction.data.len()); + instruction.data[index] = instruction.data[index].wrapping_add(12); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_err()); + } + + // Signatures are malleable. + #[test] + fn test_malleability() { + solana_logger::setup(); + + let secret_key = libsecp256k1::SecretKey::random(&mut thread_rng()); + let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key); + let eth_address = construct_eth_pubkey(&public_key); + + let message = b"hello"; + let message_hash = { + let mut hasher = keccak::Hasher::default(); + hasher.hash(message); + hasher.result() + }; + + let secp_message = libsecp256k1::Message::parse(&message_hash.0); + let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secret_key); + + // Flip the S value in the signature to make a different but valid signature. + let mut alt_signature = signature; + alt_signature.s = -alt_signature.s; + let alt_recovery_id = libsecp256k1::RecoveryId::parse(recovery_id.serialize() ^ 1).unwrap(); + + let mut data: Vec = vec![]; + let mut both_offsets = vec![]; + + // Verify both signatures of the same message. + let sigs = [(signature, recovery_id), (alt_signature, alt_recovery_id)]; + for (signature, recovery_id) in sigs.iter() { + let signature_offset = data.len(); + data.extend(signature.serialize()); + data.push(recovery_id.serialize()); + let eth_address_offset = data.len(); + data.extend(eth_address); + let message_data_offset = data.len(); + data.extend(message); + + let data_start = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE * 2; + + let offsets = SecpSignatureOffsets { + signature_offset: (signature_offset + data_start) as u16, + signature_instruction_index: 0, + eth_address_offset: (eth_address_offset + data_start) as u16, + eth_address_instruction_index: 0, + message_data_offset: (message_data_offset + data_start) as u16, + message_data_size: message.len() as u16, + message_instruction_index: 0, + }; + + both_offsets.push(offsets); + } + + let mut instruction_data: Vec = vec![2]; + + for offsets in both_offsets { + let offsets = bincode::serialize(&offsets).unwrap(); + instruction_data.extend(offsets); + } + + instruction_data.extend(data); + + verify( + &instruction_data, + &[&instruction_data], + &FeatureSet::all_enabled(), + ) + .unwrap(); + } +} diff --git a/precompiles/src/secp256r1.rs b/precompiles/src/secp256r1.rs new file mode 100644 index 00000000000000..ead081f8b19078 --- /dev/null +++ b/precompiles/src/secp256r1.rs @@ -0,0 +1,538 @@ +use { + openssl::{ + bn::{BigNum, BigNumContext}, + ec::{EcGroup, EcKey, EcPoint}, + nid::Nid, + pkey::PKey, + sign::Verifier, + }, + solana_feature_set::FeatureSet, + solana_precompile_error::PrecompileError, + solana_secp256r1_program::{ + Secp256r1SignatureOffsets, COMPRESSED_PUBKEY_SERIALIZED_SIZE, FIELD_SIZE, + SECP256R1_HALF_ORDER, SECP256R1_ORDER_MINUS_ONE, SIGNATURE_OFFSETS_SERIALIZED_SIZE, + SIGNATURE_OFFSETS_START, SIGNATURE_SERIALIZED_SIZE, + }, +}; + +pub fn verify( + data: &[u8], + instruction_datas: &[&[u8]], + _feature_set: &FeatureSet, +) -> Result<(), PrecompileError> { + if data.len() < SIGNATURE_OFFSETS_START { + return Err(PrecompileError::InvalidInstructionDataSize); + } + let num_signatures = data[0] as usize; + if num_signatures == 0 { + return Err(PrecompileError::InvalidInstructionDataSize); + } + if num_signatures > 8 { + return Err(PrecompileError::InvalidInstructionDataSize); + } + + let expected_data_size = num_signatures + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(SIGNATURE_OFFSETS_START); + + // We do not check or use the byte at data[1] + if data.len() < expected_data_size { + return Err(PrecompileError::InvalidInstructionDataSize); + } + + // Parse half order from constant + let half_order: BigNum = + BigNum::from_slice(&SECP256R1_HALF_ORDER).map_err(|_| PrecompileError::InvalidSignature)?; + + // Parse order - 1 from constant + let order_minus_one: BigNum = BigNum::from_slice(&SECP256R1_ORDER_MINUS_ONE) + .map_err(|_| PrecompileError::InvalidSignature)?; + + // Create a BigNum for 1 + let one = BigNum::from_u32(1).map_err(|_| PrecompileError::InvalidSignature)?; + + // Define curve group + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1) + .map_err(|_| PrecompileError::InvalidSignature)?; + let mut ctx = BigNumContext::new().map_err(|_| PrecompileError::InvalidSignature)?; + + for i in 0..num_signatures { + let start = i + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(SIGNATURE_OFFSETS_START); + let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE); + + // bytemuck wants structures aligned + let offsets: &Secp256r1SignatureOffsets = bytemuck::try_from_bytes(&data[start..end]) + .map_err(|_| PrecompileError::InvalidDataOffsets)?; + + // Parse out signature + let signature = get_data_slice( + data, + instruction_datas, + offsets.signature_instruction_index, + offsets.signature_offset, + SIGNATURE_SERIALIZED_SIZE, + )?; + + // Parse out pubkey + let pubkey = get_data_slice( + data, + instruction_datas, + offsets.public_key_instruction_index, + offsets.public_key_offset, + COMPRESSED_PUBKEY_SERIALIZED_SIZE, + )?; + + // Parse out message + let message = get_data_slice( + data, + instruction_datas, + offsets.message_instruction_index, + offsets.message_data_offset, + offsets.message_data_size as usize, + )?; + + let r_bignum = BigNum::from_slice(&signature[..FIELD_SIZE]) + .map_err(|_| PrecompileError::InvalidSignature)?; + let s_bignum = BigNum::from_slice(&signature[FIELD_SIZE..]) + .map_err(|_| PrecompileError::InvalidSignature)?; + + // Check that the signature is generally in range + let within_range = r_bignum >= one + && r_bignum <= order_minus_one + && s_bignum >= one + && s_bignum <= half_order; + + if !within_range { + return Err(PrecompileError::InvalidSignature); + } + + // Create an ECDSA signature object from the ASN.1 integers + let ecdsa_sig = openssl::ecdsa::EcdsaSig::from_private_components(r_bignum, s_bignum) + .and_then(|sig| sig.to_der()) + .map_err(|_| PrecompileError::InvalidSignature)?; + + let public_key_point = EcPoint::from_bytes(&group, pubkey, &mut ctx) + .map_err(|_| PrecompileError::InvalidPublicKey)?; + let public_key = EcKey::from_public_key(&group, &public_key_point) + .map_err(|_| PrecompileError::InvalidPublicKey)?; + let public_key_as_pkey = + PKey::from_ec_key(public_key).map_err(|_| PrecompileError::InvalidPublicKey)?; + + let mut verifier = + Verifier::new(openssl::hash::MessageDigest::sha256(), &public_key_as_pkey) + .map_err(|_| PrecompileError::InvalidSignature)?; + verifier + .update(message) + .map_err(|_| PrecompileError::InvalidSignature)?; + + if !verifier + .verify(&ecdsa_sig) + .map_err(|_| PrecompileError::InvalidSignature)? + { + return Err(PrecompileError::InvalidSignature); + } + } + Ok(()) +} + +fn get_data_slice<'a>( + data: &'a [u8], + instruction_datas: &'a [&[u8]], + instruction_index: u16, + offset_start: u16, + size: usize, +) -> Result<&'a [u8], PrecompileError> { + let instruction = if instruction_index == u16::MAX { + data + } else { + let signature_index = instruction_index as usize; + if signature_index >= instruction_datas.len() { + return Err(PrecompileError::InvalidDataOffsets); + } + instruction_datas[signature_index] + }; + + let start = offset_start as usize; + let end = start.saturating_add(size); + if end > instruction.len() { + return Err(PrecompileError::InvalidDataOffsets); + } + + Ok(&instruction[start..end]) +} + +#[cfg(test)] +mod tests { + use { + super::*, + bytemuck::bytes_of, + solana_feature_set::FeatureSet, + solana_secp256r1_program::{new_secp256r1_instruction, DATA_START, SECP256R1_ORDER}, + }; + + fn test_case( + num_signatures: u16, + offsets: &Secp256r1SignatureOffsets, + ) -> Result<(), PrecompileError> { + assert_eq!( + bytemuck::bytes_of(offsets).len(), + SIGNATURE_OFFSETS_SERIALIZED_SIZE + ); + + let mut instruction_data = vec![0u8; DATA_START]; + instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&num_signatures)); + instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(offsets)); + verify( + &instruction_data, + &[&[0u8; 100]], + &FeatureSet::all_enabled(), + ) + } + + #[test] + fn test_invalid_offsets() { + solana_logger::setup(); + + let mut instruction_data = vec![0u8; DATA_START]; + let offsets = Secp256r1SignatureOffsets::default(); + instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&1u16)); + instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(&offsets)); + instruction_data.truncate(instruction_data.len() - 1); + + assert_eq!( + verify( + &instruction_data, + &[&[0u8; 100]], + &FeatureSet::all_enabled() + ), + Err(PrecompileError::InvalidInstructionDataSize) + ); + + let offsets = Secp256r1SignatureOffsets { + signature_instruction_index: 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Secp256r1SignatureOffsets { + message_instruction_index: 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Secp256r1SignatureOffsets { + public_key_instruction_index: 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_invalid_signature_data_size() { + solana_logger::setup(); + + // Test data.len() < SIGNATURE_OFFSETS_START + let small_data = vec![0u8; SIGNATURE_OFFSETS_START - 1]; + assert_eq!( + verify(&small_data, &[&[]], &FeatureSet::all_enabled()), + Err(PrecompileError::InvalidInstructionDataSize) + ); + + // Test num_signatures == 0 + let mut zero_sigs_data = vec![0u8; DATA_START]; + zero_sigs_data[0] = 0; // Set num_signatures to 0 + assert_eq!( + verify(&zero_sigs_data, &[&[]], &FeatureSet::all_enabled()), + Err(PrecompileError::InvalidInstructionDataSize) + ); + + // Test num_signatures > 8 + let mut too_many_sigs = vec![0u8; DATA_START]; + too_many_sigs[0] = 9; // Set num_signatures to 9 + assert_eq!( + verify(&too_many_sigs, &[&[]], &FeatureSet::all_enabled()), + Err(PrecompileError::InvalidInstructionDataSize) + ); + } + #[test] + fn test_message_data_offsets() { + let offsets = Secp256r1SignatureOffsets { + message_data_offset: 99, + message_data_size: 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidSignature) + ); + + let offsets = Secp256r1SignatureOffsets { + message_data_offset: 100, + message_data_size: 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Secp256r1SignatureOffsets { + message_data_offset: 100, + message_data_size: 1000, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Secp256r1SignatureOffsets { + message_data_offset: u16::MAX, + message_data_size: u16::MAX, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_pubkey_offset() { + let offsets = Secp256r1SignatureOffsets { + public_key_offset: u16::MAX, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Secp256r1SignatureOffsets { + public_key_offset: 100 - (COMPRESSED_PUBKEY_SERIALIZED_SIZE as u16) + 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_signature_offset() { + let offsets = Secp256r1SignatureOffsets { + signature_offset: u16::MAX, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + + let offsets = Secp256r1SignatureOffsets { + signature_offset: 100 - (SIGNATURE_SERIALIZED_SIZE as u16) + 1, + ..Secp256r1SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(PrecompileError::InvalidDataOffsets) + ); + } + + #[test] + fn test_secp256r1() { + solana_logger::setup(); + let message_arr = b"hello"; + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let signing_key = EcKey::generate(&group).unwrap(); + let mut instruction = new_secp256r1_instruction(message_arr, signing_key).unwrap(); + let feature_set = FeatureSet::all_enabled(); + + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + + // The message is the last field in the instruction data so + // changing its last byte will also change the signature validity + let message_byte_index = instruction.data.len() - 1; + instruction.data[message_byte_index] = + instruction.data[message_byte_index].wrapping_add(12); + + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_err()); + } + + #[test] + fn test_secp256r1_high_s() { + solana_logger::setup(); + let message_arr = b"hello"; + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let signing_key = EcKey::generate(&group).unwrap(); + let mut instruction = new_secp256r1_instruction(message_arr, signing_key).unwrap(); + + // To double check that the untampered low-S value signature passes + let feature_set = FeatureSet::all_enabled(); + let tx_pass = verify( + instruction.data.as_slice(), + &[instruction.data.as_slice()], + &feature_set, + ); + assert!(tx_pass.is_ok()); + + // Determine offsets at which to perform the S-value manipulation + let public_key_offset = DATA_START; + let signature_offset = public_key_offset + COMPRESSED_PUBKEY_SERIALIZED_SIZE; + let s_offset = signature_offset + FIELD_SIZE; + + // Create a high S value by doing order - s + let order = BigNum::from_slice(&SECP256R1_ORDER).unwrap(); + let current_s = + BigNum::from_slice(&instruction.data[s_offset..s_offset + FIELD_SIZE]).unwrap(); + let mut high_s = BigNum::new().unwrap(); + high_s.checked_sub(&order, ¤t_s).unwrap(); + + // Replace the S value in the signature with our high S + instruction.data[s_offset..s_offset + FIELD_SIZE].copy_from_slice(&high_s.to_vec()); + + let tx_fail = verify(&instruction.data, &[&instruction.data], &feature_set); + assert!(tx_fail.unwrap_err() == PrecompileError::InvalidSignature); + } + #[test] + fn test_new_secp256r1_instruction_31byte_components() { + solana_logger::setup(); + let message_arr = b"hello"; + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let signing_key = EcKey::generate(&group).unwrap(); + + // Keep generating signatures until we get one with a 31-byte component + loop { + let instruction = new_secp256r1_instruction(message_arr, signing_key.clone()).unwrap(); + + // Extract r and s from the signature + let signature_offset = DATA_START + COMPRESSED_PUBKEY_SERIALIZED_SIZE; + let r = &instruction.data[signature_offset..signature_offset + FIELD_SIZE]; + let s = + &instruction.data[signature_offset + FIELD_SIZE..signature_offset + 2 * FIELD_SIZE]; + + // Convert to BigNum and back to get byte representation + let r_bn = BigNum::from_slice(r).unwrap(); + let s_bn = BigNum::from_slice(s).unwrap(); + let r_bytes = r_bn.to_vec(); + let s_bytes = s_bn.to_vec(); + + if r_bytes.len() == 31 || s_bytes.len() == 31 { + // Once found, verify the signature and break out of the loop + let feature_set = FeatureSet::all_enabled(); + assert!(verify(&instruction.data, &[&instruction.data], &feature_set).is_ok()); + break; + } + } + } + + #[test] + fn test_new_secp256r1_instruction_signing_key() { + solana_logger::setup(); + let message_arr = b"hello"; + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let signing_key = EcKey::generate(&group).unwrap(); + assert!(new_secp256r1_instruction(message_arr, signing_key).is_ok()); + + let incorrect_group = EcGroup::from_curve_name(Nid::X9_62_PRIME192V1).unwrap(); + let incorrect_key = EcKey::generate(&incorrect_group).unwrap(); + assert!(new_secp256r1_instruction(message_arr, incorrect_key).is_err()); + } + #[test] + fn test_secp256r1_order() { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let mut ctx = BigNumContext::new().unwrap(); + let mut openssl_order = BigNum::new().unwrap(); + group.order(&mut openssl_order, &mut ctx).unwrap(); + + let our_order = BigNum::from_slice(&SECP256R1_ORDER).unwrap(); + assert_eq!(our_order, openssl_order); + } + + #[test] + fn test_secp256r1_order_minus_one() { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let mut ctx = BigNumContext::new().unwrap(); + let mut openssl_order = BigNum::new().unwrap(); + group.order(&mut openssl_order, &mut ctx).unwrap(); + + let mut expected_order_minus_one = BigNum::new().unwrap(); + expected_order_minus_one + .checked_sub(&openssl_order, &BigNum::from_u32(1).unwrap()) + .unwrap(); + + let our_order_minus_one = BigNum::from_slice(&SECP256R1_ORDER_MINUS_ONE).unwrap(); + assert_eq!(our_order_minus_one, expected_order_minus_one); + } + + #[test] + fn test_secp256r1_half_order() { + // Get the secp256r1 curve group + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + + // Get the order from OpenSSL + let mut ctx = BigNumContext::new().unwrap(); + let mut openssl_order = BigNum::new().unwrap(); + group.order(&mut openssl_order, &mut ctx).unwrap(); + + // Calculate half order + let mut calculated_half_order = BigNum::new().unwrap(); + let two = BigNum::from_u32(2).unwrap(); + calculated_half_order + .checked_div(&openssl_order, &two, &mut ctx) + .unwrap(); + + // Get our constant half order + let our_half_order = BigNum::from_slice(&SECP256R1_HALF_ORDER).unwrap(); + + // Compare the calculated half order with our constant + assert_eq!(calculated_half_order, our_half_order); + } + + #[test] + fn test_secp256r1_order_relationships() { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let mut ctx = BigNumContext::new().unwrap(); + let mut openssl_order = BigNum::new().unwrap(); + group.order(&mut openssl_order, &mut ctx).unwrap(); + + let our_order = BigNum::from_slice(&SECP256R1_ORDER).unwrap(); + let our_order_minus_one = BigNum::from_slice(&SECP256R1_ORDER_MINUS_ONE).unwrap(); + let our_half_order = BigNum::from_slice(&SECP256R1_HALF_ORDER).unwrap(); + + // Verify our order matches OpenSSL's order + assert_eq!(our_order, openssl_order); + + // Verify order - 1 + let mut expected_order_minus_one = BigNum::new().unwrap(); + expected_order_minus_one + .checked_sub(&openssl_order, &BigNum::from_u32(1).unwrap()) + .unwrap(); + assert_eq!(our_order_minus_one, expected_order_minus_one); + + // Verify half order + let mut expected_half_order = BigNum::new().unwrap(); + expected_half_order + .checked_div(&openssl_order, &BigNum::from_u32(2).unwrap(), &mut ctx) + .unwrap(); + assert_eq!(our_half_order, expected_half_order); + + // Verify half order * 2 = order - 1 + let mut double_half_order = BigNum::new().unwrap(); + double_half_order + .checked_mul(&our_half_order, &BigNum::from_u32(2).unwrap(), &mut ctx) + .unwrap(); + assert_eq!(double_half_order, expected_order_minus_one); + } +} diff --git a/program-runtime/Cargo.toml b/program-runtime/Cargo.toml index f1563a4c1c21da..9687ec29b16667 100644 --- a/program-runtime/Cargo.toml +++ b/program-runtime/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-precompiles = { workspace = true } base64 = { workspace = true } bincode = { workspace = true } enum-iterator = { workspace = true } @@ -36,7 +37,6 @@ solana-last-restart-slot = { workspace = true } solana-log-collector = { workspace = true } solana-measure = { workspace = true } solana-metrics = { workspace = true, optional = true } -solana-precompiles = { workspace = true } solana-pubkey = { workspace = true } solana-rent = { workspace = true } solana-sbpf = { workspace = true } diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 65c9cd1638827e..4d31ca16aba82e 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -7,6 +7,7 @@ use { stable_log, sysvar_cache::SysvarCache, }, + agave_precompiles::Precompile, solana_account::{create_account_shared_data_for_test, AccountSharedData}, solana_clock::Slot, solana_compute_budget::compute_budget::ComputeBudget, @@ -19,7 +20,6 @@ use { solana_instruction::{error::InstructionError, AccountMeta}, solana_log_collector::{ic_msg, LogCollector}, solana_measure::measure::Measure, - solana_precompiles::Precompile, solana_pubkey::Pubkey, solana_sbpf::{ ebpf::MM_HEAP_START, diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index 2f35e3c2627ad5..b54247d7328fe8 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-precompiles = { workspace = true } bincode = { workspace = true } libsecp256k1 = { workspace = true } qualifier_attr = { workspace = true } @@ -34,7 +35,6 @@ solana-log-collector = { workspace = true } solana-measure = { workspace = true } solana-packet = { workspace = true } solana-poseidon = { workspace = true } -solana-precompiles = { workspace = true } solana-program-entrypoint = { workspace = true } solana-program-memory = { workspace = true } solana-program-runtime = { workspace = true } diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index e77ecd9599066f..0fb7124b8034dc 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -12,6 +12,7 @@ pub use self::{ }; #[allow(deprecated)] use { + agave_precompiles::is_precompile, solana_account_info::AccountInfo, solana_big_mod_exp::{big_mod_exp, BigModExpParams}, solana_blake3_hasher as blake3, @@ -39,7 +40,6 @@ use { solana_keccak_hasher as keccak, solana_log_collector::{ic_logger_msg, ic_msg}, solana_poseidon as poseidon, - solana_precompiles::is_precompile, solana_program_entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS}, solana_program_memory::is_nonoverlapping, solana_program_runtime::{invoke_context::InvokeContext, stable_log}, diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index b5ce1b081edd8f..02e7c76a545874 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -83,6 +83,28 @@ dependencies = [ "thiserror 2.0.11", ] +[[package]] +name = "agave-precompiles" +version = "2.2.6" +dependencies = [ + "bincode", + "bytemuck", + "digest 0.10.7", + "ed25519-dalek", + "lazy_static", + "libsecp256k1 0.6.0", + "openssl", + "sha3", + "solana-ed25519-program", + "solana-feature-set", + "solana-message", + "solana-precompile-error", + "solana-pubkey", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-secp256r1-program", +] + [[package]] name = "agave-reserved-account-keys" version = "2.2.6" @@ -5312,6 +5334,7 @@ dependencies = [ name = "solana-bpf-loader-program" version = "2.2.6" dependencies = [ + "agave-precompiles", "bincode", "libsecp256k1 0.6.0", "qualifier_attr", @@ -5336,7 +5359,6 @@ dependencies = [ "solana-measure", "solana-packet", "solana-poseidon", - "solana-precompiles", "solana-program-entrypoint", "solana-program-memory", "solana-program-runtime", @@ -5871,9 +5893,9 @@ dependencies = [ [[package]] name = "solana-ed25519-program" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0c4dfce08d71d8f1e9b7d1b4e2c7101a8109903ad481acbbc1119a73d459f2" +checksum = "9d0fc717048fdbe5d2ee7d673d73e6a30a094002f4a29ca7630ac01b6bddec04" dependencies = [ "bytemuck", "bytemuck_derive", @@ -6030,9 +6052,9 @@ dependencies = [ [[package]] name = "solana-feature-set" -version = "2.2.1" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e1d3b52b4a014efeaaab67f14e40af3972a4be61c523d612860db8e3145529" +checksum = "92f6c09cc41059c0e03ccbee7f5d4cc0a315d68ef0d59b67eb90246adfd8cc35" dependencies = [ "ahash 0.8.11", "lazy_static", @@ -6900,6 +6922,7 @@ dependencies = [ name = "solana-program-runtime" version = "2.2.6" dependencies = [ + "agave-precompiles", "base64 0.22.1", "bincode", "enum-iterator", @@ -6920,7 +6943,6 @@ dependencies = [ "solana-log-collector", "solana-measure", "solana-metrics", - "solana-precompiles", "solana-pubkey", "solana-rent", "solana-sbpf", @@ -7298,6 +7320,7 @@ dependencies = [ name = "solana-runtime" version = "2.2.6" dependencies = [ + "agave-precompiles", "agave-reserved-account-keys", "ahash 0.8.11", "aquamarine", @@ -8048,9 +8071,9 @@ dependencies = [ [[package]] name = "solana-secp256r1-program" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ea9282950921611bd9e0200da7236fbb1d4f8388942f8451bd55e9f3cb228f" +checksum = "5cda2aa1bbaceda14763c4f142a00b486f2f262cfd901bd0410649ad0404d5f7" dependencies = [ "bytemuck", "openssl", @@ -8385,6 +8408,7 @@ dependencies = [ name = "solana-svm" version = "2.2.6" dependencies = [ + "agave-precompiles", "ahash 0.8.11", "itertools 0.12.1", "log", @@ -8408,7 +8432,6 @@ dependencies = [ "solana-message", "solana-nonce", "solana-nonce-account", - "solana-precompiles", "solana-program", "solana-program-runtime", "solana-pubkey", diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 7482cd05353755..6c729499ca147e 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -43,7 +43,7 @@ solana-compute-budget = { path = "../../compute-budget", version = "=2.2.6" } solana-compute-budget-instruction = { path = "../../compute-budget-instruction", version = "=2.2.6" } solana-curve25519 = { path = "../../curves/curve25519", version = "=2.2.6" } solana-decode-error = "=2.2.1" -solana-feature-set = "=2.2.1" +solana-feature-set = "=2.2.4" # will be removed solana-fee = { path = "../../fee", version = "=2.2.6" } solana-ledger = { path = "../../ledger", version = "=2.2.6" } solana-log-collector = { path = "../../log-collector", version = "=2.2.6" } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index e75844f0f30722..21014eeba2098d 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-precompiles = { workspace = true } agave-reserved-account-keys = { workspace = true } ahash = { workspace = true } aquamarine = { workspace = true } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index ecef0176ea52f7..acad358854f6ec 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -58,6 +58,7 @@ use { verify_precompiles::verify_precompiles, }, accounts_lt_hash::{CacheValue as AccountsLtHashCacheValue, Stats as AccountsLtHashStats}, + agave_precompiles::get_precompiles, agave_reserved_account_keys::ReservedAccountKeys, ahash::AHashSet, dashmap::{DashMap, DashSet}, @@ -133,7 +134,6 @@ use { native_loader, native_token::LAMPORTS_PER_SOL, packet::PACKET_DATA_SIZE, - precompiles::get_precompiles, pubkey::Pubkey, rent_collector::{CollectedInfo, RentCollector}, rent_debits::RentDebits, diff --git a/runtime/src/verify_precompiles.rs b/runtime/src/verify_precompiles.rs index d199a7b84317a8..a1e183de5e946a 100644 --- a/runtime/src/verify_precompiles.rs +++ b/runtime/src/verify_precompiles.rs @@ -1,8 +1,8 @@ use { + agave_precompiles::get_precompiles, solana_feature_set::FeatureSet, solana_sdk::{ instruction::InstructionError, - precompiles::get_precompiles, transaction::{Result, TransactionError}, }, solana_svm_transaction::svm_message::SVMMessage, diff --git a/svm/Cargo.toml b/svm/Cargo.toml index cdb2debe5169b7..1cb1cb4c313a44 100644 --- a/svm/Cargo.toml +++ b/svm/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +agave-precompiles = { workspace = true } ahash = { workspace = true } itertools = { workspace = true } log = { workspace = true } @@ -39,7 +40,6 @@ solana-measure = { workspace = true } solana-message = { workspace = true } solana-nonce = { workspace = true } solana-nonce-account = { workspace = true } -solana-precompiles = { workspace = true } solana-program = { workspace = true, default-features = false } solana-program-runtime = { workspace = true, features = ["metrics"] } solana-pubkey = { workspace = true } diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index 23c5c96c9540bf..cc1766d0ac6f3a 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -83,6 +83,28 @@ dependencies = [ "thiserror 2.0.11", ] +[[package]] +name = "agave-precompiles" +version = "2.2.6" +dependencies = [ + "bincode", + "bytemuck", + "digest 0.10.7", + "ed25519-dalek", + "lazy_static", + "libsecp256k1", + "openssl", + "sha3", + "solana-ed25519-program", + "solana-feature-set", + "solana-message", + "solana-precompile-error", + "solana-pubkey", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-secp256r1-program", +] + [[package]] name = "agave-reserved-account-keys" version = "2.2.6" @@ -5168,6 +5190,7 @@ dependencies = [ name = "solana-bpf-loader-program" version = "2.2.6" dependencies = [ + "agave-precompiles", "bincode", "libsecp256k1", "qualifier_attr", @@ -5192,7 +5215,6 @@ dependencies = [ "solana-measure", "solana-packet", "solana-poseidon", - "solana-precompiles", "solana-program-entrypoint", "solana-program-memory", "solana-program-runtime", @@ -5716,9 +5738,9 @@ dependencies = [ [[package]] name = "solana-ed25519-program" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0c4dfce08d71d8f1e9b7d1b4e2c7101a8109903ad481acbbc1119a73d459f2" +checksum = "9d0fc717048fdbe5d2ee7d673d73e6a30a094002f4a29ca7630ac01b6bddec04" dependencies = [ "bytemuck", "bytemuck_derive", @@ -5875,9 +5897,9 @@ dependencies = [ [[package]] name = "solana-feature-set" -version = "2.2.1" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e1d3b52b4a014efeaaab67f14e40af3972a4be61c523d612860db8e3145529" +checksum = "92f6c09cc41059c0e03ccbee7f5d4cc0a315d68ef0d59b67eb90246adfd8cc35" dependencies = [ "ahash 0.8.11", "lazy_static", @@ -6722,6 +6744,7 @@ dependencies = [ name = "solana-program-runtime" version = "2.2.6" dependencies = [ + "agave-precompiles", "base64 0.22.1", "bincode", "enum-iterator", @@ -6742,7 +6765,6 @@ dependencies = [ "solana-log-collector", "solana-measure", "solana-metrics", - "solana-precompiles", "solana-pubkey", "solana-rent", "solana-sbpf", @@ -7120,6 +7142,7 @@ dependencies = [ name = "solana-runtime" version = "2.2.6" dependencies = [ + "agave-precompiles", "agave-reserved-account-keys", "ahash 0.8.11", "aquamarine", @@ -7368,9 +7391,9 @@ dependencies = [ [[package]] name = "solana-secp256r1-program" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ea9282950921611bd9e0200da7236fbb1d4f8388942f8451bd55e9f3cb228f" +checksum = "5cda2aa1bbaceda14763c4f142a00b486f2f262cfd901bd0410649ad0404d5f7" dependencies = [ "bytemuck", "openssl", @@ -7705,6 +7728,7 @@ dependencies = [ name = "solana-svm" version = "2.2.6" dependencies = [ + "agave-precompiles", "ahash 0.8.11", "itertools 0.12.1", "log", @@ -7727,7 +7751,6 @@ dependencies = [ "solana-message", "solana-nonce", "solana-nonce-account", - "solana-precompiles", "solana-program", "solana-program-runtime", "solana-pubkey", diff --git a/svm/src/message_processor.rs b/svm/src/message_processor.rs index ff419c8db0b1fb..12cafc3fac294b 100644 --- a/svm/src/message_processor.rs +++ b/svm/src/message_processor.rs @@ -1,8 +1,8 @@ use { + agave_precompiles::get_precompile, solana_account::WritableAccount, solana_instructions_sysvar as instructions, solana_measure::measure_us, - solana_precompiles::get_precompile, solana_program_runtime::invoke_context::InvokeContext, solana_svm_transaction::svm_message::SVMMessage, solana_timings::{ExecuteDetailsTimings, ExecuteTimings},