diff --git a/Cargo.lock b/Cargo.lock index 33543a024ae40a..8fb169c4940812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7196,6 +7196,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-keccak-hasher" +version = "2.2.0" +dependencies = [ + "borsh 1.5.3", + "serde", + "serde_derive", + "sha3", + "solana-define-syscall", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-hash", + "solana-sanitize", +] + [[package]] name = "solana-keygen" version = "2.2.0" @@ -7742,6 +7757,7 @@ dependencies = [ "solana-frozen-abi-macro", "solana-hash", "solana-instruction", + "solana-keccak-hasher", "solana-last-restart-slot", "solana-logger", "solana-msg", diff --git a/Cargo.toml b/Cargo.toml index 73f1183da72747..1693ffd9c88c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,6 +124,7 @@ members = [ "sdk/hash", "sdk/inflation", "sdk/instruction", + "sdk/keccak-hasher", "sdk/keypair", "sdk/logger", "sdk/macro", @@ -463,6 +464,7 @@ solana-hash = { path = "sdk/hash", version = "=2.2.0", default-features = false solana-inflation = { path = "sdk/inflation", version = "=2.2.0" } solana-inline-spl = { path = "inline-spl", version = "=2.2.0" } solana-instruction = { path = "sdk/instruction", version = "=2.2.0", default-features = false } +solana-keccak-hasher = { path = "sdk/keccak-hasher", version = "=2.2.0" } solana-keypair = { path = "sdk/keypair", version = "=2.2.0" } solana-last-restart-slot = { path = "sdk/last-restart-slot", version = "=2.2.0" } solana-lattice-hash = { path = "lattice-hash", version = "=2.2.0" } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 04379aeb571d24..69e4fb808ecba6 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5749,6 +5749,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-keccak-hasher" +version = "2.2.0" +dependencies = [ + "sha3", + "solana-define-syscall", + "solana-hash", + "solana-sanitize", +] + [[package]] name = "solana-keypair" version = "2.2.0" @@ -6095,6 +6105,7 @@ dependencies = [ "solana-fee-calculator", "solana-hash", "solana-instruction", + "solana-keccak-hasher", "solana-last-restart-slot", "solana-msg", "solana-native-token", diff --git a/sdk/keccak-hasher/Cargo.toml b/sdk/keccak-hasher/Cargo.toml new file mode 100644 index 00000000000000..91fd59b41973ea --- /dev/null +++ b/sdk/keccak-hasher/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "solana-keccak-hasher" +description = "Solana Keccak hashing" +documentation = "https://docs.rs/solana-keccak-hasher" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] + +[dependencies] +borsh = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-frozen-abi = { workspace = true, optional = true, features = [ + "frozen-abi", +] } +solana-frozen-abi-macro = { workspace = true, optional = true, features = [ + "frozen-abi", +] } +solana-hash = { workspace = true } +solana-sanitize = { workspace = true } + +[target.'cfg(not(target_os = "solana"))'.dependencies] +sha3 = { workspace = true } + +[target.'cfg(target_os = "solana")'.dependencies] +# sha3 should be removed in the next breaking release, +# as there's no reason to use the crate instead of the syscall +# onchain +sha3 = { workspace = true, optional = true } +solana-define-syscall = { workspace = true } + +[features] +borsh = ["dep:borsh", "std"] +frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", "std"] +serde = ["dep:serde", "dep:serde_derive"] +sha3 = ["dep:sha3"] +std = ["solana-hash/std"] + +[lints] +workspace = true diff --git a/sdk/program/src/keccak.rs b/sdk/keccak-hasher/src/lib.rs similarity index 59% rename from sdk/program/src/keccak.rs rename to sdk/keccak-hasher/src/lib.rs index cf94dfa4287722..b0a18694c50e5a 100644 --- a/sdk/program/src/keccak.rs +++ b/sdk/keccak-hasher/src/lib.rs @@ -1,34 +1,51 @@ //! Hashing with the [keccak] (SHA-3) hash function. //! //! [keccak]: https://keccak.team/keccak.html - +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(feature = "frozen-abi", feature(min_specialization))] +#![no_std] +#[cfg(feature = "std")] +extern crate std; + +#[cfg(any(feature = "sha3", not(target_os = "solana")))] +use sha3::{Digest, Keccak256}; +pub use solana_hash::{ParseHashError, HASH_BYTES, MAX_BASE58_LEN}; #[cfg(feature = "borsh")] -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use { - sha3::{Digest, Keccak256}, + borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, + std::string::ToString, +}; +use { + core::{fmt, str::FromStr}, solana_sanitize::Sanitize, - std::{convert::TryFrom, fmt, str::FromStr}, - thiserror::Error, }; -pub const HASH_BYTES: usize = 32; -/// Maximum string length of a base58 encoded hash -const MAX_BASE58_LEN: usize = 44; -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] +// TODO: replace this with `solana_hash::Hash` in the +// next breaking change. +// It's a breaking change because the field is public +// here and private in `solana_hash`, and making +// it public in `solana_hash` would break wasm-bindgen +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] #[cfg_attr( feature = "borsh", derive(BorshSerialize, BorshDeserialize, BorshSchema), borsh(crate = "borsh") )] -#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr( + feature = "serde", + derive(serde_derive::Deserialize, serde_derive::Serialize) +)] +#[derive(Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct Hash(pub [u8; HASH_BYTES]); +#[cfg(any(feature = "sha3", not(target_os = "solana")))] #[derive(Clone, Default)] pub struct Hasher { hasher: Keccak256, } +#[cfg(any(feature = "sha3", not(target_os = "solana")))] impl Hasher { pub fn hash(&mut self, val: &[u8]) { self.hasher.update(val); @@ -43,6 +60,18 @@ impl Hasher { } } +impl From for Hash { + fn from(val: solana_hash::Hash) -> Self { + Self(val.to_bytes()) + } +} + +impl From for solana_hash::Hash { + fn from(val: Hash) -> Self { + Self::new_from_array(val.0) + } +} + impl Sanitize for Hash {} impl AsRef<[u8]> for Hash { @@ -53,46 +82,32 @@ impl AsRef<[u8]> for Hash { impl fmt::Debug for Hash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", bs58::encode(self.0).into_string()) + let converted: solana_hash::Hash = (*self).into(); + fmt::Debug::fmt(&converted, f) } } impl fmt::Display for Hash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", bs58::encode(self.0).into_string()) + let converted: solana_hash::Hash = (*self).into(); + fmt::Display::fmt(&converted, f) } } -#[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); - } - bs58::decode(s) - .into_vec() - .map_err(|_| ParseHashError::Invalid) - .and_then(|bytes| { - <[u8; HASH_BYTES]>::try_from(bytes) - .map(Hash::new_from_array) - .map_err(|_| ParseHashError::WrongSize) - }) + let unconverted = solana_hash::Hash::from_str(s)?; + Ok(unconverted.into()) } } impl Hash { #[deprecated(since = "2.2.0", note = "Use 'Hash::new_from_array' instead")] pub fn new(hash_slice: &[u8]) -> Self { - Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap()) + #[allow(deprecated)] + Self::from(solana_hash::Hash::new(hash_slice)) } pub const fn new_from_array(hash_array: [u8; HASH_BYTES]) -> Self { @@ -101,13 +116,7 @@ impl Hash { /// unique Hash for tests and benchmarks. pub fn new_unique() -> Self { - use solana_atomic_u64::AtomicU64; - static I: AtomicU64 = AtomicU64::new(1); - - let mut b = [0u8; HASH_BYTES]; - let i = I.fetch_add(1); - b[0..8].copy_from_slice(&i.to_le_bytes()); - Self::new_from_array(b) + Self::from(solana_hash::Hash::new_unique()) } pub fn to_bytes(self) -> [u8; HASH_BYTES] { @@ -130,7 +139,7 @@ pub fn hashv(vals: &[&[u8]]) -> Hash { { let mut hash_result = [0; HASH_BYTES]; unsafe { - crate::syscalls::sol_keccak256( + solana_define_syscall::definitions::sol_keccak256( vals as *const _ as *const u8, vals.len() as u64, &mut hash_result as *mut _ as *mut u8, @@ -145,6 +154,7 @@ pub fn hash(val: &[u8]) -> Hash { hashv(&[val]) } +#[cfg(feature = "std")] /// 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(); diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 141773c4f789df..1db2814a97d495 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -54,6 +54,7 @@ solana-instruction = { workspace = true, default-features = false, features = [ "serde", "std", ] } +solana-keccak-hasher = { workspace = true, features = ["sha3"] } solana-last-restart-slot = { workspace = true, features = ["serde", "sysvar"] } solana-msg = { workspace = true } solana-native-token = { workspace = true } diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 55da6831369d8f..4cb41e6cbed3cd 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -484,7 +484,6 @@ pub mod feature; pub mod hash; pub mod incinerator; pub mod instruction; -pub mod keccak; pub mod lamports; pub mod loader_instruction; pub mod loader_upgradeable_instruction; @@ -522,6 +521,8 @@ pub use solana_borsh::v1 as borsh1; pub use solana_epoch_rewards as epoch_rewards; #[deprecated(since = "2.1.0", note = "Use `solana-fee-calculator` crate instead")] pub use solana_fee_calculator as fee_calculator; +#[deprecated(since = "2.2.0", note = "Use `solana-keccak-hasher` crate instead")] +pub use solana_keccak_hasher as keccak; #[deprecated(since = "2.1.0", note = "Use `solana-last-restart-slot` crate instead")] pub use solana_last_restart_slot as last_restart_slot; #[deprecated(since = "2.1.0", note = "Use `solana-program-memory` crate instead")] diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index 8e5b6843bd91c7..851010dd0db9f6 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -5570,6 +5570,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-keccak-hasher" +version = "2.2.0" +dependencies = [ + "sha3", + "solana-define-syscall", + "solana-hash", + "solana-sanitize", +] + [[package]] name = "solana-keypair" version = "2.2.0" @@ -5916,6 +5926,7 @@ dependencies = [ "solana-fee-calculator", "solana-hash", "solana-instruction", + "solana-keccak-hasher", "solana-last-restart-slot", "solana-msg", "solana-native-token",