diff --git a/Cargo.lock b/Cargo.lock index 520adc0f05..286f735c72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4689,6 +4689,17 @@ dependencies = [ "tar", ] +[[package]] +name = "solana-ed25519-program" +version = "1.8.1" +dependencies = [ + "bytemuck", + "ed25519-dalek", + "rand 0.7.3", + "solana-logger 1.8.1", + "solana-sdk", +] + [[package]] name = "solana-exchange-program" version = "1.8.1" @@ -5424,6 +5435,7 @@ dependencies = [ "serde_derive", "solana-compute-budget-program", "solana-config-program", + "solana-ed25519-program", "solana-frozen-abi 1.8.1", "solana-frozen-abi-macro 1.8.1", "solana-logger 1.8.1", @@ -5460,6 +5472,7 @@ dependencies = [ "borsh-derive", "bs58 0.4.0", "bv", + "bytemuck", "byteorder", "chrono", "curve25519-dalek 2.1.0", diff --git a/Cargo.toml b/Cargo.toml index f727522054..6e01beaeea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ "programs/compute-budget", "programs/config", "programs/exchange", + "programs/ed25519", "programs/secp256k1", "programs/stake", "programs/vote", diff --git a/docs/src/developing/runtime-facilities/programs.md b/docs/src/developing/runtime-facilities/programs.md index c66afe9841..5dd1722001 100644 --- a/docs/src/developing/runtime-facilities/programs.md +++ b/docs/src/developing/runtime-facilities/programs.md @@ -65,6 +65,48 @@ to the BPF Upgradeable Loader to process the instruction. [More information about deployment](cli/deploy-a-program.md) +## Ed25519 Program + +Verify ed25519 signature program. This program takes an ed25519 signature, public key, and message. +Multiple signatures can be verified. If any of the signatures fail to verify, an error is returned. + +- Program id: `Ed25519SigVerify111111111111111111111111111` +- Instructions: [new_ed25519_instruction](https://github.com/solana-labs/solana/blob/master/sdk/src/ed25519_instruction.rs#L31) + +The ed25519 program processes an instruction. The first `u8` is a count of the number of +signatures to check, which is followed by a single byte padding. After that, the +following struct is serialized, one for each signature to check. + +``` +struct Ed25519SignatureOffsets { + signature_offset: u16, // offset to ed25519 signature of 64 bytes + signature_instruction_index: u16, // instruction index to find signature + public_key_offset: u16, // offset to public key of 32 bytes + public_key_instruction_index: u16, // instruction index to find public key + message_data_offset: u16, // offset to start of message data + message_data_size: u16, // size of message data + message_instruction_index: u16, // index of instruction data to get message data +} +``` + +Pseudo code of the operation: + +``` +process_instruction() { + for i in 0..count { + // i'th index values referenced: + instructions = &transaction.message().instructions + signature = instructions[ed25519_signature_instruction_index].data[ed25519_signature_offset..ed25519_signature_offset + 64] + pubkey = instructions[ed25519_pubkey_instruction_index].data[ed25519_pubkey_offset..ed25519_pubkey_offset + 32] + message = instructions[ed25519_message_instruction_index].data[ed25519_message_data_offset..ed25519_message_data_offset + ed25519_message_data_size] + if pubkey.verify(signature, message) != Success { + return Error + } + } + return Success +} +``` + ## Secp256k1 Program Verify secp256k1 public key recovery operations (ecrecover). diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index ab07fb6099..3fc3309267 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -3142,6 +3142,13 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "solana-ed25519-program" +version = "1.8.1" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-faucet" version = "1.8.1" @@ -3439,6 +3446,7 @@ dependencies = [ "serde_derive", "solana-compute-budget-program", "solana-config-program", + "solana-ed25519-program", "solana-frozen-abi 1.8.1", "solana-frozen-abi-macro 1.8.1", "solana-logger 1.8.1", @@ -3467,6 +3475,7 @@ dependencies = [ "borsh-derive", "bs58 0.4.0", "bv", + "bytemuck", "byteorder 1.3.4", "chrono", "derivation-path", diff --git a/programs/ed25519/Cargo.toml b/programs/ed25519/Cargo.toml new file mode 100644 index 0000000000..a6e605bb2d --- /dev/null +++ b/programs/ed25519/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "solana-ed25519-program" +description = "Solana Ed25519 program" +version = "1.8.1" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-ed25519-program" +repository = "https://github.com/solana-labs/solana" +authors = ["Solana Maintainers "] +license = "Apache-2.0" +edition = "2018" + +[dependencies] +solana-sdk = { path = "../../sdk", version = "=1.8.1" } + +[dev-dependencies] +bytemuck = { version = "1.7.2", features = ["derive"] } +ed25519-dalek = "=1.0.1" +rand = "0.7.0" +solana-logger = { path = "../../logger", version = "=1.8.1" } + +[lib] +crate-type = ["lib"] +name = "solana_ed25519_program" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/ed25519/src/lib.rs b/programs/ed25519/src/lib.rs new file mode 100644 index 0000000000..da579fb1d1 --- /dev/null +++ b/programs/ed25519/src/lib.rs @@ -0,0 +1,55 @@ +use solana_sdk::{ + instruction::InstructionError, process_instruction::InvokeContext, pubkey::Pubkey, +}; + +pub fn process_instruction( + _program_id: &Pubkey, + _data: &[u8], + _invoke_context: &mut dyn InvokeContext, +) -> Result<(), InstructionError> { + // Should be already checked by now. + Ok(()) +} + +#[cfg(test)] +pub mod test { + use rand::{thread_rng, Rng}; + use solana_sdk::{ + ed25519_instruction::new_ed25519_instruction, + feature_set::FeatureSet, + hash::Hash, + signature::{Keypair, Signer}, + transaction::Transaction, + }; + use std::sync::Arc; + + #[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 mint_keypair = Keypair::new(); + let feature_set = Arc::new(FeatureSet::all_enabled()); + + let tx = Transaction::new_signed_with_payer( + &[instruction.clone()], + Some(&mint_keypair.pubkey()), + &[&mint_keypair], + Hash::default(), + ); + + assert!(tx.verify_precompiles(&feature_set).is_ok()); + + let index = thread_rng().gen_range(0, instruction.data.len()); + instruction.data[index] = instruction.data[index].wrapping_add(12); + let tx = Transaction::new_signed_with_payer( + &[instruction], + Some(&mint_keypair.pubkey()), + &[&mint_keypair], + Hash::default(), + ); + assert!(tx.verify_precompiles(&feature_set).is_err()); + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index f5db094a35..ec75f7cb8b 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -39,6 +39,7 @@ serde = { version = "1.0.122", features = ["rc"] } serde_derive = "1.0.103" solana-config-program = { path = "../programs/config", version = "=1.8.1" } solana-compute-budget-program = { path = "../programs/compute-budget", version = "=1.8.1" } +solana-ed25519-program = { path = "../programs/ed25519", version = "=1.8.1" } solana-frozen-abi = { path = "../frozen-abi", version = "=1.8.1" } solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.8.1" } solana-logger = { path = "../logger", version = "=1.8.1" } diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index f71a2eb0d6..fd00ba8cc7 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -87,15 +87,26 @@ pub enum ActivationType { /// normal child Bank creation. /// https://github.com/solana-labs/solana/blob/84b139cc94b5be7c9e0c18c2ad91743231b85a0d/runtime/src/bank.rs#L1723 fn feature_builtins() -> Vec<(Builtin, Pubkey, ActivationType)> { - vec![( - Builtin::new( - "compute_budget_program", - solana_sdk::compute_budget::id(), - solana_compute_budget_program::process_instruction, + vec![ + ( + Builtin::new( + "compute_budget_program", + solana_sdk::compute_budget::id(), + solana_compute_budget_program::process_instruction, + ), + feature_set::tx_wide_compute_cap::id(), + ActivationType::NewProgram, ), - feature_set::tx_wide_compute_cap::id(), - ActivationType::NewProgram, - )] + ( + Builtin::new( + "ed25519_program", + solana_sdk::ed25519_program::id(), + solana_ed25519_program::process_instruction, + ), + feature_set::ed25519_program_enabled::id(), + ActivationType::NewProgram, + ), + ] } pub(crate) fn get() -> Builtins { diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 3896514709..2db20bc2db 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -40,6 +40,7 @@ full = [ [dependencies] assert_matches = { version = "1.5.0", optional = true } bincode = "1.3.3" +bytemuck = { version = "1.7.2", features = ["derive"] } borsh = "0.9.0" base64 = "0.13" borsh-derive = "0.9.0" diff --git a/sdk/program/src/ed25519_program.rs b/sdk/program/src/ed25519_program.rs new file mode 100644 index 0000000000..6301e3ab6c --- /dev/null +++ b/sdk/program/src/ed25519_program.rs @@ -0,0 +1 @@ +crate::declare_id!("Ed25519SigVerify111111111111111111111111111"); diff --git a/sdk/program/src/fee_calculator.rs b/sdk/program/src/fee_calculator.rs index f9a9f48bb1..4bb8cb62e5 100644 --- a/sdk/program/src/fee_calculator.rs +++ b/sdk/program/src/fee_calculator.rs @@ -1,5 +1,6 @@ #![allow(clippy::integer_arithmetic)] use crate::clock::DEFAULT_MS_PER_SLOT; +use crate::ed25519_program; use crate::message::Message; use crate::secp256k1_program; use log::*; @@ -28,20 +29,22 @@ impl FeeCalculator { } pub fn calculate_fee(&self, message: &Message) -> u64 { - let mut num_secp256k1_signatures: u64 = 0; + let mut num_signatures: u64 = 0; 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_secp256k1_signatures += instruction.data[0] as u64; + if (secp256k1_program::check_id(&id) || ed25519_program::check_id(&id)) + && !instruction.data.is_empty() + { + num_signatures += instruction.data[0] as u64; } } } self.lamports_per_signature - * (u64::from(message.header.num_required_signatures) + num_secp256k1_signatures) + * (u64::from(message.header.num_required_signatures) + num_signatures) } } diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index c71d19cbf0..2cc1ee53d9 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -12,6 +12,7 @@ pub mod bpf_loader_deprecated; pub mod bpf_loader_upgradeable; pub mod clock; pub mod decode_error; +pub mod ed25519_program; pub mod entrypoint; pub mod entrypoint_deprecated; pub mod epoch_schedule; diff --git a/sdk/src/ed25519_instruction.rs b/sdk/src/ed25519_instruction.rs new file mode 100644 index 0000000000..b8e814e3e4 --- /dev/null +++ b/sdk/src/ed25519_instruction.rs @@ -0,0 +1,328 @@ +#![cfg(feature = "full")] + +use crate::{decode_error::DecodeError, instruction::Instruction}; +use bytemuck::{bytes_of, Pod, Zeroable}; +use ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier}; +use thiserror::Error; + +#[derive(Error, Debug, Clone, PartialEq)] +pub enum Ed25519Error { + #[error("ed25519 public key is not valid")] + InvalidPublicKey, + #[error("ed25519 signature is not valid")] + InvalidSignature, + #[error("offset not valid")] + InvalidDataOffsets, + #[error("instruction is incorrect size")] + InvalidInstructionDataSize, +} + +impl DecodeError for Ed25519Error { + fn type_of() -> &'static str { + "Ed25519Error" + } +} + +pub const PUBKEY_SERIALIZED_SIZE: usize = 32; +pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; +pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14; +// bytemuck requires structures to be aligned +pub const SIGNATURE_OFFSETS_START: usize = 2; +pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START; + +#[derive(Default, Debug, Copy, Clone, Zeroable, Pod)] +#[repr(C)] +pub struct Ed25519SignatureOffsets { + signature_offset: u16, // offset to ed25519 signature of 64 bytes + signature_instruction_index: u16, // instruction index to find signature + public_key_offset: u16, // offset to public key of 32 bytes + public_key_instruction_index: u16, // instruction index to find public key + message_data_offset: u16, // offset to start of message data + message_data_size: u16, // size of message data + message_instruction_index: u16, // index of instruction data to get message data +} + +pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8]) -> Instruction { + let signature = keypair.sign(message).to_bytes(); + let pubkey = keypair.public.to_bytes(); + + 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: 0, + public_key_offset: public_key_offset as u16, + public_key_instruction_index: 0, + message_data_offset: message_data_offset as u16, + message_data_size: message.len() as u16, + message_instruction_index: 0, + }; + + 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::ed25519_program::id(), + accounts: vec![], + data: instruction_data, + } +} + +pub fn verify_signatures(data: &[u8], instruction_datas: &[&[u8]]) -> Result<(), Ed25519Error> { + if data.len() < SIGNATURE_OFFSETS_START { + return Err(Ed25519Error::InvalidInstructionDataSize); + } + let num_signatures = data[0] as usize; + if num_signatures == 0 && data.len() > SIGNATURE_OFFSETS_START { + return Err(Ed25519Error::InvalidInstructionDataSize); + } + let expected_data_size = num_signatures + .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE) + .saturating_add(SIGNATURE_OFFSETS_START); + if data.len() < expected_data_size { + return Err(Ed25519Error::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(|_| Ed25519Error::InvalidDataOffsets)?; + + // Parse out signature + let signature_index = offsets.signature_instruction_index as usize; + if signature_index >= instruction_datas.len() { + return Err(Ed25519Error::InvalidDataOffsets); + } + 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(Ed25519Error::InvalidDataOffsets); + } + + let signature = + ed25519_dalek::Signature::from_bytes(&signature_instruction[sig_start..sig_end]) + .map_err(|_| Ed25519Error::InvalidSignature)?; + + // Parse out pubkey + let pubkey = get_data_slice( + instruction_datas, + offsets.public_key_instruction_index, + offsets.public_key_offset, + PUBKEY_SERIALIZED_SIZE, + )?; + + let publickey = ed25519_dalek::PublicKey::from_bytes(pubkey) + .map_err(|_| Ed25519Error::InvalidPublicKey)?; + + // Parse out message + let message = get_data_slice( + instruction_datas, + offsets.message_instruction_index, + offsets.message_data_offset, + offsets.message_data_size as usize, + )?; + + publickey + .verify(message, &signature) + .map_err(|_| Ed25519Error::InvalidSignature)?; + } + Ok(()) +} + +fn get_data_slice<'a>( + instruction_datas: &'a [&[u8]], + instruction_index: u16, + offset_start: u16, + size: usize, +) -> Result<&'a [u8], Ed25519Error> { + let signature_index = instruction_index as usize; + if signature_index >= instruction_datas.len() { + return Err(Ed25519Error::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(Ed25519Error::InvalidDataOffsets); + } + + Ok(&instruction_datas[signature_index][start..end]) +} + +#[cfg(test)] +pub mod test { + use super::*; + + fn test_case( + num_signatures: u16, + offsets: &Ed25519SignatureOffsets, + ) -> Result<(), Ed25519Error> { + 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_signatures(&instruction_data, &[&[0u8; 100]]) + } + + #[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_signatures(&instruction_data, &[&[0u8; 100]]), + Err(Ed25519Error::InvalidInstructionDataSize) + ); + + let offsets = Ed25519SignatureOffsets { + signature_instruction_index: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + message_instruction_index: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + public_key_instruction_index: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::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(Ed25519Error::InvalidSignature)); + + let offsets = Ed25519SignatureOffsets { + message_data_offset: 100, + message_data_size: 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + message_data_offset: 100, + message_data_size: 1000, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + message_data_offset: std::u16::MAX, + message_data_size: std::u16::MAX, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + } + + #[test] + fn test_pubkey_offset() { + let offsets = Ed25519SignatureOffsets { + public_key_offset: std::u16::MAX, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + public_key_offset: 100 - PUBKEY_SERIALIZED_SIZE as u16 + 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + } + + #[test] + fn test_signature_offset() { + let offsets = Ed25519SignatureOffsets { + signature_offset: std::u16::MAX, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + + let offsets = Ed25519SignatureOffsets { + signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1, + ..Ed25519SignatureOffsets::default() + }; + assert_eq!( + test_case(1, &offsets), + Err(Ed25519Error::InvalidDataOffsets) + ); + } +} diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 3700a34694..8379331492 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -239,6 +239,10 @@ pub mod sol_log_data_syscall_enabled { solana_sdk::declare_id!("HYPs7jyJ3KwQFdDpuSzMtVKf1MLJDaZRv3CSWvfUqdFo"); } +pub mod ed25519_program_enabled { + solana_sdk::declare_id!("E1TvTNipX8TKNHrhRC8SMuAwQmGY58TZ4drdztP3Gxwc"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -299,6 +303,7 @@ lazy_static! { (remove_native_loader::id(), "Remove support for the native loader"), (return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall"), (sol_log_data_syscall_enabled::id(), "enable sol_log_data syscall"), + (ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index ddee313fc8..11e3189815 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -18,6 +18,7 @@ pub mod commitment_config; pub mod compute_budget; pub mod derivation_path; pub mod deserialize_utils; +pub mod ed25519_instruction; pub mod entrypoint; pub mod entrypoint_deprecated; pub mod entrypoint_native; diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index f70f388236..c6bc85a5a9 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -5,6 +5,7 @@ use crate::sanitize::{Sanitize, SanitizeError}; use crate::secp256k1_instruction::verify_eth_addresses; use crate::{ + ed25519_instruction::verify_signatures, feature_set, hash::Hash, instruction::{CompiledInstruction, Instruction, InstructionError}, @@ -432,6 +433,18 @@ impl Transaction { feature_set.is_active(&feature_set::libsecp256k1_0_5_upgrade_enabled::id()), ); e.map_err(|_| TransactionError::InvalidAccountIndex)?; + } else if crate::ed25519_program::check_id(program_id) + && feature_set.is_active(&feature_set::ed25519_program_enabled::id()) + { + let instruction_datas: Vec<_> = self + .message() + .instructions + .iter() + .map(|instruction| instruction.data.as_ref()) + .collect(); + let data = &instruction.data; + let e = verify_signatures(data, &instruction_datas); + e.map_err(|_| TransactionError::InvalidAccountIndex)?; } } Ok(())