Skip to main content

snap_coin/crypto/
mod.rs

1use bincode::{Decode, Encode};
2use ed25519_dalek::SigningKey;
3use ed25519_dalek::ed25519::Error;
4use ed25519_dalek::ed25519::signature::SignerMut;
5use ed25519_dalek::{Signature as DalekSignature, VerifyingKey};
6use num_bigint::BigUint;
7use randomx_rs::{RandomXCache, RandomXDataset, RandomXFlag, RandomXVM};
8use serde::{Deserialize, Serialize};
9use std::cell::RefCell;
10use std::fmt;
11use std::ops::Deref;
12use std::sync::OnceLock;
13use std::sync::atomic::{AtomicBool, Ordering};
14
15use keys::{Private, Public};
16
17/// Public / Private key logic
18pub mod keys;
19
20/// Merkle tree
21pub mod merkle_tree;
22
23/// Address inclusion filter
24pub mod address_inclusion_filter;
25
26pub const RANDOMX_SEED: &[u8] = b"snap-coin-testnet";
27
28/// Wrapper for the dataset to assert Sync manually
29#[derive(Clone)]
30struct SharedDataset(RandomXDataset);
31
32// SAFETY: RandomXDataset is immutable after creation, safe for concurrent reads
33unsafe impl Sync for SharedDataset {}
34unsafe impl Send for SharedDataset {}
35
36static DATASET: OnceLock<SharedDataset> = OnceLock::new();
37static IS_LIGHT_MODE: AtomicBool = AtomicBool::new(true);
38
39/// This can only be called at the beginning of a program to be effective (before all Hash::new() or Hash::compare_with_data() calls to work)
40/// Enables full memory mode, substantially increasing hash rate, by allocating a 2GB scratch pad for hashing
41pub fn randomx_use_full_mode() {
42    IS_LIGHT_MODE.store(false, Ordering::SeqCst);
43}
44
45/// Returns a reference to the shared dataset
46fn get_dataset() -> RandomXDataset {
47    let dataset = DATASET.get_or_init(|| {
48        println!("Creating RandomX dataset...");
49        let flags = RandomXFlag::FLAG_FULL_MEM | RandomXFlag::FLAG_JIT;
50
51        let cache = RandomXCache::new(flags, RANDOMX_SEED).expect("Failed to create RandomX cache");
52
53        let dataset =
54            RandomXDataset::new(flags, cache, 0).expect("Failed to create RandomX dataset");
55
56        let shared_dataset = SharedDataset(dataset);
57        println!("RandomX dataset created!");
58        shared_dataset
59    });
60    dataset.clone().0
61}
62
63// Thread-local VM
64thread_local! {
65    static THREAD_VM: RefCell<RandomXVM> = RefCell::new({
66        if IS_LIGHT_MODE.load(Ordering::SeqCst) {
67            let flags = RandomXFlag::FLAG_JIT;
68            let cache = RandomXCache::new(flags, RANDOMX_SEED).expect("Failed to create RandomX cache (light mode)");
69            RandomXVM::new(flags, Some(cache), None)
70                .expect("Failed to create RandomX VM (light mode)")
71        } else {
72            let flags = RandomXFlag::FLAG_FULL_MEM | RandomXFlag::FLAG_JIT;
73            let dataset = get_dataset();
74            RandomXVM::new(flags, None, Some(dataset.clone()))
75                .expect("Failed to create RandomX VM (full mode)")
76        }
77    });
78}
79
80pub fn randomx_hash(input: &[u8]) -> [u8; 32] {
81    THREAD_VM.with(|vm_cell| {
82        let vm = vm_cell.borrow_mut();
83        let hash_vec = vm.calculate_hash(input).expect("RandomX hashing failed");
84        hash_vec.try_into().expect("Hash must be 32 bytes")
85    })
86}
87
88/// Store and hash Argon2 hashes (compare too)
89/// When used in in hashmaps, the already hashed argon 2 digest gets re-hashed, for speed
90#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, std::hash::Hash)]
91pub struct Hash([u8; 32]);
92
93impl Hash {
94    /// Create a new hash by hashing some data
95    /// WARNING: SLOW
96    pub fn new(data: &[u8]) -> Self {
97        Hash(randomx_hash(data))
98    }
99
100    /// Create a new hash with a buffer of an already existing hash
101    pub const fn new_from_buf(hash_buf: [u8; 32]) -> Self {
102        Hash(hash_buf)
103    }
104
105    /// Compare this hash with some data (check if the data hash is the same)
106    pub fn compare_with_data(&self, other_data: &[u8]) -> bool {
107        let computed = randomx_hash(other_data);
108        computed == self.0
109    }
110
111    /// Create a new hash from a already existing hash encoded in a base36 string
112    pub fn new_from_base36(s: &str) -> Option<Self> {
113        // Convert base36 string to bytes
114        let big_int = BigUint::parse_bytes(s.as_bytes(), 36)?;
115        let mut buf = big_int.to_bytes_be();
116
117        // Ensure the buffer is exactly 32 bytes
118        if buf.len() > 32 {
119            return None;
120        } else if buf.len() < 32 {
121            // Pad with zeros at the front
122            let mut padded = vec![0u8; 32 - buf.len()];
123            padded.extend(buf);
124            buf = padded;
125        }
126
127        // Convert Vec<u8> to [u8; 32]
128        let buf: [u8; 32] = buf.try_into().ok()?;
129
130        Some(Hash(buf))
131    }
132
133    pub fn dump_base36(&self) -> String {
134        let big_int = BigUint::from_bytes_be(&self.0);
135        big_int.to_str_radix(36)
136    }
137
138    pub fn dump_buf(&self) -> [u8; 32] {
139        self.0
140    }
141}
142
143impl Serialize for Hash {
144    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
145    where
146        S: serde::Serializer,
147    {
148        serializer.serialize_str(&self.dump_base36())
149    }
150}
151
152impl<'de> Deserialize<'de> for Hash {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: serde::Deserializer<'de>,
156    {
157        let s = String::deserialize(deserializer)?;
158        Self::new_from_base36(&s).ok_or_else(|| serde::de::Error::custom("Invalid base36 hash"))
159    }
160}
161
162impl Deref for Hash {
163    type Target = [u8; 32];
164    fn deref(&self) -> &Self::Target {
165        &self.0
166    }
167}
168
169impl fmt::Debug for Hash {
170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171        write!(f, "Hash: {}", self.dump_base36())
172    }
173}
174
175/// Store sign and verify ed25519
176#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, Hash)]
177pub struct Signature([u8; 64]);
178
179impl Signature {
180    /// Create a signature by signing some data
181    pub fn new_signature(private: &mut Private, data: &[u8]) -> Self {
182        let mut key = SigningKey::from_bytes(private.dump_buf());
183        let signature = key.sign(data);
184        Signature(signature.to_bytes()) // [u8; 64]
185    }
186
187    /// Create a signature with the raw signature bytes (no signing)
188    pub fn new_from_buf(signature: &[u8; 64]) -> Self {
189        Signature(signature.clone())
190    }
191
192    /// Validate a signature and return true if valid
193    pub fn validate_with_public(&self, public: &Public, data: &[u8]) -> Result<bool, Error> {
194        let key = VerifyingKey::from_bytes(public.dump_buf())?;
195        Ok(key
196            .verify_strict(data, &DalekSignature::from_bytes(&self.0))
197            .is_ok())
198    }
199
200    /// Validate a signature and return true if valid
201    pub fn validate_with_private(&self, private: &Private, data: &[u8]) -> Result<bool, Error> {
202        let key = SigningKey::from_bytes(private.dump_buf());
203        Ok(key
204            .verify_strict(data, &DalekSignature::from_bytes(&self.0))
205            .is_ok())
206    }
207
208    /// Create a signature from a base 36 string
209    pub fn new_from_base36(s: &str) -> Option<Self> {
210        // Convert base36 string to bytes
211        let big_int = BigUint::parse_bytes(s.as_bytes(), 36)?;
212        let mut buf = big_int.to_bytes_be();
213
214        // Ensure the buffer is exactly 64 bytes
215        if buf.len() > 64 {
216            return None;
217        } else if buf.len() < 64 {
218            // Pad with zeros at the front
219            let mut padded = vec![0u8; 64 - buf.len()];
220            padded.extend(buf);
221            buf = padded;
222        }
223
224        // Convert Vec<u8> to [u8; 64]
225        let buf: [u8; 64] = buf.try_into().ok()?;
226
227        Some(Self(buf))
228    }
229
230    pub fn dump_base36(&self) -> String {
231        let big_int = BigUint::from_bytes_be(&self.0);
232        big_int.to_str_radix(36)
233    }
234
235    pub fn dump_buf(&self) -> [u8; 64] {
236        self.0
237    }
238}
239
240impl Serialize for Signature {
241    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242    where
243        S: serde::Serializer,
244    {
245        serializer.serialize_str(&self.dump_base36())
246    }
247}
248
249impl<'de> Deserialize<'de> for Signature {
250    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
251    where
252        D: serde::Deserializer<'de>,
253    {
254        let s = String::deserialize(deserializer)?;
255        Self::new_from_base36(&s)
256            .ok_or_else(|| serde::de::Error::custom("Invalid base36 signature"))
257    }
258}
259
260impl Deref for Signature {
261    type Target = [u8; 64]; // match the array size
262    fn deref(&self) -> &Self::Target {
263        &self.0
264    }
265}
266
267impl fmt::Debug for Signature {
268    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269        write!(f, "Signature: {}", self.dump_base36())
270    }
271}