From ab98c1f2d4564e569d5df849cbddc5b46e36526d Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 15 Dec 2020 08:15:01 -0800 Subject: [PATCH] Add try_find_program_address syscall (#14118) --- programs/bpf/c/src/invoke/invoke.c | 14 +++ programs/bpf/rust/invoke/src/lib.rs | 13 +++ programs/bpf_loader/src/syscalls.rs | 139 ++++++++++++++++++++++------ sdk/bpf/c/inc/solana_sdk.h | 23 ++++- sdk/program/src/pubkey.rs | 63 ++++++++++--- sdk/src/feature_set.rs | 5 + 6 files changed, 213 insertions(+), 44 deletions(-) diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index 33e646d4db..5d0188b1df 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -155,6 +155,20 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert(SolPubkey_same(&address, accounts[DERIVED_KEY1_INDEX].key)); } + sol_log("Test try_find_program_address"); + { + uint8_t seed[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's', + ' ', 'b', 'u', 't', 't', 'e', 'r'}; + const SolSignerSeed seeds[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + SolPubkey address; + uint8_t bump_seed; + sol_assert(SUCCESS == sol_try_find_program_address( + seeds, SOL_ARRAY_SIZE(seeds), params.program_id, + &address, &bump_seed)); + sol_assert(SolPubkey_same(&address, accounts[DERIVED_KEY1_INDEX].key)); + sol_assert(bump_seed == bump_seed1); + } + sol_log("Test derived signers"); { sol_assert(!accounts[DERIVED_KEY1_INDEX].is_signer); diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs index eae04bc495..2bb423f185 100644 --- a/programs/bpf/rust/invoke/src/lib.rs +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -259,6 +259,19 @@ fn process_instruction( ); } + msg!("Test try_find_program_address"); + { + let (address, bump_seed) = + Pubkey::try_find_program_address(&[b"You pass butter"], program_id).unwrap(); + assert_eq!(&address, accounts[DERIVED_KEY1_INDEX].key); + assert_eq!(bump_seed, bump_seed1); + assert_eq!( + Pubkey::create_program_address(&[b"You pass butter"], &Pubkey::default()) + .unwrap_err(), + PubkeyError::InvalidSeeds + ); + } + msg!("Test derived signers"); { assert!(!accounts[DERIVED_KEY1_INDEX].is_signer); diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index de04598edb..dc81bc9bd7 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -16,7 +16,7 @@ use solana_sdk::{ entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, feature_set::{ pubkey_log_syscall_enabled, ristretto_mul_syscall_enabled, sha256_syscall_enabled, - sol_log_compute_units_syscall, + sol_log_compute_units_syscall, try_find_program_address_syscall_enabled, }, hash::{Hasher, HASH_BYTES}, instruction::{AccountMeta, Instruction, InstructionError}, @@ -123,6 +123,12 @@ pub fn register_syscalls( b"sol_create_program_address", SyscallCreateProgramAddress::call, )?; + if invoke_context.is_feature_active(&try_find_program_address_syscall_enabled::id()) { + syscall_registry.register_syscall_by_name( + b"sol_try_find_program_address", + SyscallTryFindProgramAddress::call, + )?; + } syscall_registry .register_syscall_by_name(b"sol_invoke_signed_c", SyscallInvokeSignedC::call)?; syscall_registry @@ -217,6 +223,17 @@ pub fn bind_syscall_context_objects<'a>( None, )?; + if invoke_context.is_feature_active(&try_find_program_address_syscall_enabled::id()) { + vm.bind_syscall_context_object( + Box::new(SyscallTryFindProgramAddress { + cost: bpf_compute_budget.create_program_address_units, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + None, + )?; + } + // Cross-program invocation syscalls let invoke_context = Rc::new(RefCell::new(invoke_context)); @@ -580,6 +597,33 @@ impl SyscallObject for SyscallAllocFree { } } +fn translate_program_address_inputs<'a>( + seeds_addr: u64, + seeds_len: u64, + program_id_addr: u64, + memory_mapping: &MemoryMapping, + loader_id: &Pubkey, +) -> Result<(Vec<&'a [u8]>, &'a Pubkey), EbpfError> { + let untranslated_seeds = + translate_slice::<&[&u8]>(memory_mapping, seeds_addr, seeds_len, loader_id)?; + if untranslated_seeds.len() > MAX_SEEDS { + return Err(SyscallError::BadSeeds(PubkeyError::MaxSeedLengthExceeded).into()); + } + let seeds = untranslated_seeds + .iter() + .map(|untranslated_seed| { + translate_slice::( + memory_mapping, + untranslated_seed.as_ptr() as *const _ as u64, + untranslated_seed.len() as u64, + loader_id, + ) + }) + .collect::, EbpfError>>()?; + let program_id = translate_type::(memory_mapping, program_id_addr, loader_id)?; + Ok((seeds, program_id)) +} + /// Create a program address struct SyscallCreateProgramAddress<'a> { cost: u64, @@ -597,35 +641,18 @@ impl<'a> SyscallObject for SyscallCreateProgramAddress<'a> { memory_mapping: &MemoryMapping, result: &mut Result>, ) { - question_mark!(self.compute_meter.consume(self.cost), result); - // TODO need ref? - let untranslated_seeds = question_mark!( - translate_slice::<&[&u8]>(memory_mapping, seeds_addr, seeds_len, self.loader_id), - result - ); - if untranslated_seeds.len() > MAX_SEEDS { - *result = Ok(1); - return; - } - let seeds = question_mark!( - untranslated_seeds - .iter() - .map(|untranslated_seed| { - translate_slice::( - memory_mapping, - untranslated_seed.as_ptr() as *const _ as u64, - untranslated_seed.len() as u64, - self.loader_id, - ) - }) - .collect::, EbpfError>>(), - result - ); - let program_id = question_mark!( - translate_type::(memory_mapping, program_id_addr, self.loader_id), + let (seeds, program_id) = question_mark!( + translate_program_address_inputs( + seeds_addr, + seeds_len, + program_id_addr, + memory_mapping, + self.loader_id, + ), result ); + question_mark!(self.compute_meter.consume(self.cost), result); let new_address = match Pubkey::create_program_address(&seeds, program_id) { Ok(address) => address, Err(_) => { @@ -642,6 +669,64 @@ impl<'a> SyscallObject for SyscallCreateProgramAddress<'a> { } } +/// Create a program address +struct SyscallTryFindProgramAddress<'a> { + cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallTryFindProgramAddress<'a> { + fn call( + &mut self, + seeds_addr: u64, + seeds_len: u64, + program_id_addr: u64, + address_addr: u64, + bump_seed_addr: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + let (seeds, program_id) = question_mark!( + translate_program_address_inputs( + seeds_addr, + seeds_len, + program_id_addr, + memory_mapping, + self.loader_id, + ), + result + ); + + let mut bump_seed = [std::u8::MAX]; + for _ in 0..std::u8::MAX { + { + let mut seeds_with_bump = seeds.to_vec(); + seeds_with_bump.push(&bump_seed); + + question_mark!(self.compute_meter.consume(self.cost), result); + if let Ok(new_address) = + Pubkey::create_program_address(&seeds_with_bump, program_id) + { + let bump_seed_ref = question_mark!( + translate_type_mut::(memory_mapping, bump_seed_addr, self.loader_id), + result + ); + let address = question_mark!( + translate_slice_mut::(memory_mapping, address_addr, 32, self.loader_id), + result + ); + *bump_seed_ref = bump_seed[0]; + address.copy_from_slice(new_address.as_ref()); + *result = Ok(0); + return; + } + } + bump_seed[0] -= 1; + } + *result = Ok(1); + } +} + /// SHA256 pub struct SyscallSha256<'a> { sha256_base_cost: u64, diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index 4543fe9015..2e4712906b 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -479,14 +479,31 @@ typedef struct { * * @param seeds Seed bytes used to sign program accounts * @param seeds_len Length of the seeds array - * @param Progam id of the signer - * @param Program address created, filled on return + * @param program_id Program id of the signer + * @param program_address Program address created, filled on return */ static uint64_t sol_create_program_address( const SolSignerSeed *seeds, int seeds_len, const SolPubkey *program_id, - const SolPubkey *address + const SolPubkey *program_address +); + +/** + * Try to find a program address and return corresponding bump seed + * + * @param seeds Seed bytes used to sign program accounts + * @param seeds_len Length of the seeds array + * @param program_id Program id of the signer + * @param program_address Program address created, filled on return + * @param bump_seed Bump seed required to create a valid program address + */ +static uint64_t sol_try_find_program_address( + const SolSignerSeed *seeds, + int seeds_len, + const SolPubkey *program_id, + const SolPubkey *program_address, + const uint8_t *bump_seed ); /** diff --git a/sdk/program/src/pubkey.rs b/sdk/program/src/pubkey.rs index 5e9eeb1f75..e3e41499ac 100644 --- a/sdk/program/src/pubkey.rs +++ b/sdk/program/src/pubkey.rs @@ -194,31 +194,66 @@ impl Pubkey { } } - /// Find a valid program address and its corresponding bump seed which must be passed - /// as an additional seed when calling `invoke_signed`. + /// Find a valid program address and its corresponding bump seed which must + /// be passed as an additional seed when calling `invoke_signed`. /// - /// Panics in the very unlikely event that the additional seed could not be found. + /// Panics in the very unlikely event that the additional seed could not be + /// found. pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) { Self::try_find_program_address(seeds, program_id) .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed")) } - /// Find a valid program address and its corresponding bump seed which must be passed - /// as an additional seed when calling `invoke_signed` + /// Find a valid program address and its corresponding bump seed which must + /// be passed as an additional seed when calling `invoke_signed` #[allow(clippy::same_item_push)] pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> { - let mut bump_seed = [std::u8::MAX]; - for _ in 0..std::u8::MAX { - { - let mut seeds_with_bump = seeds.to_vec(); - seeds_with_bump.push(&bump_seed); - if let Ok(address) = Self::create_program_address(&seeds_with_bump, program_id) { - return Some((address, bump_seed[0])); + // Perform the calculation inline, calling this from within a program is + // not supported + #[cfg(not(target_arch = "bpf"))] + { + let mut bump_seed = [std::u8::MAX]; + for _ in 0..std::u8::MAX { + { + let mut seeds_with_bump = seeds.to_vec(); + seeds_with_bump.push(&bump_seed); + if let Ok(address) = Self::create_program_address(&seeds_with_bump, program_id) + { + return Some((address, bump_seed[0])); + } } + bump_seed[0] -= 1; + } + None + } + // Call via a system call to perform the calculation + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_try_find_program_address( + seeds_addr: *const u8, + seeds_len: u64, + program_id_addr: *const u8, + address_bytes_addr: *const u8, + bump_seed_addr: *const u8, + ) -> u64; + }; + let mut bytes = [0; 32]; + let mut bump_seed = std::u8::MAX; + let result = unsafe { + sol_try_find_program_address( + seeds as *const _ as *const u8, + seeds.len() as u64, + program_id as *const _ as *const u8, + &mut bytes as *mut _ as *mut u8, + &mut bump_seed as *mut _ as *mut u8, + ) + }; + match result { + crate::entrypoint::SUCCESS => Some((Pubkey::new(&bytes), bump_seed)), + _ => None, } - bump_seed[0] -= 1; } - None } pub fn to_bytes(self) -> [u8; 32] { diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index c0dca054bf..2bd4af1db8 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -106,6 +106,10 @@ pub mod bpf_loader_upgradeable_program { solana_sdk::declare_id!("FbhK8HN9qvNHvJcoFVHAEUCNkagHvu7DTWzdnLuVQ5u4"); } +pub mod try_find_program_address_syscall_enabled { + solana_sdk::declare_id!("EMsMNadQNhCYDyGpYH5Tx6dGHxiUqKHk782PU5XaWfmi"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -134,6 +138,7 @@ lazy_static! { (filter_stake_delegation_accounts::id(), "filter stake_delegation_accounts #14062"), (simple_capitalization::id(), "simple capitalization"), (bpf_loader_upgradeable_program::id(), "upgradeable bpf loader"), + (try_find_program_address_syscall_enabled::id(), "add try_find_program_address syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()