Skip to main content

sails_reflect_hash/
lib.rs

1// This file is part of Gear.
2
3// Copyright (C) 2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Structural compile-time hashing for Sails types.
20//!
21//! This crate provides the `ReflectHash` trait which computes a deterministic,
22//! name-independent structural hash for types at compile time using const evaluation.
23//! This enables unique interface IDs for services based purely on their structure.
24//!
25//! # Deriving ReflectHash
26//!
27//! The easiest way to implement `ReflectHash` is to derive it:
28//!
29//! ```ignore
30//! use sails_reflect_hash::ReflectHash;
31//!
32//! #[derive(ReflectHash)]
33//! struct Transfer {
34//!     from: ActorId,
35//!     to: ActorId,
36//!     amount: u128,
37//! }
38//!
39//! #[derive(ReflectHash)]
40//! enum Event {
41//!     Transferred { from: ActorId, to: ActorId },
42//!     Approved(ActorId, u128),
43//!     Paused,
44//! }
45//! ```
46
47#![no_std]
48
49extern crate alloc;
50
51// Re-export the derive macro
52pub use sails_reflect_hash_derive::ReflectHash;
53
54// Re-export dependencies needed by the derive macro
55#[doc(hidden)]
56pub use keccak_const;
57
58use alloc::{collections::BTreeMap, string::String, vec::Vec};
59use core::num::{
60    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroU8, NonZeroU16, NonZeroU32,
61    NonZeroU64, NonZeroU128,
62};
63use gprimitives::{ActorId, CodeId, H160, H256, MessageId, NonZeroU256, U256};
64use keccak_const::Keccak256;
65
66/// Core trait for computing structural compile-time hashes.
67///
68/// Types implementing this trait can be hashed at compile time to produce
69/// a unique 32-byte identifier based solely on their structure, not their names.
70///
71/// # Deriving
72///
73/// Most types should use `#[derive(ReflectHash)]` which automatically generates
74/// the correct implementation based on the type's structure.
75pub trait ReflectHash {
76    /// The 256-bit structural hash of this type, computed at compile time.
77    const HASH: [u8; 32];
78}
79
80macro_rules! impl_reflect_hash_for_primitives {
81    ($($t:ty => $discriminant:literal),* $(,)?) => {
82        $(
83            impl ReflectHash for $t {
84                const HASH: [u8; 32] = Keccak256::new()
85                    .update($discriminant)
86                    .finalize();
87            }
88        )*
89    };
90}
91
92// Note: str has the hash for "String" since they represent
93// the same logical type in a structural interface and in IDL.
94impl_reflect_hash_for_primitives! {
95    u8 => b"u8",
96    u16 => b"u16",
97    u32 => b"u32",
98    u64 => b"u64",
99    u128 => b"u128",
100    i8 => b"i8",
101    i16 => b"i16",
102    i32 => b"i32",
103    i64 => b"i64",
104    i128 => b"i128",
105    bool => b"bool",
106    char => b"char",
107    str => b"String",
108    String => b"String",
109}
110
111impl ReflectHash for NonZeroU8 {
112    const HASH: [u8; 32] = Keccak256::new()
113        .update(b"NonZeroU8")
114        .update(&<u8 as ReflectHash>::HASH)
115        .finalize();
116}
117
118impl ReflectHash for NonZeroU16 {
119    const HASH: [u8; 32] = Keccak256::new()
120        .update(b"NonZeroU16")
121        .update(&<u16 as ReflectHash>::HASH)
122        .finalize();
123}
124
125impl ReflectHash for NonZeroU32 {
126    const HASH: [u8; 32] = Keccak256::new()
127        .update(b"NonZeroU32")
128        .update(&<u32 as ReflectHash>::HASH)
129        .finalize();
130}
131
132impl ReflectHash for NonZeroU64 {
133    const HASH: [u8; 32] = Keccak256::new()
134        .update(b"NonZeroU64")
135        .update(&<u64 as ReflectHash>::HASH)
136        .finalize();
137}
138
139impl ReflectHash for NonZeroU128 {
140    const HASH: [u8; 32] = Keccak256::new()
141        .update(b"NonZeroU128")
142        .update(&<u128 as ReflectHash>::HASH)
143        .finalize();
144}
145
146impl ReflectHash for NonZeroI8 {
147    const HASH: [u8; 32] = Keccak256::new()
148        .update(b"NonZeroI8")
149        .update(&<i8 as ReflectHash>::HASH)
150        .finalize();
151}
152
153impl ReflectHash for NonZeroI16 {
154    const HASH: [u8; 32] = Keccak256::new()
155        .update(b"NonZeroI16")
156        .update(&<i16 as ReflectHash>::HASH)
157        .finalize();
158}
159
160impl ReflectHash for NonZeroI32 {
161    const HASH: [u8; 32] = Keccak256::new()
162        .update(b"NonZeroI32")
163        .update(&<i32 as ReflectHash>::HASH)
164        .finalize();
165}
166
167impl ReflectHash for NonZeroI64 {
168    const HASH: [u8; 32] = Keccak256::new()
169        .update(b"NonZeroI64")
170        .update(&<i64 as ReflectHash>::HASH)
171        .finalize();
172}
173
174impl ReflectHash for NonZeroI128 {
175    const HASH: [u8; 32] = Keccak256::new()
176        .update(b"NonZeroI128")
177        .update(&<i128 as ReflectHash>::HASH)
178        .finalize();
179}
180
181// [T] (slice) has same data structure as Vec<T>
182impl<T: ReflectHash> ReflectHash for [T] {
183    const HASH: [u8; 32] = {
184        Keccak256::new()
185            .update(b"[")
186            .update(&T::HASH)
187            .update(b"]")
188            .finalize()
189    };
190}
191
192// Immutable references have the same hash as the referent
193// (structural equivalence: &T ≡ T in interface terms)
194impl<T: ReflectHash + ?Sized> ReflectHash for &T {
195    const HASH: [u8; 32] = T::HASH;
196}
197
198// Mutable references have the same hash as the referent
199// (structural equivalence: &mut T ≡ T in interface terms)
200impl<T: ReflectHash + ?Sized> ReflectHash for &mut T {
201    const HASH: [u8; 32] = T::HASH;
202}
203
204impl ReflectHash for () {
205    const HASH: [u8; 32] = Keccak256::new().update(b"()").finalize();
206}
207
208impl<T: ReflectHash> ReflectHash for Option<T> {
209    const HASH: [u8; 32] = {
210        Keccak256::new()
211            .update(b"Option")
212            .update(&T::HASH)
213            .finalize()
214    };
215}
216
217impl<T: ReflectHash, E: ReflectHash> ReflectHash for Result<T, E> {
218    const HASH: [u8; 32] = {
219        Keccak256::new()
220            .update(b"Result")
221            .update(&T::HASH)
222            .update(&E::HASH)
223            .finalize()
224    };
225}
226
227macro_rules! impl_reflect_hash_for_tuples {
228    () => {};
229    ($first:ident $(, $rest:ident)*) => {
230        impl<$first: ReflectHash, $($rest: ReflectHash),*> ReflectHash for ($first, $($rest),*) {
231            const HASH: [u8; 32] = {
232                Keccak256::new()
233                    .update(&$first::HASH)
234                    $(
235                        .update(&$rest::HASH)
236                    )*
237                    .finalize()
238            };
239        }
240        impl_reflect_hash_for_tuples!($($rest),*);
241    };
242}
243
244// Implement ReflectHash for tuples up to 12 elements
245impl_reflect_hash_for_tuples!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
246
247macro_rules! impl_reflect_hash_for_bytes_arrays {
248    ($($n:expr),* $(,)?) => {
249        $(
250            impl<T: ReflectHash> ReflectHash for [T; $n] {
251                const HASH: [u8; 32] = {
252                    let n_str = stringify!($n);
253                    Keccak256::new()
254                        .update(&T::HASH)
255                        .update(n_str.as_bytes())
256                        .finalize()
257                };
258            }
259        )*
260    };
261}
262
263// Implement ReflectHash for arrays up to size 32
264impl_reflect_hash_for_bytes_arrays!(
265    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
266    26, 27, 28, 29, 30, 31, 32,
267);
268
269impl ReflectHash for ActorId {
270    const HASH: [u8; 32] = Keccak256::new().update(b"ActorId").finalize();
271}
272
273impl ReflectHash for MessageId {
274    const HASH: [u8; 32] = Keccak256::new().update(b"MessageId").finalize();
275}
276
277impl ReflectHash for CodeId {
278    const HASH: [u8; 32] = Keccak256::new().update(b"CodeId").finalize();
279}
280
281impl ReflectHash for H256 {
282    const HASH: [u8; 32] = Keccak256::new().update(b"H256").finalize();
283}
284
285impl ReflectHash for H160 {
286    const HASH: [u8; 32] = Keccak256::new().update(b"H160").finalize();
287}
288
289#[cfg(feature = "alloy-primitives")]
290impl ReflectHash for alloy_primitives::Address {
291    const HASH: [u8; 32] = H160::HASH;
292}
293
294#[cfg(feature = "alloy-primitives")]
295impl ReflectHash for alloy_primitives::B256 {
296    const HASH: [u8; 32] = H256::HASH;
297}
298
299impl ReflectHash for U256 {
300    const HASH: [u8; 32] = Keccak256::new().update(b"U256").finalize();
301}
302
303impl ReflectHash for NonZeroU256 {
304    const HASH: [u8; 32] = Keccak256::new()
305        .update(b"NonZeroU256")
306        .update(&U256::HASH)
307        .finalize();
308}
309
310impl<T: ReflectHash> ReflectHash for Vec<T> {
311    const HASH: [u8; 32] = {
312        Keccak256::new()
313            .update(b"[")
314            .update(&T::HASH)
315            .update(b"]")
316            .finalize()
317    };
318}
319
320impl<K: ReflectHash, V: ReflectHash> ReflectHash for BTreeMap<K, V> {
321    const HASH: [u8; 32] = {
322        Keccak256::new()
323            .update(b"[")
324            .update(&<(K, V) as ReflectHash>::HASH)
325            .update(b"]")
326            .finalize()
327    };
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333
334    #[test]
335    fn same_hash_types() {
336        assert_eq!(str::HASH, String::HASH);
337        assert_eq!(<[u8] as ReflectHash>::HASH, Vec::<u8>::HASH);
338        assert_eq!(<&u32 as ReflectHash>::HASH, u32::HASH);
339        assert_eq!(<&mut u32 as ReflectHash>::HASH, u32::HASH);
340        assert_eq!(<&'static str as ReflectHash>::HASH, str::HASH);
341    }
342
343    // Test it builds and works
344    #[test]
345    fn crate_paths() {
346        use crate as reflect_hash_crate;
347
348        #[derive(ReflectHash)]
349        #[reflect_hash(crate = reflect_hash_crate)]
350        #[allow(dead_code)]
351        struct TestStruct(String);
352    }
353
354    #[cfg(feature = "alloy-primitives")]
355    #[test]
356    fn alloy_primitives_reuse_h160_h256_hashes() {
357        assert_eq!(
358            <alloy_primitives::Address as ReflectHash>::HASH,
359            <H160 as ReflectHash>::HASH,
360        );
361        assert_eq!(
362            <alloy_primitives::B256 as ReflectHash>::HASH,
363            <H256 as ReflectHash>::HASH,
364        );
365    }
366}