Skip to main content

hopper_native/
hash.rs

1//! Cryptographic hash functions via Solana syscalls.
2//!
3//! No existing Solana framework wraps `sol_sha256` or `sol_keccak256`
4//! with ergonomic APIs at the raw substrate level. Programs that need
5//! hashing either pull in heavy crates or write unsafe syscall glue
6//! every time.
7//!
8//! Hopper wraps these syscalls with safe, zero-alloc APIs.
9
10use crate::error::ProgramError;
11
12/// SHA-256 hash output: 32 bytes.
13pub type Sha256Hash = [u8; 32];
14
15/// Keccak-256 hash output: 32 bytes.
16pub type Keccak256Hash = [u8; 32];
17
18/// Compute SHA-256 over one or more byte slices.
19///
20/// The Solana `sol_sha256` syscall accepts a vector of (ptr, len) pairs,
21/// so multi-part hashing is done in a single syscall without concatenation.
22///
23/// # Example
24///
25/// ```ignore
26/// let hash = sha256(&[b"hello", b" world"])?;
27/// ```
28#[inline]
29#[allow(unused_mut)]
30pub fn sha256(inputs: &[&[u8]]) -> Result<Sha256Hash, ProgramError> {
31    let mut result = [0u8; 32];
32
33    #[cfg(target_os = "solana")]
34    {
35        // Build the parameter array: each element is (ptr, len) as two u64s.
36        // Maximum practical limit: 16 segments.
37        let count = inputs.len().min(16);
38        let mut params: [u64; 32] = [0; 32];
39        let mut i = 0;
40        while i < count {
41            params[i * 2] = inputs[i].as_ptr() as u64;
42            params[i * 2 + 1] = inputs[i].len() as u64;
43            i += 1;
44        }
45
46        let rc = unsafe {
47            crate::syscalls::sol_sha256(
48                params.as_ptr() as *const u8,
49                count as u64,
50                result.as_mut_ptr(),
51            )
52        };
53        if rc != 0 {
54            return Err(ProgramError::InvalidArgument);
55        }
56    }
57    #[cfg(not(target_os = "solana"))]
58    {
59        let _ = inputs;
60        // Off-chain: return zeroed hash (tests should use a software
61        // implementation if they need real hashes).
62    }
63
64    Ok(result)
65}
66
67/// Compute SHA-256 over a single byte slice.
68#[inline]
69pub fn sha256_single(input: &[u8]) -> Result<Sha256Hash, ProgramError> {
70    sha256(&[input])
71}
72
73/// Compute Keccak-256 over one or more byte slices.
74///
75/// Same multi-part API as `sha256`. Keccak-256 is the hash function used
76/// by Ethereum's `keccak256()` and by Solana's secp256k1 precompile.
77#[inline]
78#[allow(unused_mut)]
79pub fn keccak256(inputs: &[&[u8]]) -> Result<Keccak256Hash, ProgramError> {
80    let mut result = [0u8; 32];
81
82    #[cfg(target_os = "solana")]
83    {
84        let count = inputs.len().min(16);
85        let mut params: [u64; 32] = [0; 32];
86        let mut i = 0;
87        while i < count {
88            params[i * 2] = inputs[i].as_ptr() as u64;
89            params[i * 2 + 1] = inputs[i].len() as u64;
90            i += 1;
91        }
92
93        let rc = unsafe {
94            crate::syscalls::sol_keccak256(
95                params.as_ptr() as *const u8,
96                count as u64,
97                result.as_mut_ptr(),
98            )
99        };
100        if rc != 0 {
101            return Err(ProgramError::InvalidArgument);
102        }
103    }
104    #[cfg(not(target_os = "solana"))]
105    {
106        let _ = inputs;
107    }
108
109    Ok(result)
110}
111
112/// Compute Keccak-256 over a single byte slice.
113#[inline]
114pub fn keccak256_single(input: &[u8]) -> Result<Keccak256Hash, ProgramError> {
115    keccak256(&[input])
116}