diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 90ad25af34..1340c93841 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1912,6 +1912,13 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-bpf-rust-sha256" +version = "1.4.0" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-bpf-rust-sysval" version = "1.4.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 4887866e6d..be436ebacf 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -54,6 +54,7 @@ members = [ "rust/param_passing_dep", "rust/rand", "rust/sanity", + "rust/sha256", "rust/sysval", ] diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 85c018837f..50b4dcbd7a 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -82,6 +82,7 @@ fn main() { "param_passing", "rand", "sanity", + "sha256", "sysval", ]; for program in rust_programs.iter() { diff --git a/programs/bpf/c/src/sha256/sha256.c b/programs/bpf/c/src/sha256/sha256.c new file mode 100644 index 0000000000..55decd0bd0 --- /dev/null +++ b/programs/bpf/c/src/sha256/sha256.c @@ -0,0 +1,25 @@ +/** + * @brief SHA256 Syscall test + */ +#include + +extern uint64_t entrypoint(const uint8_t *input) { + + uint8_t result[SHA256_RESULT_LENGTH]; + uint8_t expected[] = {0x9f, 0xa2, 0x7e, 0x8f, 0x7b, 0xc1, 0xec, 0xe8, + 0xae, 0x7b, 0x9a, 0x91, 0x46, 0x53, 0x20, 0xf, + 0x1c, 0x22, 0x8e, 0x56, 0x10, 0x30, 0x59, 0xfd, + 0x35, 0x8d, 0x57, 0x54, 0x96, 0x47, 0x2c, 0xc9}; + + uint8_t bytes1[] = {'G', 'a', 'g', 'g', 'a', 'b', 'l', 'a', + 'g', 'h', 'b', 'l', 'a', 'g', 'h', '!'}; + uint8_t bytes2[] = {'f', 'l', 'u', 'r', 'b', 'o', 's'}; + const SolBytes bytes[] = {{bytes1, SOL_ARRAY_SIZE(bytes1)}, + {bytes2, SOL_ARRAY_SIZE(bytes2)}}; + + sol_sha256(bytes, SOL_ARRAY_SIZE(bytes), result); + + sol_assert(0 == sol_memcmp(result, expected, SHA256_RESULT_LENGTH)); + + return SUCCESS; +} diff --git a/programs/bpf/c/src/tuner/tuner.c b/programs/bpf/c/src/tuner/tuner.c index db76dd4e31..2a82f30741 100644 --- a/programs/bpf/c/src/tuner/tuner.c +++ b/programs/bpf/c/src/tuner/tuner.c @@ -18,6 +18,16 @@ extern uint64_t entrypoint(const uint8_t *input) { *val = *val + 1; } } + + // // Uncomment for SHA256 syscall + // { + // uint8_t result[SHA256_RESULT_LENGTH]; + // uint8_t bytes1[1024]; + // const SolBytes bytes[] = {{bytes1, SOL_ARRAY_SIZE(bytes1)}}; + + // sol_sha256(bytes, SOL_ARRAY_SIZE(bytes), result); + // *val = result[0]; + // } } return *val; } diff --git a/programs/bpf/rust/sha256/Cargo.toml b/programs/bpf/rust/sha256/Cargo.toml new file mode 100644 index 0000000000..6b2b96d0ff --- /dev/null +++ b/programs/bpf/rust/sha256/Cargo.toml @@ -0,0 +1,26 @@ + +# Note: This crate must be built using do.sh + +[package] +name = "solana-bpf-rust-sha256" +version = "1.4.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-sdk = { path = "../../../../sdk/", version = "1.4.0", default-features = false } + +[features] +program = ["solana-sdk/program"] +default = ["program", "solana-sdk/default"] + +[lib] +name = "solana_bpf_rust_sha256" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/sha256/Xargo.toml b/programs/bpf/rust/sha256/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/sha256/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/sha256/src/lib.rs b/programs/bpf/rust/sha256/src/lib.rs new file mode 100644 index 0000000000..74b5002498 --- /dev/null +++ b/programs/bpf/rust/sha256/src/lib.rs @@ -0,0 +1,19 @@ +//! @brief SHA256 Syscall test + +extern crate solana_sdk; +use solana_sdk::{ + hash::{hashv, Hasher}, + info, +}; + +#[no_mangle] +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { + info!("sha256"); + + let vals = &["Gaggablaghblagh!".as_ref(), "flurbos".as_ref()]; + let mut hasher = Hasher::default(); + hasher.hashv(vals); + assert_eq!(hashv(vals), hasher.result()); + + 0 +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index ba4df7f221..b6338eedd6 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -136,6 +136,7 @@ fn test_program_bpf_sanity() { ("relative_call", true), ("sanity", true), ("sanity++", true), + ("sha256", true), ("struct_pass", true), ("struct_ret", true), ]); @@ -154,6 +155,7 @@ fn test_program_bpf_sanity() { ("solana_bpf_rust_param_passing", true), ("solana_bpf_rust_rand", true), ("solana_bpf_rust_sanity", true), + ("solana_bpf_rust_sha256", true), ("solana_bpf_rust_sysval", true), ]); } diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index afa8b86161..b44fddab08 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -601,6 +601,8 @@ mod tests { create_program_address_units: 1500, invoke_units: 1000, max_invoke_depth: 2, + sha256_base_cost: 85, + sha256_byte_cost: 1, }, Rc::new(RefCell::new(Executors::default())), None, diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 207a31ee56..0b8dfbd410 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -7,6 +7,7 @@ use solana_rbpf::{ vm::{EbpfVm, SyscallObject}, }; use solana_runtime::{ + feature_set::sha256_syscall_enabled, message_processor::MessageProcessor, process_instruction::{ComputeMeter, InvokeContext, Logger}, }; @@ -16,6 +17,7 @@ use solana_sdk::{ account_info::AccountInfo, bpf_loader, bpf_loader_deprecated, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, + hash::{Hasher, HASH_BYTES}, instruction::{AccountMeta, Instruction, InstructionError}, message::Message, program_error::ProgramError, @@ -117,6 +119,18 @@ pub fn register_syscalls<'a>( }), )?; + if invoke_context.is_feature_active(&sha256_syscall_enabled::id()) { + vm.register_syscall_with_context_ex( + "sol_sha256", + Box::new(SyscallSha256 { + sha256_base_cost: compute_budget.sha256_base_cost, + sha256_byte_cost: compute_budget.sha256_byte_cost, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + )?; + } + vm.register_syscall_with_context_ex( "sol_create_program_address", Box::new(SyscallCreateProgramAddress { @@ -460,6 +474,43 @@ impl<'a> SyscallObject for SyscallCreateProgramAddress<'a> { } } +/// SHA256 +pub struct SyscallSha256<'a> { + sha256_base_cost: u64, + sha256_byte_cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallSha256<'a> { + fn call( + &mut self, + vals_addr: u64, + vals_len: u64, + result_addr: u64, + _arg4: u64, + _arg5: u64, + ro_regions: &[MemoryRegion], + rw_regions: &[MemoryRegion], + ) -> Result> { + self.compute_meter.consume(self.sha256_base_cost)?; + let hash_result = + translate_slice_mut!(u8, result_addr, HASH_BYTES, rw_regions, self.loader_id)?; + let mut hasher = Hasher::default(); + if vals_len > 0 { + let vals = translate_slice!(&[u8], vals_addr, vals_len, ro_regions, self.loader_id)?; + for val in vals.iter() { + let bytes = + translate_slice!(u8, val.as_ptr(), val.len(), ro_regions, self.loader_id)?; + self.compute_meter + .consume(self.sha256_byte_cost * (val.len() as u64 / 2))?; + hasher.hash(bytes); + } + } + hash_result.copy_from_slice(&hasher.result().to_bytes()); + Ok(0) + } +} + // Cross-program invocation syscalls struct AccountReferences<'a> { @@ -1095,6 +1146,7 @@ fn call<'a>( mod tests { use super::*; use crate::tests::{MockComputeMeter, MockLogger}; + use solana_sdk::hash::hashv; macro_rules! assert_access_violation { ($result:expr, $va:expr, $len:expr) => { @@ -1449,4 +1501,115 @@ mod tests { check_alignment::(); check_alignment::(); } + + #[test] + fn test_syscall_sha256() { + let bytes1 = "Gaggablaghblagh!"; + let bytes2 = "flurbos"; + + struct MockSlice { + pub addr: u64, + pub len: usize, + } + let mock_slice1 = MockSlice { + addr: 4096, + len: bytes1.len(), + }; + let mock_slice2 = MockSlice { + addr: 8192, + len: bytes2.len(), + }; + let bytes_to_hash = [mock_slice1, mock_slice2]; // TODO + let ro_len = bytes_to_hash.len() as u64; + let ro_va = 96; + let ro_regions = &mut [ + MemoryRegion { + addr_host: bytes1.as_ptr() as *const _ as u64, + addr_vm: 4096, + len: bytes1.len() as u64, + }, + MemoryRegion { + addr_host: bytes2.as_ptr() as *const _ as u64, + addr_vm: 8192, + len: bytes2.len() as u64, + }, + MemoryRegion { + addr_host: bytes_to_hash.as_ptr() as *const _ as u64, + addr_vm: 96, + len: 32, + }, + ]; + ro_regions.sort_by(|a, b| a.addr_vm.cmp(&b.addr_vm)); + let hash_result = [0; HASH_BYTES]; + let rw_va = 192; + let rw_regions = &[MemoryRegion { + addr_host: hash_result.as_ptr() as *const _ as u64, + addr_vm: rw_va, + len: HASH_BYTES as u64, + }]; + let compute_meter: Rc> = + Rc::new(RefCell::new(MockComputeMeter { + remaining: (bytes1.len() + bytes2.len()) as u64, + })); + let mut syscall = SyscallSha256 { + sha256_base_cost: 0, + sha256_byte_cost: 2, + compute_meter, + loader_id: &bpf_loader_deprecated::id(), + }; + + syscall + .call(ro_va, ro_len, rw_va, 0, 0, ro_regions, rw_regions) + .unwrap(); + + let hash_local = hashv(&[bytes1.as_ref(), bytes2.as_ref()]).to_bytes(); + assert_eq!(hash_result, hash_local); + + assert_access_violation!( + syscall.call( + ro_va - 1, // AccessViolation + ro_len, + rw_va, + 0, + 0, + ro_regions, + rw_regions + ), + ro_va - 1, + ro_len + ); + assert_access_violation!( + syscall.call( + ro_va, + ro_len + 1, // AccessViolation + rw_va, + 0, + 0, + ro_regions, + rw_regions + ), + ro_va, + ro_len + 1 + ); + assert_access_violation!( + syscall.call( + ro_va, + ro_len, + rw_va - 1, // AccessViolation + 0, + 0, + ro_regions, + rw_regions + ), + rw_va - 1, + HASH_BYTES as u64 + ); + + assert_eq!( + Err(EbpfError::UserError(BPFError::SyscallError( + SyscallError::InstructionError(InstructionError::ComputationalBudgetExceeded) + ))), + syscall.call(ro_va, ro_len, rw_va, 0, 0, ro_regions, rw_regions) + ); + } } diff --git a/runtime/src/feature_set.rs b/runtime/src/feature_set.rs index dacb26d68e..9cb798f933 100644 --- a/runtime/src/feature_set.rs +++ b/runtime/src/feature_set.rs @@ -33,6 +33,10 @@ pub mod compute_budget_config2 { solana_sdk::declare_id!("HxvjqDSiF5sYdSYuCXsUnS8UeAoWsMT9iGoFP8pgV1mB"); } +pub mod sha256_syscall_enabled { + solana_sdk::declare_id!("D7KfP7bZxpkYtD4Pc38t9htgs1k5k47Yhxe4rp6WDVi8"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -43,6 +47,7 @@ lazy_static! { (spl_token_v2_multisig_fix::id(), "spl-token multisig fix"), (bpf_loader2_program::id(), "bpf_loader2 program"), (compute_budget_config2::id(), "1ms compute budget"), + (sha256_syscall_enabled::id(), "sha256 syscall") /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index f35249c4fe..db8010a718 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -427,6 +427,8 @@ impl MessageProcessor { create_program_address_units: 0, invoke_units: 0, max_invoke_depth: 2, + sha256_base_cost: 0, + sha256_byte_cost: 0, } } } diff --git a/runtime/src/process_instruction.rs b/runtime/src/process_instruction.rs index a15131fd7c..1890852bfc 100644 --- a/runtime/src/process_instruction.rs +++ b/runtime/src/process_instruction.rs @@ -87,6 +87,10 @@ pub struct ComputeBudget { pub invoke_units: u64, /// Maximum cross-program invocation depth allowed including the orignal caller pub max_invoke_depth: usize, + /// Base number of compute units consumed to call sha256 + pub sha256_base_cost: u64, + /// Incremental number of units consumed by sha256 (based on bytes) + pub sha256_byte_cost: u64, } impl Default for ComputeBudget { fn default() -> Self { @@ -98,6 +102,8 @@ impl Default for ComputeBudget { create_program_address_units: 1500, invoke_units: 1000, max_invoke_depth: 2, + sha256_base_cost: 85, + sha256_byte_cost: 1, } } } diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index af9e29c55e..e153a43250 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -391,6 +391,32 @@ static bool sol_deserialize( return true; } +/** + * Byte array pointer and string + */ +typedef struct { + const uint8_t *addr; /** bytes */ + uint64_t len; /** number of bytes*/ +} SolBytes; + +/** + * Length of a sha256 hash result + */ +#define SHA256_RESULT_LENGTH 32 + +/** + * Sha256 + * + * @param bytes Array of byte arrays + * @param bytes_len Number of byte arrays + * @param result 32 byte array to hold the result + */ +static uint64_t sol_sha256( + const SolBytes *bytes, + int bytes_len, + const uint8_t *result +); + /** * Account Meta */ @@ -428,7 +454,7 @@ typedef struct { uint64_t len; /** Number of seeds */ } SolSignerSeeds; -/* +/** * Create a program address * * @param seeds Seed bytes used to sign program accounts @@ -448,7 +474,7 @@ static uint64_t sol_create_program_address( * * @{ */ -/* +/** * Invoke another program and sign for some of the keys * * @param instruction Instruction to process @@ -480,7 +506,7 @@ static uint64_t sol_invoke_signed( signers_seeds_len ); } -/* +/** * Invoke another program * * @param instruction Instruction to process diff --git a/sdk/src/hash.rs b/sdk/src/hash.rs index c9dcbcb4a5..957f5d93db 100644 --- a/sdk/src/hash.rs +++ b/sdk/src/hash.rs @@ -103,9 +103,27 @@ impl Hash { /// Return a Sha256 hash for the given data. pub fn hashv(vals: &[&[u8]]) -> Hash { - let mut hasher = Hasher::default(); - hasher.hashv(vals); - hasher.result() + #[cfg(not(feature = "program"))] + { + let mut hasher = Hasher::default(); + hasher.hashv(vals); + hasher.result() + } + #[cfg(feature = "program")] + { + extern "C" { + fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64; + }; + let mut hash_result = [0; HASH_BYTES]; + unsafe { + sol_sha256( + vals as *const _ as *const u8, + vals.len() as u64, + &mut hash_result as *mut _ as *mut u8, + ); + } + Hash::new_from_array(hash_result) + } } /// Return a Sha256 hash for the given data.