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        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
47        let rc = unsafe {
48            crate::syscalls::sol_sha256(
49                params.as_ptr() as *const u8,
50                count as u64,
51                result.as_mut_ptr(),
52            )
53        };
54        if rc != 0 {
55            return Err(ProgramError::InvalidArgument);
56        }
57    }
58    #[cfg(not(target_os = "solana"))]
59    {
60        let _ = inputs;
61        // Off-chain: return zeroed hash (tests should use a software
62        // implementation if they need real hashes).
63    }
64
65    Ok(result)
66}
67
68/// Compute SHA-256 over a single byte slice.
69#[inline]
70pub fn sha256_single(input: &[u8]) -> Result<Sha256Hash, ProgramError> {
71    sha256(&[input])
72}
73
74/// Compute Keccak-256 over one or more byte slices.
75///
76/// Same multi-part API as `sha256`. Keccak-256 is the hash function used
77/// by Ethereum's `keccak256()` and by Solana's secp256k1 precompile.
78#[inline]
79#[allow(unused_mut)]
80pub fn keccak256(inputs: &[&[u8]]) -> Result<Keccak256Hash, ProgramError> {
81    let mut result = [0u8; 32];
82
83    #[cfg(target_os = "solana")]
84    {
85        let count = inputs.len().min(16);
86        let mut params: [u64; 32] = [0; 32];
87        let mut i = 0;
88        while i < count {
89            params[i * 2] = inputs[i].as_ptr() as u64;
90            params[i * 2 + 1] = inputs[i].len() as u64;
91            i += 1;
92        }
93
94        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
95        let rc = unsafe {
96            crate::syscalls::sol_keccak256(
97                params.as_ptr() as *const u8,
98                count as u64,
99                result.as_mut_ptr(),
100            )
101        };
102        if rc != 0 {
103            return Err(ProgramError::InvalidArgument);
104        }
105    }
106    #[cfg(not(target_os = "solana"))]
107    {
108        let _ = inputs;
109    }
110
111    Ok(result)
112}
113
114/// Compute Keccak-256 over a single byte slice.
115#[inline]
116pub fn keccak256_single(input: &[u8]) -> Result<Keccak256Hash, ProgramError> {
117    keccak256(&[input])
118}