diff --git a/Cargo.lock b/Cargo.lock index 6ab0dd84df..0fabb1dfe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4087,6 +4087,7 @@ dependencies = [ "rand 0.7.3", "rand_core 0.6.2", "rustversion", + "sha3", "solana-measure", "solana-runtime", "solana-sdk", @@ -4969,6 +4970,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.9.2", + "sha3", "solana-frozen-abi 1.7.0", "solana-frozen-abi-macro 1.7.0", "solana-logger 1.7.0", diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index fb37c11edd..75d96031fd 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2761,6 +2761,7 @@ dependencies = [ "num-derive 0.3.0", "num-traits", "rand_core 0.6.2", + "sha3", "solana-measure", "solana-runtime", "solana-sdk", @@ -3004,7 +3005,7 @@ dependencies = [ ] [[package]] -name = "solana-bpf-rust-sha256" +name = "solana-bpf-rust-sha" version = "1.7.0" dependencies = [ "solana-program 1.7.0", @@ -3364,6 +3365,7 @@ dependencies = [ "serde_bytes", "serde_derive", "sha2 0.9.2", + "sha3", "solana-frozen-abi 1.7.0", "solana-frozen-abi-macro 1.7.0", "solana-logger 1.7.0", diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 611fa9cc8f..81b0430702 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -70,7 +70,7 @@ members = [ "rust/rand", "rust/ro_modify", "rust/sanity", - "rust/sha256", + "rust/sha", "rust/spoof1", "rust/spoof1_system", "rust/sysvar", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 31dd4c58ef..a044671a04 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -84,7 +84,7 @@ fn main() { "rand", "ro_modify", "sanity", - "sha256", + "sha", "spoof1", "spoof1_system", "upgradeable", diff --git a/programs/bpf/c/src/sha/sha.c b/programs/bpf/c/src/sha/sha.c new file mode 100644 index 0000000000..b61a14de8f --- /dev/null +++ b/programs/bpf/c/src/sha/sha.c @@ -0,0 +1,47 @@ +/** + * @brief SHA256 Syscall test + */ +#include + +extern uint64_t entrypoint(const uint8_t *input) { + + // SHA256 + { + 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)); + } + + // Keccak + { + uint8_t result[KECCAK_RESULT_LENGTH]; + uint8_t expected[] = {0xd1, 0x9a, 0x9d, 0xe2, 0x89, 0x7f, 0x7c, 0x9e, + 0x5, 0x32, 0x32, 0x22, 0xe8, 0xc6, 0xb4, 0x88, + 0x6b, 0x5b, 0xbb, 0xec, 0xd4, 0x42, 0xfd, 0x10, + 0x7d, 0xd5, 0x9a, 0x6f, 0x21, 0xd3, 0xb8, 0xa7}; + + 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_keccak256(bytes, SOL_ARRAY_SIZE(bytes), result); + + sol_assert(0 == sol_memcmp(result, expected, KECCAK_RESULT_LENGTH)); + } + + return SUCCESS; +} diff --git a/programs/bpf/c/src/sha256/sha256.c b/programs/bpf/c/src/sha256/sha256.c deleted file mode 100644 index 55decd0bd0..0000000000 --- a/programs/bpf/c/src/sha256/sha256.c +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @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/rust/sha256/Cargo.toml b/programs/bpf/rust/sha/Cargo.toml similarity index 83% rename from programs/bpf/rust/sha256/Cargo.toml rename to programs/bpf/rust/sha/Cargo.toml index 656fc45436..06e8eddfb3 100644 --- a/programs/bpf/rust/sha256/Cargo.toml +++ b/programs/bpf/rust/sha/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "solana-bpf-rust-sha256" +name = "solana-bpf-rust-sha" version = "1.7.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/" -documentation = "https://docs.rs/solana-bpf-rust-sha256" +documentation = "https://docs.rs/solana-bpf-rust-sha" edition = "2018" [dependencies] diff --git a/programs/bpf/rust/sha/src/lib.rs b/programs/bpf/rust/sha/src/lib.rs new file mode 100644 index 0000000000..6ee55cc9ab --- /dev/null +++ b/programs/bpf/rust/sha/src/lib.rs @@ -0,0 +1,43 @@ +//! @brief SHA Syscall test + +extern crate solana_program; +use solana_program::{custom_panic_default, msg}; + +fn test_sha256_hasher() { + use solana_program::hash::{hashv, Hasher}; + let vals = &["Gaggablaghblagh!".as_ref(), "flurbos".as_ref()]; + let mut hasher = Hasher::default(); + hasher.hashv(vals); + assert_eq!(hashv(vals), hasher.result()); +} + +fn test_keccak256_hasher() { + use solana_program::keccak::{hashv, Hasher}; + let vals = &["Gaggablaghblagh!".as_ref(), "flurbos".as_ref()]; + let mut hasher = Hasher::default(); + hasher.hashv(vals); + assert_eq!(hashv(vals), hasher.result()); +} + +#[no_mangle] +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { + msg!("sha"); + + test_sha256_hasher(); + test_keccak256_hasher(); + + 0 +} + +custom_panic_default!(); + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_sha() { + test_sha256_hasher(); + test_keccak256_hasher(); + } +} diff --git a/programs/bpf/rust/sha256/src/lib.rs b/programs/bpf/rust/sha256/src/lib.rs deleted file mode 100644 index fc5c55b3e6..0000000000 --- a/programs/bpf/rust/sha256/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! @brief SHA256 Syscall test - -extern crate solana_program; -use solana_program::{ - custom_panic_default, - hash::{hashv, Hasher}, - msg, -}; - -fn test_hasher() { - let vals = &["Gaggablaghblagh!".as_ref(), "flurbos".as_ref()]; - let mut hasher = Hasher::default(); - hasher.hashv(vals); - assert_eq!(hashv(vals), hasher.result()); -} - -#[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { - msg!("sha256"); - - test_hasher(); - - 0 -} - -custom_panic_default!(); - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_sha256() { - test_hasher(); - } -} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index d5d39e2a6f..65cae0ec91 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -422,7 +422,7 @@ fn test_program_bpf_sanity() { ("relative_call", true), ("sanity", true), ("sanity++", true), - ("sha256", true), + ("sha", true), ("struct_pass", true), ("struct_ret", true), ]); @@ -443,7 +443,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_sha", true), ]); } @@ -1239,7 +1239,7 @@ fn assert_instruction_count() { ("relative_call", 10), ("sanity", 175), ("sanity++", 177), - ("sha256", 348), + ("sha", 694), ("struct_pass", 8), ("struct_ret", 22), ]); @@ -1258,8 +1258,8 @@ fn assert_instruction_count() { ("solana_bpf_rust_noop", 472), ("solana_bpf_rust_param_passing", 46), ("solana_bpf_rust_rand", 475), - ("solana_bpf_rust_sanity", 869), - ("solana_bpf_rust_sha256", 10830), + ("solana_bpf_rust_sanity", 894), + ("solana_bpf_rust_sha", 29099), ]); } diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index cd1e8ec382..a2c0515f12 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4.11" num-derive = "0.3" num-traits = "0.2" rand_core = "0.6.2" +sha3 = "0.9.1" solana-measure = { path = "../../measure", version = "=1.7.0" } solana-runtime = { path = "../../runtime", version = "=1.7.0" } solana-sdk = { path = "../../sdk", version = "=1.7.0" } diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index cbfafbc9e1..039f89a1fd 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -20,12 +20,13 @@ use solana_sdk::{ epoch_schedule::EpochSchedule, feature_set::{ cpi_data_cost, cpi_share_ro_and_exec_accounts, demote_sysvar_write_locks, - enforce_aligned_host_addrs, set_upgrade_authority_via_cpi_enabled, sysvar_via_syscall, - update_data_on_realloc, + enforce_aligned_host_addrs, keccak256_syscall_enabled, + set_upgrade_authority_via_cpi_enabled, sysvar_via_syscall, update_data_on_realloc, }, hash::{Hasher, HASH_BYTES}, ic_msg, instruction::{AccountMeta, Instruction, InstructionError}, + keccak, keyed_account::KeyedAccount, native_loader, process_instruction::{stable_log, ComputeMeter, InvokeContext, Logger}, @@ -127,6 +128,10 @@ pub fn register_syscalls( syscall_registry.register_syscall_by_name(b"sol_sha256", SyscallSha256::call)?; + if invoke_context.is_feature_active(&keccak256_syscall_enabled::id()) { + syscall_registry.register_syscall_by_name(b"sol_keccak256", SyscallKeccak256::call)?; + } + if invoke_context.is_feature_active(&sysvar_via_syscall::id()) { syscall_registry .register_syscall_by_name(b"sol_get_clock_sysvar", SyscallGetClockSysvar::call)?; @@ -252,6 +257,17 @@ pub fn bind_syscall_context_objects<'a>( None, )?; + bind_feature_gated_syscall_context_object!( + vm, + invoke_context.is_feature_active(&keccak256_syscall_enabled::id()), + Box::new(SyscallKeccak256 { + base_cost: bpf_compute_budget.sha256_base_cost, + byte_cost: bpf_compute_budget.sha256_byte_cost, + compute_meter: invoke_context.get_compute_meter(), + loader_id, + }), + ); + let is_sysvar_via_syscall_active = invoke_context.is_feature_active(&sysvar_via_syscall::id()); let invoke_context = Rc::new(RefCell::new(invoke_context)); @@ -1069,6 +1085,65 @@ impl<'a> SyscallObject for SyscallGetRentSysvar<'a> { } } +// Keccak256 +pub struct SyscallKeccak256<'a> { + base_cost: u64, + byte_cost: u64, + compute_meter: Rc>, + loader_id: &'a Pubkey, +} +impl<'a> SyscallObject for SyscallKeccak256<'a> { + fn call( + &mut self, + vals_addr: u64, + vals_len: u64, + result_addr: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + question_mark!(self.compute_meter.consume(self.base_cost), result); + let hash_result = question_mark!( + translate_slice_mut::( + memory_mapping, + result_addr, + keccak::HASH_BYTES as u64, + self.loader_id, + true, + ), + result + ); + let mut hasher = keccak::Hasher::default(); + if vals_len > 0 { + let vals = question_mark!( + translate_slice::<&[u8]>(memory_mapping, vals_addr, vals_len, self.loader_id, true), + result + ); + for val in vals.iter() { + let bytes = question_mark!( + translate_slice::( + memory_mapping, + val.as_ptr() as u64, + val.len() as u64, + self.loader_id, + true, + ), + result + ); + question_mark!( + self.compute_meter + .consume(self.byte_cost * (val.len() as u64 / 2)), + result + ); + hasher.hash(bytes); + } + } + hash_result.copy_from_slice(&hasher.result().to_bytes()); + *result = Ok(0); + } +} + // Cross-program invocation syscalls struct AccountReferences<'a> { diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index 644b864e7b..99a671c13a 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -437,6 +437,24 @@ uint64_t sol_sha256( const uint8_t *result ); +/** + * Length of a Keccak hash result + */ +#define KECCAK_RESULT_LENGTH 32 + +/** + * Keccak + * + * @param bytes Array of byte arrays + * @param bytes_len Number of byte arrays + * @param result 32 byte array to hold the result + */ +uint64_t sol_keccak256( + const SolBytes *bytes, + int bytes_len, + const uint8_t *result +); + /** * Account Meta */ diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index e95fa5f109..3b42e9d14a 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -26,6 +26,7 @@ serde = "1.0.112" serde_bytes = "0.11" serde_derive = "1.0.103" sha2 = "0.9.2" +sha3 = "0.9.1" solana-frozen-abi = { path = "../../frozen-abi", version = "=1.7.0" } solana-frozen-abi-macro = { path = "../../frozen-abi/macro", version = "=1.7.0" } solana-sdk-macro = { path = "../macro", version = "=1.7.0" } diff --git a/sdk/program/src/keccak.rs b/sdk/program/src/keccak.rs new file mode 100644 index 0000000000..ab0572b2fa --- /dev/null +++ b/sdk/program/src/keccak.rs @@ -0,0 +1,159 @@ +use crate::sanitize::Sanitize; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use sha3::{Digest, Keccak256}; +use std::{convert::TryFrom, fmt, mem, str::FromStr}; +use thiserror::Error; + +pub const HASH_BYTES: usize = 32; +/// Maximum string length of a base58 encoded hash +const MAX_BASE58_LEN: usize = 44; +#[derive( + Serialize, + Deserialize, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Clone, + Copy, + Default, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + AbiExample, +)] +#[repr(transparent)] +pub struct Hash(pub [u8; HASH_BYTES]); + +#[derive(Clone, Default)] +pub struct Hasher { + hasher: Keccak256, +} + +impl Hasher { + pub fn hash(&mut self, val: &[u8]) { + self.hasher.update(val); + } + pub fn hashv(&mut self, vals: &[&[u8]]) { + for val in vals { + self.hash(val); + } + } + pub fn result(self) -> Hash { + // At the time of this writing, the sha3 library is stuck on an old version + // of generic_array (0.9.0). Decouple ourselves with a clone to our version. + Hash(<[u8; HASH_BYTES]>::try_from(self.hasher.finalize().as_slice()).unwrap()) + } +} + +impl Sanitize for Hash {} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl fmt::Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", bs58::encode(self.0).into_string()) + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", bs58::encode(self.0).into_string()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum ParseHashError { + #[error("string decoded to wrong size for hash")] + WrongSize, + #[error("failed to decoded string to hash")] + Invalid, +} + +impl FromStr for Hash { + type Err = ParseHashError; + + fn from_str(s: &str) -> Result { + if s.len() > MAX_BASE58_LEN { + return Err(ParseHashError::WrongSize); + } + let bytes = bs58::decode(s) + .into_vec() + .map_err(|_| ParseHashError::Invalid)?; + if bytes.len() != mem::size_of::() { + Err(ParseHashError::WrongSize) + } else { + Ok(Hash::new(&bytes)) + } + } +} + +impl Hash { + pub fn new(hash_slice: &[u8]) -> Self { + Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap()) + } + + pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self { + Self(hash_array) + } + + /// unique Hash for tests and benchmarks. + pub fn new_unique() -> Self { + use std::sync::atomic::{AtomicU64, Ordering}; + static I: AtomicU64 = AtomicU64::new(1); + + let mut b = [0u8; HASH_BYTES]; + let i = I.fetch_add(1, Ordering::Relaxed); + b[0..8].copy_from_slice(&i.to_le_bytes()); + Self::new(&b) + } + + pub fn to_bytes(self) -> [u8; HASH_BYTES] { + self.0 + } +} + +/// Return a Keccak256 hash for the given data. +pub fn hashv(vals: &[&[u8]]) -> Hash { + // Perform the calculation inline, calling this from within a program is + // not supported + #[cfg(not(target_arch = "bpf"))] + { + let mut hasher = Hasher::default(); + hasher.hashv(vals); + hasher.result() + } + // Call via a system call to perform the calculation + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_keccak256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64; + }; + let mut hash_result = [0; HASH_BYTES]; + unsafe { + sol_keccak256( + 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 Keccak256 hash for the given data. +pub fn hash(val: &[u8]) -> Hash { + hashv(&[val]) +} + +/// Return the hash of the given hash extended with the given value. +pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash { + let mut hash_data = id.as_ref().to_vec(); + hash_data.extend_from_slice(val); + hash(&hash_data) +} diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index fcac9c4faa..7b550008e1 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -20,6 +20,7 @@ pub mod fee_calculator; pub mod hash; pub mod incinerator; pub mod instruction; +pub mod keccak; pub mod lamports; pub mod loader_instruction; pub mod loader_upgradeable_instruction; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index f886834cc4..7302e5f147 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -135,6 +135,10 @@ pub mod update_data_on_realloc { solana_sdk::declare_id!("BkPcYCrwHXBoTsv9vMhiRF9gteZmDj3Uwisz9CDjoMKp"); } +pub mod keccak256_syscall_enabled { + solana_sdk::declare_id!("7Ua8mFtahVfA3WCY9LoXDAJJdvJRJHckvSSr1dD8FTWc"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -169,6 +173,7 @@ lazy_static! { (enforce_aligned_host_addrs::id(), "enforce aligned host addresses"), (set_upgrade_authority_via_cpi_enabled::id(), "set upgrade authority instruction via cpi calls for upgradable programs"), (update_data_on_realloc::id(), "Retain updated data values modified after realloc via CPI"), + (keccak256_syscall_enabled::id(), "keccak256 syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()