From 0a6bb84aec84b0e62525b3fa7fad079f9d9b170c Mon Sep 17 00:00:00 2001 From: Sean Young Date: Fri, 3 Sep 2021 22:35:38 +0100 Subject: [PATCH] feat: add ed25519 signature verify program Solang requires a method for verify ed25519 signatures. Add a new builtin program at address Ed25519SigVerify111111111111111111111111111 which takes any number of ed25519 signature, public key, and message. If any of the signatures fails to verify, an error is returned. The changes for the web3.js package will go into another commit, since the tests test against a released solana node. Adding web3.js ed25519 testing will break CI. (cherry picked from commit b491354e51b20f961e928f38901b96e253864244) Conflicts: Cargo.lock Cargo.toml programs/bpf/Cargo.lock runtime/Cargo.toml sdk/src/feature_set.rs sdk/src/transaction.rs sdk/src/transaction/sanitized.rs --- Cargo.lock | 13 + Cargo.toml | 1 + .../developing/runtime-facilities/programs.md | 42 +++ programs/bpf/Cargo.lock | 9 + programs/ed25519/Cargo.toml | 26 ++ programs/ed25519/src/lib.rs | 55 +++ runtime/Cargo.toml | 1 + runtime/src/builtins.rs | 27 +- sdk/Cargo.toml | 1 + sdk/program/src/ed25519_program.rs | 1 + sdk/program/src/fee_calculator.rs | 11 +- sdk/program/src/lib.rs | 1 + sdk/src/ed25519_instruction.rs | 328 ++++++++++++++++++ sdk/src/feature_set.rs | 5 + sdk/src/lib.rs | 1 + sdk/src/transaction.rs | 13 + 16 files changed, 523 insertions(+), 12 deletions(-) create mode 100644 programs/ed25519/Cargo.toml create mode 100644 programs/ed25519/src/lib.rs create mode 100644 sdk/program/src/ed25519_program.rs create mode 100644 sdk/src/ed25519_instruction.rs 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(())