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
289impl ReflectHash for U256 {
290    const HASH: [u8; 32] = Keccak256::new().update(b"U256").finalize();
291}
292
293impl ReflectHash for NonZeroU256 {
294    const HASH: [u8; 32] = Keccak256::new()
295        .update(b"NonZeroU256")
296        .update(&U256::HASH)
297        .finalize();
298}
299
300impl<T: ReflectHash> ReflectHash for Vec<T> {
301    const HASH: [u8; 32] = {
302        Keccak256::new()
303            .update(b"[")
304            .update(&T::HASH)
305            .update(b"]")
306            .finalize()
307    };
308}
309
310impl<K: ReflectHash, V: ReflectHash> ReflectHash for BTreeMap<K, V> {
311    const HASH: [u8; 32] = {
312        Keccak256::new()
313            .update(b"[")
314            .update(&<(K, V) as ReflectHash>::HASH)
315            .update(b"]")
316            .finalize()
317    };
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn same_hash_types() {
326        assert_eq!(str::HASH, String::HASH);
327        assert_eq!(<[u8] as ReflectHash>::HASH, Vec::<u8>::HASH);
328        assert_eq!(<&u32 as ReflectHash>::HASH, u32::HASH);
329        assert_eq!(<&mut u32 as ReflectHash>::HASH, u32::HASH);
330        assert_eq!(<&'static str as ReflectHash>::HASH, str::HASH);
331    }
332
333    // Test it builds and works
334    #[test]
335    fn crate_paths() {
336        use crate as reflect_hash_crate;
337
338        #[derive(ReflectHash)]
339        #[reflect_hash(crate = reflect_hash_crate)]
340        #[allow(dead_code)]
341        struct TestStruct(String);
342    }
343}