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}