Skip to main content

solana_nostd_sha256/
lib.rs

1//! A more efficient, no_std SHA-256 for the Solana SVM.
2//!
3//! On `target_os = "solana"`, hashing routes through the `sol_sha256`
4//! syscall. Off-Solana, it falls through to the `sha2` crate so the same
5//! APIs work in host code (tests, off-chain tooling). The Solana
6//! implementation costs ~100 CUs for `hashv(&[b"test"])`, vs ~120 CUs for
7//! `solana_program::hash::hashv`.
8#![no_std]
9
10use core::mem::MaybeUninit;
11
12#[cfg(not(any(target_arch = "bpf", target_os = "solana")))]
13use sha2::{Digest, Sha256};
14
15/// Length of a SHA-256 digest, in bytes.
16pub const HASH_LENGTH: usize = 32;
17
18#[cfg(all(
19    any(target_arch = "bpf", target_os = "solana"),
20    not(feature = "static-syscalls")
21))]
22unsafe extern "C" {
23    fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64;
24}
25
26#[cfg(all(
27    any(target_arch = "bpf", target_os = "solana"),
28    feature = "static-syscalls"
29))]
30#[inline(always)]
31unsafe fn sol_sha256(vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64 {
32    // murmur3_32(b"sol_sha256", 0) — precomputed
33    const SOL_SHA256_ID: usize = 0x11f49d86;
34    let syscall: extern "C" fn(*const u8, u64, *mut u8) -> u64 =
35        unsafe { core::mem::transmute(SOL_SHA256_ID) };
36    syscall(vals, val_len, hash_result)
37}
38
39/// Hash a single byte slice and return the digest by value.
40#[cfg_attr(target_os = "solana", inline(always))]
41pub fn hash(data: &[u8]) -> [u8; HASH_LENGTH] {
42    hashv(&[data])
43}
44
45/// Hash any `T: AsRef<[u8]>` (e.g. `&str`, `&[u8; N]`) and return the digest.
46#[inline(always)]
47pub fn hash_ref<T: AsRef<[u8]>>(data: T) -> [u8; HASH_LENGTH] {
48    hashv(&[data.as_ref()])
49}
50
51/// Hash a sequence of byte slices as if they were concatenated, and return
52/// the digest. Cheaper than concatenating the inputs yourself.
53#[cfg_attr(target_os = "solana", inline(always))]
54pub fn hashv(data: &[&[u8]]) -> [u8; HASH_LENGTH] {
55    let mut out = MaybeUninit::<[u8; HASH_LENGTH]>::uninit();
56    unsafe {
57        hash_into(data, out.assume_init_mut());
58        out.assume_init()
59    }
60}
61
62/// Hash `data` directly into the provided 32-byte buffer.
63///
64/// Use this when you want the digest written into pre-existing storage
65/// (e.g. a struct field) without an intermediate move.
66#[cfg(not(target_os = "solana"))]
67pub fn hash_into(data: &[&[u8]], out: &mut [u8; HASH_LENGTH]) {
68    let mut hasher = Sha256::new();
69    for item in data {
70        hasher.update(item);
71    }
72    hasher.finalize_into(out.into());
73}
74
75/// Hash `data` directly into the provided 32-byte buffer.
76///
77/// Use this when you want the digest written into pre-existing storage
78/// (e.g. a struct field) without an intermediate move.
79#[cfg(target_os = "solana")]
80#[inline(always)]
81pub fn hash_into(data: &[&[u8]], out: &mut [u8; HASH_LENGTH]) {
82    unsafe {
83        sol_sha256(
84            data as *const _ as *const u8,
85            data.len() as u64,
86            out.as_mut_ptr(),
87        );
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use crate::*;
94
95    #[test]
96    fn test_hash() {
97        let h = hash_ref("test");
98        let h2 = hashv(&[b"test".as_ref()]);
99        assert_eq!(h, h2);
100        assert_eq!(
101            h2,
102            [
103                0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a,
104                0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0x0b, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15,
105                0xb0, 0xf0, 0x0a, 0x08
106            ]
107        );
108    }
109}