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 =
52            RandomXCache::new(flags, RANDOMX_SEED).expect("Failed to create RandomX cache");
53
54        let dataset =
55            RandomXDataset::new(flags, cache, 0).expect("Failed to create RandomX dataset");
56
57        let shared_dataset = SharedDataset(dataset);
58        println!("RandomX dataset created!");
59        shared_dataset
60    });
61    dataset.clone().0
62}
63
64// Thread-local VM
65thread_local! {
66    static THREAD_VM: RefCell<RandomXVM> = RefCell::new({
67        if IS_LIGHT_MODE.load(Ordering::SeqCst) {
68            let flags = RandomXFlag::FLAG_JIT;
69            let cache = RandomXCache::new(flags, RANDOMX_SEED).expect("Failed to create RandomX cache (light mode)");
70            RandomXVM::new(flags, Some(cache), None)
71                .expect("Failed to create RandomX VM (light mode)")
72        } else {
73            let flags = RandomXFlag::FLAG_FULL_MEM | RandomXFlag::FLAG_JIT;
74            let dataset = get_dataset();
75            RandomXVM::new(flags, None, Some(dataset.clone()))
76                .expect("Failed to create RandomX VM (full mode)")
77        }
78    });
79}
80
81pub fn randomx_hash(input: &[u8]) -> [u8; 32] {
82    THREAD_VM.with(|vm_cell| {
83        let vm = vm_cell.borrow_mut();
84        let hash_vec = vm.calculate_hash(input).expect("RandomX hashing failed");
85        hash_vec.try_into().expect("Hash must be 32 bytes")
86    })
87}
88
89/// Store and hash Argon2 hashes (compare too)
90/// When used in in hashmaps, the already hashed argon 2 digest gets re-hashed, for speed
91#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, std::hash::Hash)]
92pub struct Hash([u8; 32]);
93
94impl Hash {
95    /// Create a new hash by hashing some data
96    /// WARNING: SLOW
97    pub fn new(data: &[u8]) -> Self {
98        Hash(randomx_hash(data))
99    }
100
101    /// Create a new hash with a buffer of an already existing hash
102    pub const fn new_from_buf(hash_buf: [u8; 32]) -> Self {
103        Hash(hash_buf)
104    }
105
106    /// Compare this hash with some data (check if the data hash is the same)
107    pub fn compare_with_data(&self, other_data: &[u8]) -> bool {
108        let computed = randomx_hash(other_data);
109        computed == self.0
110    }
111
112    /// Create a new hash from a already existing hash encoded in a base36 string
113    pub fn new_from_base36(s: &str) -> Option<Self> {
114        // Convert base36 string to bytes
115        let big_int = BigUint::parse_bytes(s.as_bytes(), 36)?;
116        let mut buf = big_int.to_bytes_be();
117
118        // Ensure the buffer is exactly 32 bytes
119        if buf.len() > 32 {
120            return None;
121        } else if buf.len() < 32 {
122            // Pad with zeros at the front
123            let mut padded = vec![0u8; 32 - buf.len()];
124            padded.extend(buf);
125            buf = padded;
126        }
127
128        // Convert Vec<u8> to [u8; 32]
129        let buf: [u8; 32] = buf.try_into().ok()?;
130
131        Some(Hash(buf))
132    }
133
134    pub fn dump_base36(&self) -> String {
135        let big_int = BigUint::from_bytes_be(&self.0);
136        big_int.to_str_radix(36)
137    }
138
139    pub fn dump_buf(&self) -> [u8; 32] {
140        self.0
141    }
142}
143
144impl Serialize for Hash {
145    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146    where
147        S: serde::Serializer,
148    {
149        serializer.serialize_str(&self.dump_base36())
150    }
151}
152
153impl<'de> Deserialize<'de> for Hash {
154    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155    where
156        D: serde::Deserializer<'de>,
157    {
158        let s = String::deserialize(deserializer)?;
159        Self::new_from_base36(&s).ok_or_else(|| serde::de::Error::custom("Invalid base36 hash"))
160    }
161}
162
163impl Deref for Hash {
164    type Target = [u8; 32];
165    fn deref(&self) -> &Self::Target {
166        &self.0
167    }
168}
169
170impl fmt::Debug for Hash {
171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172        write!(f, "Hash: {}", self.dump_base36())
173    }
174}
175
176/// Store sign and verify ed25519
177#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, Hash)]
178pub struct Signature([u8; 64]);
179
180impl Signature {
181    /// Create a signature by signing some data
182    pub fn new_signature(private: &mut Private, data: &[u8]) -> Self {
183        let mut key = SigningKey::from_bytes(private.dump_buf());
184        let signature = key.sign(data);
185        Signature(signature.to_bytes()) // [u8; 64]
186    }
187
188    /// Create a signature with the raw signature bytes (no signing)
189    pub fn new_from_buf(signature: &[u8; 64]) -> Self {
190        Signature(signature.clone())
191    }
192
193    /// Validate a signature and return true if valid
194    pub fn validate_with_public(&self, public: &Public, data: &[u8]) -> Result<bool, Error> {
195        let key = VerifyingKey::from_bytes(public.dump_buf())?;
196        Ok(key
197            .verify_strict(data, &DalekSignature::from_bytes(&self.0))
198            .is_ok())
199    }
200
201    /// Validate a signature and return true if valid
202    pub fn validate_with_private(&self, private: &Private, data: &[u8]) -> Result<bool, Error> {
203        let key = SigningKey::from_bytes(private.dump_buf());
204        Ok(key
205            .verify_strict(data, &DalekSignature::from_bytes(&self.0))
206            .is_ok())
207    }
208
209    /// Create a signature from a base 36 string
210    pub fn new_from_base36(s: &str) -> Option<Self> {
211        // Convert base36 string to bytes
212        let big_int = BigUint::parse_bytes(s.as_bytes(), 36)?;
213        let mut buf = big_int.to_bytes_be();
214
215        // Ensure the buffer is exactly 64 bytes
216        if buf.len() > 64 {
217            return None;
218        } else if buf.len() < 64 {
219            // Pad with zeros at the front
220            let mut padded = vec![0u8; 64 - buf.len()];
221            padded.extend(buf);
222            buf = padded;
223        }
224
225        // Convert Vec<u8> to [u8; 64]
226        let buf: [u8; 64] = buf.try_into().ok()?;
227
228        Some(Self(buf))
229    }
230
231    pub fn dump_base36(&self) -> String {
232        let big_int = BigUint::from_bytes_be(&self.0);
233        big_int.to_str_radix(36)
234    }
235
236    pub fn dump_buf(&self) -> [u8; 64] {
237        self.0
238    }
239}
240
241impl Serialize for Signature {
242    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
243    where
244        S: serde::Serializer,
245    {
246        serializer.serialize_str(&self.dump_base36())
247    }
248}
249
250impl<'de> Deserialize<'de> for Signature {
251    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252    where
253        D: serde::Deserializer<'de>,
254    {
255        let s = String::deserialize(deserializer)?;
256        Self::new_from_base36(&s)
257            .ok_or_else(|| serde::de::Error::custom("Invalid base36 signature"))
258    }
259}
260
261impl Deref for Signature {
262    type Target = [u8; 64]; // match the array size
263    fn deref(&self) -> &Self::Target {
264        &self.0
265    }
266}
267
268impl fmt::Debug for Signature {
269    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270        write!(f, "Signature: {}", self.dump_base36())
271    }
272}