From 0c5108fc84de15cd96a462eb837198ed1957eaad Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Thu, 5 Sep 2024 14:19:20 +0400 Subject: [PATCH] sdk: Extract hash and hasher crates (#2015) * extract hash and hasher crates and re-export their contents in solana_program::hash * make bytemuck and serde optional, and activate features in solana-program * fix frozen-abi support * fix import * update lock file * cargo sort * fix wasm_bindgen import * typo * fmt * make the inner field of Hash pub(crate) again because otherwise wasm_bindgen breaks * move program/src/wasm/hash.rs contents to solana-hash crate * update lock file * remove duplicate frozen-abi stuff * fix specialization stuff * delete tmp tests * update BlockhashQueue digest * Revert "update BlockhashQueue digest" This reverts commit 591302bd6efbd9be51c83a2dc7d13380ecc6b591. * update expected digests after confirming that the change is merely from moving the Hash struct and not a real ABI change * update another digest * update digests in sdk and program * update digests in runtime * update VoteTransaction digest * conditionally activate solana-hash/borsh in solana-program * move js-sys dep under cfg(target_arch = "wasm32") * remove thiserror from hash crate * remove solana-program dependency from merkle-tree * make solana-hash no_std by default * fmt * fmt after rebase * make std feature default * make sha2 an optional dep when target_os = "solana", because it's unlikely to be used in that case * fmt * make rustc_version optional * update lock file * fix frozen-abi lint * another lint fix * add comment about sha2 removal * avoid Vec in FromStr Co-authored-by: Jon C * put Hash::new_unique behind #[cfg(feature = "dev-context-only-utils")] * move tests from solana-hasher to solana-hash * rename solana-hasher to solana-sha256-hasher * fmt * make conditional import more consistent Co-authored-by: Jon C * don't use std feature of bs58 in solana-hash * undo putting new_unique behind dev-context-only-utils * missing feature * rename to write_as_base58 and reorder params Co-authored-by: Jon C * update write_as_base58 usage * fix feature activation for tests * remove part of doc that no longer makes sense Co-authored-by: Jon C --------- Co-authored-by: Jon C --- Cargo.lock | 34 +++- Cargo.toml | 6 +- accounts-db/src/blockhash_queue.rs | 2 +- core/src/banking_trace.rs | 2 +- core/src/consensus.rs | 2 +- core/src/consensus/tower1_14_11.rs | 2 +- core/src/consensus/tower1_7_14.rs | 2 +- core/src/repair/serve_repair.rs | 4 +- frozen-abi/Cargo.toml | 2 +- gossip/src/cluster_info.rs | 2 +- merkle-tree/Cargo.toml | 3 +- merkle-tree/src/merkle_tree.rs | 2 +- programs/sbf/Cargo.lock | 30 ++- programs/vote/src/vote_state/mod.rs | 2 +- rpc-client-api/Cargo.toml | 2 +- runtime/src/bank.rs | 2 +- runtime/src/bank/serde_snapshot.rs | 2 +- sdk/hash/Cargo.toml | 52 +++++ sdk/hash/build.rs | 1 + sdk/hash/src/lib.rs | 254 ++++++++++++++++++++++++ sdk/macro/Cargo.toml | 2 +- sdk/program/Cargo.toml | 13 +- sdk/program/src/hash.rs | 236 +--------------------- sdk/program/src/message/legacy.rs | 9 +- sdk/program/src/message/versions/mod.rs | 6 +- sdk/program/src/syscalls/definitions.rs | 5 +- sdk/program/src/vote/state/mod.rs | 6 +- sdk/program/src/wasm/hash.rs | 57 ------ sdk/program/src/wasm/mod.rs | 1 - sdk/sha256-hasher/Cargo.toml | 32 +++ sdk/sha256-hasher/src/lib.rs | 68 +++++++ sdk/src/transaction/mod.rs | 4 +- 32 files changed, 521 insertions(+), 326 deletions(-) create mode 100644 sdk/hash/Cargo.toml create mode 120000 sdk/hash/build.rs create mode 100644 sdk/hash/src/lib.rs delete mode 100644 sdk/program/src/wasm/hash.rs create mode 100644 sdk/sha256-hasher/Cargo.toml create mode 100644 sdk/sha256-hasher/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 05283f66d31d42..62b36b6bd5d043 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6572,6 +6572,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-hash" +version = "2.1.0" +dependencies = [ + "borsh 1.5.1", + "bs58", + "bytemuck", + "bytemuck_derive", + "js-sys", + "rustc_version 0.4.1", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-hash", + "solana-sanitize", + "wasm-bindgen", +] + [[package]] name = "solana-inline-spl" version = "2.1.0" @@ -6800,7 +6820,8 @@ version = "2.1.0" dependencies = [ "fast-math", "hex", - "solana-program", + "solana-hash", + "solana-sha256-hasher", ] [[package]] @@ -7012,6 +7033,7 @@ dependencies = [ "solana-define-syscall", "solana-frozen-abi", "solana-frozen-abi-macro", + "solana-hash", "solana-logger", "solana-msg", "solana-program-memory", @@ -7019,6 +7041,7 @@ dependencies = [ "solana-sdk-macro", "solana-secp256k1-recover", "solana-serde-varint", + "solana-sha256-hasher", "solana-short-vec", "static_assertions", "test-case", @@ -7577,6 +7600,15 @@ dependencies = [ "solana-short-vec", ] +[[package]] +name = "solana-sha256-hasher" +version = "2.1.0" +dependencies = [ + "sha2 0.10.8", + "solana-define-syscall", + "solana-hash", +] + [[package]] name = "solana-short-vec" version = "2.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3859d377714dff..11d7bc1f9550cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,12 +106,14 @@ members = [ "sdk/clock", "sdk/decode-error", "sdk/gen-headers", + "sdk/hash", "sdk/macro", "sdk/msg", "sdk/package-metadata-macro", "sdk/program", "sdk/program-memory", "sdk/serde-varint", + "sdk/sha256-hasher", "send-transaction-service", "short-vec", "stake-accounts", @@ -198,7 +200,7 @@ bincode = "1.3.3" bitflags = { version = "2.6.0" } blake3 = "1.5.1" borsh = { version = "1.5.1", features = ["derive", "unstable__schema"] } -bs58 = "0.5.1" +bs58 = { version = "0.5.1", default-features = false } bv = "0.11.1" byte-unit = "4.0.19" bytecount = "0.6.8" @@ -390,6 +392,7 @@ solana-genesis-utils = { path = "genesis-utils", version = "=2.1.0" } agave-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=2.1.0" } solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=2.1.0" } solana-gossip = { path = "gossip", version = "=2.1.0" } +solana-hash = { path = "sdk/hash", version = "=2.1.0" } solana-inline-spl = { path = "inline-spl", version = "=2.1.0" } solana-lattice-hash = { path = "lattice-hash", version = "=2.1.0" } solana-ledger = { path = "ledger", version = "=2.1.0" } @@ -418,6 +421,7 @@ solana-rayon-threadlimit = { path = "rayon-threadlimit", version = "=2.1.0" } solana-remote-wallet = { path = "remote-wallet", version = "=2.1.0", default-features = false } solana-sanitize = { path = "sanitize", version = "=2.1.0" } solana-serde-varint = { path = "sdk/serde-varint", version = "=2.1.0" } +solana-sha256-hasher = { path = "sdk/sha256-hasher", version = "=2.1.0" } solana-timings = { path = "timings", version = "=2.1.0" } solana-unified-scheduler-logic = { path = "unified-scheduler-logic", version = "=2.1.0" } solana-unified-scheduler-pool = { path = "unified-scheduler-pool", version = "=2.1.0" } diff --git a/accounts-db/src/blockhash_queue.rs b/accounts-db/src/blockhash_queue.rs index 871ad115596cb1..e99204160aa1ff 100644 --- a/accounts-db/src/blockhash_queue.rs +++ b/accounts-db/src/blockhash_queue.rs @@ -26,7 +26,7 @@ impl HashInfo { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "HzbCqb1YtCodkx6Fu57SpzkgaLp9WSrtw8texsjBhEDH") + frozen_abi(digest = "BxykY65dC2NCcDm17rHQPjEY8wK55sKAhfhKVFGc5T1u") )] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BlockhashQueue { diff --git a/core/src/banking_trace.rs b/core/src/banking_trace.rs index c2b3c38695d123..cc077dfa2c2755 100644 --- a/core/src/banking_trace.rs +++ b/core/src/banking_trace.rs @@ -65,7 +65,7 @@ pub struct BankingTracer { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "Eq6YrAFtTbtPrCEvh6Et1mZZDCARUg1gcK2qiZdqyjUz") + frozen_abi(digest = "F5GH1poHbPqipU4DB3MczhSxHZw4o27f3C7QnMVirFci") )] #[derive(Serialize, Deserialize, Debug)] pub struct TimedTracedEvent(pub std::time::SystemTime, pub TracedEvent); diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 79446125f5b819..c079dbb7cde51d 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -239,7 +239,7 @@ pub(crate) enum BlockhashStatus { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "H6T5A66kgJYANFXVrUprxV76WD5ce7Gf62q9SiBC2uYk") + frozen_abi(digest = "5BUswzvu7Qe44HbR4eBwPX4Jn9GSfhjmg8eijnBjoKUd") )] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Tower { diff --git a/core/src/consensus/tower1_14_11.rs b/core/src/consensus/tower1_14_11.rs index 8068d000deff22..7dfcd0bd340fac 100644 --- a/core/src/consensus/tower1_14_11.rs +++ b/core/src/consensus/tower1_14_11.rs @@ -9,7 +9,7 @@ use { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "EqYa8kwY9Z1Zbjxgs2aBbqKyCK4f7WAG8gJ7pVSQyKzk") + frozen_abi(digest = "9P6J8ZtVLR5zbUxWT83q1iUsJMH6B7SwcomSqcoomPmg") )] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Tower1_14_11 { diff --git a/core/src/consensus/tower1_7_14.rs b/core/src/consensus/tower1_7_14.rs index c7255bb88d9dd5..7a57b7b1d4f09e 100644 --- a/core/src/consensus/tower1_7_14.rs +++ b/core/src/consensus/tower1_7_14.rs @@ -11,7 +11,7 @@ use { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "9Kc3Cpak93xdL8bCnEwMWA8ZLGCBNfqh9PLo1o5RiPyT") + frozen_abi(digest = "DJVvkk4EFFCbA37vsKcFPGuwEULh2wEvMUESsTyvABzU") )] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Tower1_7_14 { diff --git a/core/src/repair/serve_repair.rs b/core/src/repair/serve_repair.rs index ad123ea8562957..24f484105a8ddb 100644 --- a/core/src/repair/serve_repair.rs +++ b/core/src/repair/serve_repair.rs @@ -143,7 +143,7 @@ impl AncestorHashesRepairType { #[cfg_attr( feature = "frozen-abi", derive(AbiEnumVisitor, AbiExample), - frozen_abi(digest = "AKpurCovzn6rsji4aQrP3hUdEHxjtXUfT7AatZXN7Rpz") + frozen_abi(digest = "98D6KvXCBxAHTxXgqiywLTugTp6WFUHSf559yy4VvKE7") )] #[derive(Debug, Deserialize, Serialize)] pub enum AncestorHashesResponse { @@ -224,7 +224,7 @@ pub(crate) type Ping = ping_pong::Ping<[u8; REPAIR_PING_TOKEN_SIZE]>; #[cfg_attr( feature = "frozen-abi", derive(AbiEnumVisitor, AbiExample), - frozen_abi(digest = "5cmSdmXMgkpUH5ZCmYYjxUVQfULe9iJqCqqfrADfsEmK") + frozen_abi(digest = "DzofXbeBFKJpbA88nUEnDpCGKvMEcguNphyQoVr7FyLh") )] #[derive(Debug, Deserialize, Serialize)] pub enum RepairProtocol { diff --git a/frozen-abi/Cargo.toml b/frozen-abi/Cargo.toml index 096662d05956f9..18382a28b3b1bd 100644 --- a/frozen-abi/Cargo.toml +++ b/frozen-abi/Cargo.toml @@ -10,7 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bs58 = { workspace = true } +bs58 = { workspace = true, features = ["alloc"] } bv = { workspace = true, features = ["serde"] } log = { workspace = true, features = ["std"] } serde = { workspace = true, features = ["rc"] } diff --git a/gossip/src/cluster_info.rs b/gossip/src/cluster_info.rs index 686510023b476f..fdce90cba41e6c 100644 --- a/gossip/src/cluster_info.rs +++ b/gossip/src/cluster_info.rs @@ -311,7 +311,7 @@ pub(crate) type Ping = ping_pong::Ping<[u8; GOSSIP_PING_TOKEN_SIZE]>; #[cfg_attr( feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor), - frozen_abi(digest = "6YaMJand6tKtNLUrqvusC5QVDmVLCWYRg5LtxYNi6XN4") + frozen_abi(digest = "7jwuQ3oFEy8bMnmr5XHSR2jqivZniG8ZjxHx3YKTfR6C") )] #[derive(Serialize, Deserialize, Debug)] #[allow(clippy::large_enum_variant)] diff --git a/merkle-tree/Cargo.toml b/merkle-tree/Cargo.toml index 9b9f566ade7406..cbd11a0e65797c 100644 --- a/merkle-tree/Cargo.toml +++ b/merkle-tree/Cargo.toml @@ -11,7 +11,8 @@ edition = { workspace = true } [dependencies] fast-math = { workspace = true } -solana-program = { workspace = true } +solana-hash = { workspace = true } +solana-sha256-hasher = { workspace = true } [dev-dependencies] hex = { workspace = true } diff --git a/merkle-tree/src/merkle_tree.rs b/merkle-tree/src/merkle_tree.rs index 09285a41e7af1f..de87811240a958 100644 --- a/merkle-tree/src/merkle_tree.rs +++ b/merkle-tree/src/merkle_tree.rs @@ -1,4 +1,4 @@ -use solana_program::hash::{hashv, Hash}; +use {solana_hash::Hash, solana_sha256_hasher::hashv}; // We need to discern between leaf and intermediate nodes to prevent trivial second // pre-image attacks. diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index fc502d752ce1ce..f0d9d78805ab85 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5163,6 +5163,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-hash" +version = "2.1.0" +dependencies = [ + "borsh 1.5.1", + "bs58", + "bytemuck", + "bytemuck_derive", + "js-sys", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-sanitize", + "wasm-bindgen", +] + [[package]] name = "solana-inline-spl" version = "2.1.0" @@ -5291,7 +5307,8 @@ name = "solana-merkle-tree" version = "2.1.0" dependencies = [ "fast-math", - "solana-program", + "solana-hash", + "solana-sha256-hasher", ] [[package]] @@ -5424,12 +5441,14 @@ dependencies = [ "solana-clock", "solana-decode-error", "solana-define-syscall", + "solana-hash", "solana-msg", "solana-program-memory", "solana-sanitize", "solana-sdk-macro", "solana-secp256k1-recover", "solana-serde-varint", + "solana-sha256-hasher", "solana-short-vec", "thiserror", "wasm-bindgen", @@ -6363,6 +6382,15 @@ dependencies = [ "serde", ] +[[package]] +name = "solana-sha256-hasher" +version = "2.1.0" +dependencies = [ + "sha2 0.10.8", + "solana-define-syscall", + "solana-hash", +] + [[package]] name = "solana-short-vec" version = "2.1.0" diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 851faa957db9fe..c4ea689fc9d3bf 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -30,7 +30,7 @@ use { #[cfg_attr( feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor), - frozen_abi(digest = "3R2hRL3FM6jovbYubq2UWeiVDEVzrhH6M1ihoCPZWLsk") + frozen_abi(digest = "3dbyMxwfCN43orGKa5YiyY1EqN2K97pTicNhKYTZSUQH") )] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub enum VoteTransaction { diff --git a/rpc-client-api/Cargo.toml b/rpc-client-api/Cargo.toml index 22a883244c709e..021e069960e39e 100644 --- a/rpc-client-api/Cargo.toml +++ b/rpc-client-api/Cargo.toml @@ -12,7 +12,7 @@ edition = { workspace = true } [dependencies] anyhow = { workspace = true } base64 = { workspace = true } -bs58 = { workspace = true } +bs58 = { workspace = true, features = ["std"] } jsonrpc-core = { workspace = true } reqwest = { workspace = true, features = ["blocking", "brotli", "deflate", "gzip", "rustls-tls", "json"] } reqwest-middleware = { workspace = true } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 841b09941873d8..0b53a3fd95617f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -247,7 +247,7 @@ struct RentMetrics { pub type BankStatusCache = StatusCache>; #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "9Pf3NTGr1AEzB4nKaVBY24uNwoQR4aJi8vc96W6kGvNk") + frozen_abi(digest = "EQwW6Ym6ECKaAREnAgkhXYisBQovuraBKSALdJ8koZzq") )] pub type BankSlotDelta = SlotDelta>; diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index bdcf7ae6215f72..c628fe7c0e1360 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -535,7 +535,7 @@ mod tests { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "HQYDRuCaM5V1ggSuMPTKT5Mu2vE5HX4y4ZM1Xuorx6My") + frozen_abi(digest = "FuFBQtx7rGruVC3cyh4zvZ3uN4RUtBiwh1pXJRwUCcoS") )] #[derive(Serialize)] pub struct BankAbiTestWrapper { diff --git a/sdk/hash/Cargo.toml b/sdk/hash/Cargo.toml new file mode 100644 index 00000000000000..de0dafd76226da --- /dev/null +++ b/sdk/hash/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "solana-hash" +description = "Solana wrapper for the 32-byte output of a hashing algorithm." +documentation = "https://docs.rs/solana-hash" +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"] + +[dependencies] +borsh = { workspace = true, optional = true } +bs58 = { workspace = true, default-features = false } +bytemuck = { workspace = true, optional = true } +bytemuck_derive = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-atomic-u64 = { workspace = true } +solana-frozen-abi = { workspace = true, optional = true } +solana-frozen-abi-macro = { workspace = true, optional = true } +solana-sanitize = { workspace = true } + +[dev-dependencies] +solana-hash = { path = ".", features = ["dev-context-only-utils"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } + +[build-dependencies] +rustc_version = { workspace = true, optional = true } + +[features] +borsh = ["dep:borsh", "std"] +bytemuck = ["dep:bytemuck", "dep:bytemuck_derive"] +default = ["std"] +dev-context-only-utils = ["bs58/alloc"] +frozen-abi = [ + "dep:rustc_version", + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "std" +] +serde = ["dep:serde", "dep:serde_derive"] +std = [] + +[lints] +workspace = true diff --git a/sdk/hash/build.rs b/sdk/hash/build.rs new file mode 120000 index 00000000000000..84539eddaa6ded --- /dev/null +++ b/sdk/hash/build.rs @@ -0,0 +1 @@ +../../frozen-abi/build.rs \ No newline at end of file diff --git a/sdk/hash/src/lib.rs b/sdk/hash/src/lib.rs new file mode 100644 index 00000000000000..dfc2e3efce8cf3 --- /dev/null +++ b/sdk/hash/src/lib.rs @@ -0,0 +1,254 @@ +#![no_std] +#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] +#[cfg(feature = "borsh")] +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +#[cfg(any(feature = "std", target_arch = "wasm32"))] +extern crate std; +#[cfg(feature = "bytemuck")] +use bytemuck_derive::{Pod, Zeroable}; +#[cfg(feature = "serde")] +use serde_derive::{Deserialize, Serialize}; +#[cfg(any(all(feature = "borsh", feature = "std"), target_arch = "wasm32"))] +use std::string::ToString; +use { + core::{ + convert::TryFrom, + fmt, mem, + str::{from_utf8, FromStr}, + }, + solana_sanitize::Sanitize, +}; +#[cfg(target_arch = "wasm32")] +use { + js_sys::{Array, Uint8Array}, + std::{boxed::Box, format, string::String, vec}, + wasm_bindgen::{prelude::*, JsCast}, +}; + +/// Size of a hash in bytes. +pub const HASH_BYTES: usize = 32; +/// Maximum string length of a base58 encoded hash. +pub const MAX_BASE58_LEN: usize = 44; + +/// A hash; the 32-byte output of a hashing algorithm. +/// +/// This struct is used most often in `solana-sdk` and related crates to contain +/// a [SHA-256] hash, but may instead contain a [blake3] hash. +/// +/// [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 +/// [blake3]: https://github.com/BLAKE3-team/BLAKE3 +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr( + feature = "borsh", + derive(BorshSerialize, BorshDeserialize), + borsh(crate = "borsh") +)] +#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))] +#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize,))] +#[derive(Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct Hash(pub(crate) [u8; HASH_BYTES]); + +impl Sanitize for Hash {} + +impl From<[u8; HASH_BYTES]> for Hash { + fn from(from: [u8; 32]) -> Self { + Self(from) + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +fn write_as_base58(f: &mut fmt::Formatter, h: &Hash) -> fmt::Result { + let mut out = [0u8; MAX_BASE58_LEN]; + let out_slice: &mut [u8] = &mut out; + // This will never fail because the only possible error is BufferTooSmall, + // and we will never call it with too small a buffer. + let len = bs58::encode(h.0).onto(out_slice).unwrap(); + let as_str = from_utf8(&out[..len]).unwrap(); + f.write_str(as_str) +} + +impl fmt::Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write_as_base58(f, self) + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write_as_base58(f, self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ParseHashError { + WrongSize, + Invalid, +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseHashError {} + +impl fmt::Display for ParseHashError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParseHashError::WrongSize => f.write_str("string decoded to wrong size for hash"), + ParseHashError::Invalid => f.write_str("failed to decoded string to hash"), + } + } +} + +impl FromStr for Hash { + type Err = ParseHashError; + + fn from_str(s: &str) -> Result { + if s.len() > MAX_BASE58_LEN { + return Err(ParseHashError::WrongSize); + } + let mut bytes = [0; HASH_BYTES]; + let decoded_size = bs58::decode(s) + .onto(&mut bytes) + .map_err(|_| ParseHashError::Invalid)?; + if decoded_size != mem::size_of::() { + Err(ParseHashError::WrongSize) + } else { + Ok(bytes.into()) + } + } +} + +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 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(&b) + } + + pub fn to_bytes(self) -> [u8; HASH_BYTES] { + self.0 + } +} + +#[cfg(target_arch = "wasm32")] +#[allow(non_snake_case)] +#[wasm_bindgen] +impl Hash { + /// Create a new Hash object + /// + /// * `value` - optional hash as a base58 encoded string, `Uint8Array`, `[number]` + #[wasm_bindgen(constructor)] + pub fn constructor(value: JsValue) -> Result { + if let Some(base58_str) = value.as_string() { + base58_str + .parse::() + .map_err(|x| JsValue::from(x.to_string())) + } else if let Some(uint8_array) = value.dyn_ref::() { + Ok(Hash::new(&uint8_array.to_vec())) + } else if let Some(array) = value.dyn_ref::() { + let mut bytes = vec![]; + let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable"); + for x in iterator { + let x = x?; + + if let Some(n) = x.as_f64() { + if n >= 0. && n <= 255. { + bytes.push(n as u8); + continue; + } + } + return Err(format!("Invalid array argument: {:?}", x).into()); + } + Ok(Hash::new(&bytes)) + } else if value.is_undefined() { + Ok(Hash::default()) + } else { + Err("Unsupported argument".into()) + } + } + + /// Return the base58 string representation of the hash + pub fn toString(&self) -> String { + self.to_string() + } + + /// Checks if two `Hash`s are equal + pub fn equals(&self, other: &Hash) -> bool { + self == other + } + + /// Return the `Uint8Array` representation of the hash + pub fn toBytes(&self) -> Box<[u8]> { + self.0.clone().into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_unique() { + assert!(Hash::new_unique() != Hash::new_unique()); + } + + #[test] + fn test_hash_fromstr() { + let hash = Hash::new_from_array([1; 32]); + + let mut hash_base58_str = bs58::encode(hash).into_string(); + + assert_eq!(hash_base58_str.parse::(), Ok(hash)); + + hash_base58_str.push_str(&bs58::encode(hash.as_ref()).into_string()); + assert_eq!( + hash_base58_str.parse::(), + Err(ParseHashError::WrongSize) + ); + + hash_base58_str.truncate(hash_base58_str.len() / 2); + assert_eq!(hash_base58_str.parse::(), Ok(hash)); + + hash_base58_str.truncate(hash_base58_str.len() / 2); + assert_eq!( + hash_base58_str.parse::(), + Err(ParseHashError::WrongSize) + ); + + let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string(); + assert!(input_too_big.len() > MAX_BASE58_LEN); + assert_eq!( + input_too_big.parse::(), + Err(ParseHashError::WrongSize) + ); + + let mut hash_base58_str = bs58::encode(hash.as_ref()).into_string(); + assert_eq!(hash_base58_str.parse::(), Ok(hash)); + + // throw some non-base58 stuff in there + hash_base58_str.replace_range(..1, "I"); + assert_eq!( + hash_base58_str.parse::(), + Err(ParseHashError::Invalid) + ); + } +} diff --git a/sdk/macro/Cargo.toml b/sdk/macro/Cargo.toml index 04b11590829192..07829062a4ba01 100644 --- a/sdk/macro/Cargo.toml +++ b/sdk/macro/Cargo.toml @@ -13,7 +13,7 @@ edition = { workspace = true } proc-macro = true [dependencies] -bs58 = { workspace = true } +bs58 = { workspace = true, features = ["alloc"] } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true, features = ["full"] } diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 3ab08562a96027..f293283f9dff3e 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -16,7 +16,7 @@ bincode = { workspace = true } blake3 = { workspace = true, features = ["digest", "traits-preview"] } borsh = { workspace = true, optional = true } borsh0-10 = { package = "borsh", version = "0.10.3", optional = true } -bs58 = { workspace = true } +bs58 = { workspace = true, features = ["alloc"] } bv = { workspace = true, features = ["serde"] } bytemuck = { workspace = true } bytemuck_derive = { workspace = true } @@ -36,12 +36,18 @@ solana-clock = { workspace = true, features = ["serde"] } solana-decode-error = { workspace = 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, features = [ + "bytemuck", + "serde", + "std", +] } solana-msg = { workspace = true } solana-program-memory = { workspace = true } solana-sanitize = { workspace = true } solana-sdk-macro = { workspace = true } solana-secp256k1-recover = { workspace = true } solana-serde-varint = { workspace = true } +solana-sha256-hasher = { workspace = true, features = ["sha2"] } solana-short-vec = { workspace = true } thiserror = { workspace = true } @@ -98,13 +104,14 @@ crate-type = ["cdylib", "rlib"] [features] default = ["borsh"] -borsh = ["dep:borsh", "dep:borsh0-10"] +borsh = ["dep:borsh", "dep:borsh0-10", "solana-hash/borsh"] dev-context-only-utils = ["dep:qualifier_attr"] frozen-abi = [ "dep:rustc_version", "dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", - "solana-short-vec/frozen-abi", + "solana-hash/frozen-abi", + "solana-short-vec/frozen-abi" ] [lints] diff --git a/sdk/program/src/hash.rs b/sdk/program/src/hash.rs index 27967c850376bb..990f6c184814bf 100644 --- a/sdk/program/src/hash.rs +++ b/sdk/program/src/hash.rs @@ -3,237 +3,7 @@ //! [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 //! [`Hash`]: struct@Hash -#[cfg(target_arch = "wasm32")] -use crate::wasm_bindgen; -#[cfg(feature = "borsh")] -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use { - bytemuck_derive::{Pod, Zeroable}, - sha2::{Digest, Sha256}, - solana_sanitize::Sanitize, - std::{convert::TryFrom, fmt, mem, str::FromStr}, - thiserror::Error, +pub use { + solana_hash::{Hash, ParseHashError, HASH_BYTES}, + solana_sha256_hasher::{extend_and_hash, hash, hashv, Hasher}, }; - -/// Size of a hash in bytes. -pub const HASH_BYTES: usize = 32; -/// Maximum string length of a base58 encoded hash. -const MAX_BASE58_LEN: usize = 44; - -/// A hash; the 32-byte output of a hashing algorithm. -/// -/// This struct is used most often in `solana-sdk` and related crates to contain -/// a [SHA-256] hash, but may instead contain a [blake3] hash, as created by the -/// [`blake3`] module (and used in [`Message::hash`]). -/// -/// [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 -/// [blake3]: https://github.com/BLAKE3-team/BLAKE3 -/// [`blake3`]: crate::blake3 -/// [`Message::hash`]: crate::message::Message::hash -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[cfg_attr( - feature = "borsh", - derive(BorshSerialize, BorshDeserialize, BorshSchema), - borsh(crate = "borsh") -)] -#[derive( - Serialize, - Deserialize, - Clone, - Copy, - Default, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Pod, - Zeroable, -)] -#[repr(transparent)] -pub struct Hash(pub(crate) [u8; HASH_BYTES]); - -#[derive(Clone, Default)] -pub struct Hasher { - hasher: Sha256, -} - -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 { - Hash(self.hasher.finalize().into()) - } -} - -impl Sanitize for Hash {} - -impl From<[u8; HASH_BYTES]> for Hash { - fn from(from: [u8; 32]) -> Self { - Self(from) - } -} - -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 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(&b) - } - - pub fn to_bytes(self) -> [u8; HASH_BYTES] { - self.0 - } -} - -/// Return a Sha256 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_os = "solana"))] - { - let mut hasher = Hasher::default(); - hasher.hashv(vals); - hasher.result() - } - // Call via a system call to perform the calculation - #[cfg(target_os = "solana")] - { - let mut hash_result = [0; HASH_BYTES]; - unsafe { - crate::syscalls::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. -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) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new_unique() { - assert!(Hash::new_unique() != Hash::new_unique()); - } - - #[test] - fn test_hash_fromstr() { - let hash = hash(&[1u8]); - - let mut hash_base58_str = bs58::encode(hash).into_string(); - - assert_eq!(hash_base58_str.parse::(), Ok(hash)); - - hash_base58_str.push_str(&bs58::encode(hash.0).into_string()); - assert_eq!( - hash_base58_str.parse::(), - Err(ParseHashError::WrongSize) - ); - - hash_base58_str.truncate(hash_base58_str.len() / 2); - assert_eq!(hash_base58_str.parse::(), Ok(hash)); - - hash_base58_str.truncate(hash_base58_str.len() / 2); - assert_eq!( - hash_base58_str.parse::(), - Err(ParseHashError::WrongSize) - ); - - let input_too_big = bs58::encode(&[0xffu8; HASH_BYTES + 1]).into_string(); - assert!(input_too_big.len() > MAX_BASE58_LEN); - assert_eq!( - input_too_big.parse::(), - Err(ParseHashError::WrongSize) - ); - - let mut hash_base58_str = bs58::encode(hash.0).into_string(); - assert_eq!(hash_base58_str.parse::(), Ok(hash)); - - // throw some non-base58 stuff in there - hash_base58_str.replace_range(..1, "I"); - assert_eq!( - hash_base58_str.parse::(), - Err(ParseHashError::Invalid) - ); - } -} diff --git a/sdk/program/src/message/legacy.rs b/sdk/program/src/message/legacy.rs index 4c1d4c5a9da418..68d5dfb2e25588 100644 --- a/sdk/program/src/message/legacy.rs +++ b/sdk/program/src/message/legacy.rs @@ -123,7 +123,7 @@ fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec Hash { - use blake3::traits::digest::Digest; + use {blake3::traits::digest::Digest, solana_hash::HASH_BYTES}; let mut hasher = blake3::Hasher::new(); hasher.update(b"solana-tx-message-v1"); hasher.update(message_bytes); - Hash(hasher.finalize().into()) + let hash_bytes: [u8; HASH_BYTES] = hasher.finalize().into(); + hash_bytes.into() } pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction { diff --git a/sdk/program/src/message/versions/mod.rs b/sdk/program/src/message/versions/mod.rs index 53b95d96acf490..24bd21d1090328 100644 --- a/sdk/program/src/message/versions/mod.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -10,6 +10,7 @@ use { ser::{SerializeTuple, Serializer}, }, serde_derive::{Deserialize, Serialize}, + solana_hash::HASH_BYTES, solana_sanitize::{Sanitize, SanitizeError}, solana_short_vec as short_vec, std::{collections::HashSet, fmt}, @@ -33,7 +34,7 @@ pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; /// format. #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "8wyn6rxrJ1WwsUJkVxtDH9VEmd7djwqMfBLL3EpuY7H4"), + frozen_abi(digest = "3g49yJ9ZZPsT9iF6Za6FyWXV259vWcY6gfJ94uzQ5BcY"), derive(AbiEnumVisitor, AbiExample) )] #[derive(Debug, PartialEq, Eq, Clone)] @@ -161,7 +162,8 @@ impl VersionedMessage { let mut hasher = blake3::Hasher::new(); hasher.update(b"solana-tx-message-v1"); hasher.update(message_bytes); - Hash(hasher.finalize().into()) + let hash_bytes: [u8; HASH_BYTES] = hasher.finalize().into(); + hash_bytes.into() } } diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index fd22296e03ff59..061dd317c2794d 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -1,6 +1,6 @@ #[cfg(target_feature = "static-syscalls")] pub use solana_define_syscall::sys_hash; -#[deprecated(since = "2.1.0", note = "Use `solana-msg::sol_log` instead.")] +#[deprecated(since = "2.1.0", note = "Use `solana_msg::sol_log` instead.")] pub use solana_msg::sol_log; #[deprecated( since = "2.1.0", @@ -12,6 +12,8 @@ pub use solana_program_memory::syscalls::{sol_memcmp_, sol_memcpy_, sol_memmove_ note = "Use `solana_secp256k1_recover::sol_secp256k1_recover` instead" )] pub use solana_secp256k1_recover::sol_secp256k1_recover; +#[deprecated(since = "2.1.0", note = "Use solana_sha256_hasher::sol_sha256 instead")] +pub use solana_sha256_hasher::sol_sha256; use { crate::{ instruction::{AccountMeta, ProcessedSiblingInstruction}, @@ -24,7 +26,6 @@ define_syscall!(fn sol_log_compute_units_()); define_syscall!(fn sol_log_pubkey(pubkey_addr: *const u8)); define_syscall!(fn sol_create_program_address(seeds_addr: *const u8, seeds_len: u64, program_id_addr: *const u8, address_bytes_addr: *const u8) -> u64); define_syscall!(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); -define_syscall!(fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); define_syscall!(fn sol_keccak256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); define_syscall!(fn sol_blake3(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); define_syscall!(fn sol_invoke_signed_c(instruction_addr: *const u8, account_infos_addr: *const u8, account_infos_len: u64, signers_seeds_addr: *const u8, signers_seeds_len: u64) -> u64); diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index 7a42ad8d066243..40f3268fcfd3d4 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -54,7 +54,7 @@ pub const VOTE_CREDITS_MAXIMUM_PER_SLOT: u8 = 16; #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "Ch2vVEwos2EjAVqSHCyJjnN2MNX1yrpapZTGhMSCjWUH"), + frozen_abi(digest = "GvUzgtcxhKVVxPAjSntXGPqjLZK5ovgZzCiUP1tDpB9q"), derive(AbiExample) )] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -168,7 +168,7 @@ impl From for LandedVote { #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "GwJfVFsATSj7nvKwtUkHYzqPRaPY6SLxPGXApuCya3x5"), + frozen_abi(digest = "DRKTb72wifCUcCTSJs6PqWrQQK5Pfis4SCLEvXqWnDaL"), derive(AbiExample) )] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -221,7 +221,7 @@ impl VoteStateUpdate { #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "5VUusSTenF9vZ9eHiCprVe9ABJUHCubeDNCCDxykybZY"), + frozen_abi(digest = "5PFw9pyF1UG1DXVsw7gpjHegNyRycAAxWf2GA9wUXPs5"), derive(AbiExample) )] #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] diff --git a/sdk/program/src/wasm/hash.rs b/sdk/program/src/wasm/hash.rs deleted file mode 100644 index add1e6bbe80657..00000000000000 --- a/sdk/program/src/wasm/hash.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! `Hash` Javascript interface -#![cfg(target_arch = "wasm32")] -#![allow(non_snake_case)] -use { - crate::{hash::*, wasm::display_to_jsvalue}, - js_sys::{Array, Uint8Array}, - wasm_bindgen::{prelude::*, JsCast}, -}; - -#[wasm_bindgen] -impl Hash { - /// Create a new Hash object - /// - /// * `value` - optional hash as a base58 encoded string, `Uint8Array`, `[number]` - #[wasm_bindgen(constructor)] - pub fn constructor(value: JsValue) -> Result { - if let Some(base58_str) = value.as_string() { - base58_str.parse::().map_err(display_to_jsvalue) - } else if let Some(uint8_array) = value.dyn_ref::() { - Ok(Hash::new(&uint8_array.to_vec())) - } else if let Some(array) = value.dyn_ref::() { - let mut bytes = vec![]; - let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable"); - for x in iterator { - let x = x?; - - if let Some(n) = x.as_f64() { - if n >= 0. && n <= 255. { - bytes.push(n as u8); - continue; - } - } - return Err(format!("Invalid array argument: {:?}", x).into()); - } - Ok(Hash::new(&bytes)) - } else if value.is_undefined() { - Ok(Hash::default()) - } else { - Err("Unsupported argument".into()) - } - } - - /// Return the base58 string representation of the hash - pub fn toString(&self) -> String { - self.to_string() - } - - /// Checks if two `Hash`s are equal - pub fn equals(&self, other: &Hash) -> bool { - self == other - } - - /// Return the `Uint8Array` representation of the hash - pub fn toBytes(&self) -> Box<[u8]> { - self.0.clone().into() - } -} diff --git a/sdk/program/src/wasm/mod.rs b/sdk/program/src/wasm/mod.rs index b7939a142a2d17..c390efed559ab0 100644 --- a/sdk/program/src/wasm/mod.rs +++ b/sdk/program/src/wasm/mod.rs @@ -2,7 +2,6 @@ #![cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -pub mod hash; pub mod instructions; pub mod pubkey; pub mod system_instruction; diff --git a/sdk/sha256-hasher/Cargo.toml b/sdk/sha256-hasher/Cargo.toml new file mode 100644 index 00000000000000..3933e259f07424 --- /dev/null +++ b/sdk/sha256-hasher/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "solana-sha256-hasher" +description = "Solana SHA256 hashing" +documentation = "https://docs.rs/solana-sha256-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"] + +[dependencies] +solana-hash = { workspace = true } + +[target.'cfg(not(target_os = "solana"))'.dependencies] +sha2 = { workspace = true } + +[target.'cfg(target_os = "solana")'.dependencies] +# sha2 should be removed in the next breaking release, +# as there's no reason to use the crate instead of the syscall +# onchain +sha2 = { workspace = true, optional = true } +solana-define-syscall = { workspace = true } + +[features] +sha2 = ["dep:sha2"] + +[lints] +workspace = true diff --git a/sdk/sha256-hasher/src/lib.rs b/sdk/sha256-hasher/src/lib.rs new file mode 100644 index 00000000000000..fe34c3ea929a91 --- /dev/null +++ b/sdk/sha256-hasher/src/lib.rs @@ -0,0 +1,68 @@ +#![no_std] +#[cfg(any(feature = "sha2", not(target_os = "solana")))] +use sha2::{Digest, Sha256}; +#[cfg(target_os = "solana")] +use solana_define_syscall::define_syscall; +use solana_hash::Hash; + +#[cfg(any(feature = "sha2", not(target_os = "solana")))] +#[derive(Clone, Default)] +pub struct Hasher { + hasher: Sha256, +} + +#[cfg(any(feature = "sha2", not(target_os = "solana")))] +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 { + let bytes: [u8; solana_hash::HASH_BYTES] = self.hasher.finalize().into(); + bytes.into() + } +} + +#[cfg(target_os = "solana")] +define_syscall!(fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); + +/// Return a Sha256 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_os = "solana"))] + { + let mut hasher = Hasher::default(); + hasher.hashv(vals); + hasher.result() + } + // Call via a system call to perform the calculation + #[cfg(target_os = "solana")] + { + let mut hash_result = [0; solana_hash::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. +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/src/transaction/mod.rs b/sdk/src/transaction/mod.rs index 535c8dd9bf665e..c5022487e19e06 100644 --- a/sdk/src/transaction/mod.rs +++ b/sdk/src/transaction/mod.rs @@ -172,7 +172,7 @@ pub type Result = result::Result; #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "5LPHxp7TKPeV7GZ9pcT4NxNxJa3ZhvToDekCMAPvNWLv") + frozen_abi(digest = "sGWhrQNiMNnUjPSG5cZvxujYaxHaiU5ggbvp46hKZSN") )] #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] pub struct Transaction { @@ -200,7 +200,7 @@ pub struct Transaction { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "5LPHxp7TKPeV7GZ9pcT4NxNxJa3ZhvToDekCMAPvNWLv") + frozen_abi(digest = "sGWhrQNiMNnUjPSG5cZvxujYaxHaiU5ggbvp46hKZSN") )] #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] pub struct Transaction {