use crate::error::ProgramError;
#[cfg(target_os = "solana")]
use core::mem::MaybeUninit;
pub type Sha256Hash = [u8; 32];
pub type Keccak256Hash = [u8; 32];
pub type Blake3Hash = [u8; 32];
#[cfg(feature = "sha512-syscall")]
pub type Sha512Hash = [u8; 64];
pub const MAX_HASH_SEGMENTS: usize = 20_000;
#[cfg(target_os = "solana")]
macro_rules! hash_syscall {
($syscall:ident, $inputs:expr, $out:expr) => {{
const _: () = assert!(core::mem::size_of::<&[u8]>() == 16);
unsafe {
crate::syscalls::$syscall(
$inputs.as_ptr() as *const u8,
$inputs.len() as u64,
$out.as_mut_ptr() as *mut u8,
);
}
}};
}
#[inline]
pub fn sha256(inputs: &[&[u8]]) -> Result<Sha256Hash, ProgramError> {
if inputs.len() > MAX_HASH_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
#[cfg(target_os = "solana")]
{
let mut result = MaybeUninit::<Sha256Hash>::uninit();
hash_syscall!(sol_sha256, inputs, result);
Ok(unsafe { result.assume_init() })
}
#[cfg(not(target_os = "solana"))]
{
let mut hasher = crate::sha256::ConstSha256::new();
let mut i = 0;
while i < inputs.len() {
hasher = hasher.update(inputs[i]);
i += 1;
}
Ok(hasher.finalize())
}
}
#[inline]
pub fn sha256_single(input: &[u8]) -> Result<Sha256Hash, ProgramError> {
sha256(&[input])
}
#[inline]
pub fn keccak256(inputs: &[&[u8]]) -> Result<Keccak256Hash, ProgramError> {
if inputs.len() > MAX_HASH_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
#[cfg(target_os = "solana")]
{
let mut result = MaybeUninit::<Keccak256Hash>::uninit();
hash_syscall!(sol_keccak256, inputs, result);
Ok(unsafe { result.assume_init() })
}
#[cfg(not(target_os = "solana"))]
{
let _ = inputs;
Ok([0u8; 32])
}
}
#[inline]
pub fn keccak256_single(input: &[u8]) -> Result<Keccak256Hash, ProgramError> {
keccak256(&[input])
}
#[inline]
pub fn blake3(inputs: &[&[u8]]) -> Result<Blake3Hash, ProgramError> {
if inputs.len() > MAX_HASH_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
#[cfg(target_os = "solana")]
{
let mut result = MaybeUninit::<Blake3Hash>::uninit();
hash_syscall!(sol_blake3, inputs, result);
Ok(unsafe { result.assume_init() })
}
#[cfg(not(target_os = "solana"))]
{
let _ = inputs;
Ok([0u8; 32])
}
}
#[inline]
pub fn blake3_single(input: &[u8]) -> Result<Blake3Hash, ProgramError> {
blake3(&[input])
}
#[cfg(feature = "sha512-syscall")]
#[inline]
pub fn sha512(inputs: &[&[u8]]) -> Result<Sha512Hash, ProgramError> {
if inputs.len() > MAX_HASH_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
#[cfg(target_os = "solana")]
{
let mut result = MaybeUninit::<Sha512Hash>::uninit();
hash_syscall!(sol_sha512, inputs, result);
Ok(unsafe { result.assume_init() })
}
#[cfg(not(target_os = "solana"))]
{
let _ = inputs;
Ok([0u8; 64])
}
}
#[cfg(test)]
mod tests {
use super::*;
const EMPTY: &[u8] = b"";
#[test]
fn sha256_off_chain_is_the_const_digest() {
assert_eq!(
sha256(&[b"abc"]).unwrap(),
crate::sha256::sha256(b"abc"),
"single segment"
);
assert_eq!(
sha256(&[b"global:", b"initialize"]).unwrap(),
crate::sha256::sha256(b"global:initialize"),
"segments hash as one stream"
);
assert_eq!(sha256_single(b"").unwrap(), crate::sha256::sha256(b""));
}
static AT_LIMIT: [&[u8]; MAX_HASH_SEGMENTS] = [EMPTY; MAX_HASH_SEGMENTS];
static PAST_LIMIT: [&[u8]; MAX_HASH_SEGMENTS + 1] = [EMPTY; MAX_HASH_SEGMENTS + 1];
#[test]
fn wrappers_accept_the_runtime_slice_limit() {
assert_eq!(sha256(&AT_LIMIT[..]), Ok(crate::sha256::sha256(b"")));
assert_eq!(keccak256(&AT_LIMIT[..]), Ok([0; 32]));
assert_eq!(blake3(&AT_LIMIT[..]), Ok([0; 32]));
}
#[test]
fn wrappers_refuse_beyond_the_runtime_slice_limit() {
assert_eq!(sha256(&PAST_LIMIT[..]), Err(ProgramError::InvalidArgument));
assert_eq!(
keccak256(&PAST_LIMIT[..]),
Err(ProgramError::InvalidArgument)
);
assert_eq!(blake3(&PAST_LIMIT[..]), Err(ProgramError::InvalidArgument));
}
}