randomx_rs/
lib.rs

1// Copyright 2019. The Tari Project
2//
3// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4// following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7// disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10// following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13// products derived from this software without specific prior written permission.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23//! # RandomX
24//!
25//! The `randomx-rs` crate provides bindings to the RandomX proof-of-work (PoW) system.
26//!
27//! From the [RandomX github repo]:
28//!
29//! "RandomX is a proof-of-work (PoW) algorithm that is optimized for general-purpose CPUs. RandomX uses random code
30//! execution together with several memory-hard techniques to minimize the efficiency advantage of specialized
31//! hardware."
32//!
33//! Read more about how RandomX works in the [design document].
34//!
35//! [RandomX github repo]: <https://github.com/tevador/RandomX>
36//! [design document]: <https://github.com/tevador/RandomX/blob/master/doc/design.md>
37mod bindings;
38/// Test utilities for fuzzing
39pub mod test_utils;
40
41use std::{convert::TryFrom, num::TryFromIntError, ptr, sync::Arc};
42
43use bindings::{
44    randomx_alloc_cache,
45    randomx_alloc_dataset,
46    randomx_cache,
47    randomx_calculate_hash,
48    randomx_create_vm,
49    randomx_dataset,
50    randomx_dataset_item_count,
51    randomx_destroy_vm,
52    randomx_get_dataset_memory,
53    randomx_init_cache,
54    randomx_init_dataset,
55    randomx_release_cache,
56    randomx_release_dataset,
57    randomx_vm,
58    randomx_vm_set_cache,
59    randomx_vm_set_dataset,
60    RANDOMX_HASH_SIZE,
61};
62use bitflags::bitflags;
63use libc::{c_ulong, c_void};
64use thiserror::Error;
65
66use crate::bindings::{
67    randomx_calculate_hash_first,
68    randomx_calculate_hash_last,
69    randomx_calculate_hash_next,
70    randomx_get_flags,
71};
72
73bitflags! {
74    /// RandomX Flags are used to configure the library.
75    pub struct RandomXFlag: u32 {
76        /// No flags set. Works on all platforms, but is the slowest.
77        const FLAG_DEFAULT      = 0b0000_0000;
78        /// Allocate memory in large pages.
79        const FLAG_LARGE_PAGES  = 0b0000_0001;
80        /// Use hardware accelerated AES.
81        const FLAG_HARD_AES     = 0b0000_0010;
82        /// Use the full dataset.
83        const FLAG_FULL_MEM     = 0b0000_0100;
84        /// Use JIT compilation support.
85        const FLAG_JIT          = 0b0000_1000;
86        /// When combined with FLAG_JIT, the JIT pages are never writable and executable at the
87        /// same time.
88        const FLAG_SECURE       = 0b0001_0000;
89        /// Optimize Argon2 for CPUs with the SSSE3 instruction set.
90        const FLAG_ARGON2_SSSE3 = 0b0010_0000;
91        /// Optimize Argon2 for CPUs with the AVX2 instruction set.
92        const FLAG_ARGON2_AVX2  = 0b0100_0000;
93        /// Optimize Argon2 for CPUs without the AVX2 or SSSE3 instruction sets.
94        const FLAG_ARGON2       = 0b0110_0000;
95    }
96}
97
98impl RandomXFlag {
99    /// Returns the recommended flags to be used.
100    ///
101    /// Does not include:
102    /// * FLAG_LARGE_PAGES
103    /// * FLAG_FULL_MEM
104    /// * FLAG_SECURE
105    ///
106    /// The above flags need to be set manually, if required.
107    pub fn get_recommended_flags() -> RandomXFlag {
108        RandomXFlag {
109            bits: unsafe { randomx_get_flags() },
110        }
111    }
112}
113
114impl Default for RandomXFlag {
115    /// Default value for RandomXFlag
116    fn default() -> RandomXFlag {
117        RandomXFlag::FLAG_DEFAULT
118    }
119}
120
121#[derive(Debug, Clone, Error)]
122/// This enum specifies the possible errors that may occur.
123pub enum RandomXError {
124    #[error("Problem creating the RandomX object: {0}")]
125    CreationError(String),
126    #[error("Problem with configuration flags: {0}")]
127    FlagConfigError(String),
128    #[error("Problem with parameters supplied: {0}")]
129    ParameterError(String),
130    #[error("Failed to convert Int to usize")]
131    TryFromIntError(#[from] TryFromIntError),
132    #[error("Unknown problem running RandomX: {0}")]
133    Other(String),
134}
135
136#[derive(Debug)]
137struct RandomXCacheInner {
138    cache_ptr: *mut randomx_cache,
139}
140
141unsafe impl Send for RandomXCacheInner {}
142unsafe impl Sync for RandomXCacheInner {}
143
144impl Drop for RandomXCacheInner {
145    /// De-allocates memory for the `cache` object
146    fn drop(&mut self) {
147        unsafe {
148            randomx_release_cache(self.cache_ptr);
149        }
150    }
151}
152
153#[derive(Debug, Clone)]
154/// The Cache is used for light verification and Dataset construction.
155pub struct RandomXCache {
156    inner: Arc<RandomXCacheInner>,
157}
158
159impl RandomXCache {
160    /// Creates and alllcates memory for a new cache object, and initializes it with
161    /// the key value.
162    ///
163    /// `flags` is any combination of the following two flags:
164    /// * FLAG_LARGE_PAGES
165    /// * FLAG_JIT
166    ///
167    /// and (optionally) one of the following flags (depending on instruction set supported):
168    /// * FLAG_ARGON2_SSSE3
169    /// * FLAG_ARGON2_AVX2
170    ///
171    /// `key` is a sequence of u8 used to initialize SuperScalarHash.
172    pub fn new(flags: RandomXFlag, key: &[u8]) -> Result<RandomXCache, RandomXError> {
173        if key.is_empty() {
174            Err(RandomXError::ParameterError("key is empty".to_string()))
175        } else {
176            let cache_ptr = unsafe { randomx_alloc_cache(flags.bits) };
177            if cache_ptr.is_null() {
178                Err(RandomXError::CreationError("Could not allocate cache".to_string()))
179            } else {
180                let inner = RandomXCacheInner { cache_ptr };
181                let result = RandomXCache { inner: Arc::new(inner) };
182                let key_ptr = key.as_ptr() as *mut c_void;
183                let key_size = key.len();
184                unsafe {
185                    randomx_init_cache(result.inner.cache_ptr, key_ptr, key_size);
186                }
187                Ok(result)
188            }
189        }
190    }
191}
192
193#[derive(Debug)]
194struct RandomXDatasetInner {
195    dataset_ptr: *mut randomx_dataset,
196    dataset_count: u32,
197    #[allow(dead_code)]
198    cache: RandomXCache,
199}
200
201unsafe impl Send for RandomXDatasetInner {}
202unsafe impl Sync for RandomXDatasetInner {}
203
204impl Drop for RandomXDatasetInner {
205    /// De-allocates memory for the `dataset` object.
206    fn drop(&mut self) {
207        unsafe {
208            randomx_release_dataset(self.dataset_ptr);
209        }
210    }
211}
212
213#[derive(Debug, Clone)]
214/// The Dataset is a read-only memory structure that is used during VM program execution.
215pub struct RandomXDataset {
216    inner: Arc<RandomXDatasetInner>,
217}
218
219impl RandomXDataset {
220    /// Creates a new dataset object, allocates memory to the `dataset` object and initializes it.
221    ///
222    /// `flags` is one of the following:
223    /// * FLAG_DEFAULT
224    /// * FLAG_LARGE_PAGES
225    ///
226    /// `cache` is a cache object.
227    ///
228    /// `start` is the item number where initialization should start, recommended to pass in 0.
229    // Conversions may be lossy on Windows or Linux
230    #[allow(clippy::useless_conversion)]
231    pub fn new(flags: RandomXFlag, cache: RandomXCache, start: u32) -> Result<RandomXDataset, RandomXError> {
232        let item_count = RandomXDataset::count()
233            .map_err(|e| RandomXError::CreationError(format!("Could not get dataset count: {e:?}")))?;
234
235        let test = unsafe { randomx_alloc_dataset(flags.bits) };
236        if test.is_null() {
237            Err(RandomXError::CreationError("Could not allocate dataset".to_string()))
238        } else {
239            let inner = RandomXDatasetInner {
240                dataset_ptr: test,
241                dataset_count: item_count,
242                cache,
243            };
244            let result = RandomXDataset { inner: Arc::new(inner) };
245
246            if start < item_count {
247                unsafe {
248                    randomx_init_dataset(
249                        result.inner.dataset_ptr,
250                        result.inner.cache.inner.cache_ptr,
251                        c_ulong::from(start),
252                        c_ulong::from(item_count),
253                    );
254                }
255                Ok(result)
256            } else {
257                Err(RandomXError::CreationError(format!(
258                    "start must be less than item_count: start: {start}, item_count: {item_count}",
259                )))
260            }
261        }
262    }
263
264    /// Returns the number of items in the `dataset` or an error on failure.
265    pub fn count() -> Result<u32, RandomXError> {
266        match unsafe { randomx_dataset_item_count() } {
267            0 => Err(RandomXError::Other("Dataset item count was 0".to_string())),
268            x => {
269                // This weirdness brought to you by c_ulong being different on Windows and Linux
270                #[cfg(target_os = "windows")]
271                return Ok(x);
272                #[cfg(not(target_os = "windows"))]
273                return Ok(u32::try_from(x)?);
274            },
275        }
276    }
277
278    /// Returns the values of the internal memory buffer of the `dataset` or an error on failure.
279    pub fn get_data(&self) -> Result<Vec<u8>, RandomXError> {
280        let memory = unsafe { randomx_get_dataset_memory(self.inner.dataset_ptr) };
281        if memory.is_null() {
282            Err(RandomXError::Other("Could not get dataset memory".into()))
283        } else {
284            let count = usize::try_from(self.inner.dataset_count)?;
285            let mut result: Vec<u8> = vec![0u8; count];
286            let n = usize::try_from(self.inner.dataset_count)?;
287            unsafe {
288                libc::memcpy(result.as_mut_ptr() as *mut c_void, memory, n);
289            }
290            Ok(result)
291        }
292    }
293}
294
295#[derive(Debug)]
296/// The RandomX Virtual Machine (VM) is a complex instruction set computer that executes generated programs.
297pub struct RandomXVM {
298    flags: RandomXFlag,
299    vm: *mut randomx_vm,
300    linked_cache: Option<RandomXCache>,
301    linked_dataset: Option<RandomXDataset>,
302}
303
304unsafe impl Send for RandomXVM {}
305
306impl Drop for RandomXVM {
307    /// De-allocates memory for the `VM` object.
308    fn drop(&mut self) {
309        unsafe {
310            randomx_destroy_vm(self.vm);
311        }
312    }
313}
314
315impl RandomXVM {
316    /// Creates a new `VM` and initializes it, error on failure.
317    ///
318    /// `flags` is any combination of the following 5 flags:
319    /// * FLAG_LARGE_PAGES
320    /// * FLAG_HARD_AES
321    /// * FLAG_FULL_MEM
322    /// * FLAG_JIT
323    /// * FLAG_SECURE
324    ///
325    /// Or
326    ///
327    /// * FLAG_DEFAULT
328    ///
329    /// `cache` is a cache object, optional if FLAG_FULL_MEM is set.
330    ///
331    /// `dataset` is a dataset object, optional if FLAG_FULL_MEM is not set.
332    pub fn new(
333        flags: RandomXFlag,
334        cache: Option<RandomXCache>,
335        dataset: Option<RandomXDataset>,
336    ) -> Result<RandomXVM, RandomXError> {
337        let is_full_mem = flags.contains(RandomXFlag::FLAG_FULL_MEM);
338        match (cache, dataset) {
339            (None, None) => Err(RandomXError::CreationError("Failed to allocate VM".to_string())),
340            (None, _) if !is_full_mem => Err(RandomXError::FlagConfigError(
341                "No cache and FLAG_FULL_MEM not set".to_string(),
342            )),
343            (_, None) if is_full_mem => Err(RandomXError::FlagConfigError(
344                "No dataset and FLAG_FULL_MEM set".to_string(),
345            )),
346            (cache, dataset) => {
347                let cache_ptr = cache
348                    .as_ref()
349                    .map(|stash| stash.inner.cache_ptr)
350                    .unwrap_or_else(ptr::null_mut);
351                let dataset_ptr = dataset
352                    .as_ref()
353                    .map(|data| data.inner.dataset_ptr)
354                    .unwrap_or_else(ptr::null_mut);
355                let vm = unsafe { randomx_create_vm(flags.bits, cache_ptr, dataset_ptr) };
356                Ok(RandomXVM {
357                    vm,
358                    flags,
359                    linked_cache: cache,
360                    linked_dataset: dataset,
361                })
362            },
363        }
364    }
365
366    /// Re-initializes the `VM` with a new cache that was initialised without
367    /// RandomXFlag::FLAG_FULL_MEM.
368    pub fn reinit_cache(&mut self, cache: RandomXCache) -> Result<(), RandomXError> {
369        if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
370            Err(RandomXError::FlagConfigError(
371                "Cannot reinit cache with FLAG_FULL_MEM set".to_string(),
372            ))
373        } else {
374            unsafe {
375                randomx_vm_set_cache(self.vm, cache.inner.cache_ptr);
376            }
377            self.linked_cache = Some(cache);
378            Ok(())
379        }
380    }
381
382    /// Re-initializes the `VM` with a new dataset that was initialised with
383    /// RandomXFlag::FLAG_FULL_MEM.
384    pub fn reinit_dataset(&mut self, dataset: RandomXDataset) -> Result<(), RandomXError> {
385        if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
386            unsafe {
387                randomx_vm_set_dataset(self.vm, dataset.inner.dataset_ptr);
388            }
389            self.linked_dataset = Some(dataset);
390            Ok(())
391        } else {
392            Err(RandomXError::FlagConfigError(
393                "Cannot reinit dataset without FLAG_FULL_MEM set".to_string(),
394            ))
395        }
396    }
397
398    /// Calculates a RandomX hash value and returns it, error on failure.
399    ///
400    /// `input` is a sequence of u8 to be hashed.
401    pub fn calculate_hash(&self, input: &[u8]) -> Result<Vec<u8>, RandomXError> {
402        if input.is_empty() {
403            Err(RandomXError::ParameterError("input was empty".to_string()))
404        } else {
405            let size_input = input.len();
406            let input_ptr = input.as_ptr() as *mut c_void;
407            let arr = [0; RANDOMX_HASH_SIZE as usize];
408            let output_ptr = arr.as_ptr() as *mut c_void;
409            unsafe {
410                randomx_calculate_hash(self.vm, input_ptr, size_input, output_ptr);
411            }
412            // if this failed, arr should still be empty
413            if arr == [0; RANDOMX_HASH_SIZE as usize] {
414                Err(RandomXError::Other("RandomX calculated hash was empty".to_string()))
415            } else {
416                let result = arr.to_vec();
417                Ok(result)
418            }
419        }
420    }
421
422    /// Calculates hashes from a set of inputs.
423    ///
424    /// `input` is an array of a sequence of u8 to be hashed.
425    #[allow(clippy::needless_range_loop)] // Range loop is not only for indexing `input`
426    pub fn calculate_hash_set(&self, input: &[&[u8]]) -> Result<Vec<Vec<u8>>, RandomXError> {
427        if input.is_empty() {
428            // Empty set
429            return Err(RandomXError::ParameterError("input was empty".to_string()));
430        }
431
432        let mut result = Vec::new();
433        // For single input
434        if input.len() == 1 {
435            let hash = self.calculate_hash(input[0])?;
436            result.push(hash);
437            return Ok(result);
438        }
439
440        // For multiple inputs
441        let mut output_ptr: *mut c_void = ptr::null_mut();
442        let arr = [0; RANDOMX_HASH_SIZE as usize];
443
444        // Not len() as last iteration assigns final hash
445        let iterations = input.len() + 1;
446        for i in 0..iterations {
447            if i == iterations - 1 {
448                // For last iteration
449                unsafe {
450                    randomx_calculate_hash_last(self.vm, output_ptr);
451                }
452            } else {
453                if input[i].is_empty() {
454                    // Stop calculations
455                    if arr != [0; RANDOMX_HASH_SIZE as usize] {
456                        // Complete what was started
457                        unsafe {
458                            randomx_calculate_hash_last(self.vm, output_ptr);
459                        }
460                    }
461                    return Err(RandomXError::ParameterError("input was empty".to_string()));
462                };
463                let size_input = input[i].len();
464                let input_ptr = input[i].as_ptr() as *mut c_void;
465                output_ptr = arr.as_ptr() as *mut c_void;
466                if i == 0 {
467                    // For first iteration
468                    unsafe {
469                        randomx_calculate_hash_first(self.vm, input_ptr, size_input);
470                    }
471                } else {
472                    unsafe {
473                        // For every other iteration
474                        randomx_calculate_hash_next(self.vm, input_ptr, size_input, output_ptr);
475                    }
476                }
477            }
478
479            if i != 0 {
480                // First hash is only available in 2nd iteration
481                if arr == [0; RANDOMX_HASH_SIZE as usize] {
482                    return Err(RandomXError::Other("RandomX hash was zero".to_string()));
483                }
484                let output: Vec<u8> = arr.to_vec();
485                result.push(output);
486            }
487        }
488        Ok(result)
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use std::{ptr, sync::Arc};
495
496    use crate::{RandomXCache, RandomXCacheInner, RandomXDataset, RandomXDatasetInner, RandomXFlag, RandomXVM};
497
498    #[test]
499    fn lib_alloc_cache() {
500        let flags = RandomXFlag::default();
501        let key = "Key";
502        let cache = RandomXCache::new(flags, key.as_bytes()).expect("Failed to allocate cache");
503        drop(cache);
504    }
505
506    #[test]
507    fn lib_alloc_dataset() {
508        let flags = RandomXFlag::default();
509        let key = "Key";
510        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
511        let dataset = RandomXDataset::new(flags, cache.clone(), 0).expect("Failed to allocate dataset");
512        drop(dataset);
513        drop(cache);
514    }
515
516    #[test]
517    fn lib_alloc_vm() {
518        let flags = RandomXFlag::default();
519        let key = "Key";
520        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
521        let mut vm = RandomXVM::new(flags, Some(cache.clone()), None).expect("Failed to allocate VM");
522        drop(vm);
523        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
524        vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).expect("Failed to allocate VM");
525        drop(dataset);
526        drop(cache);
527        drop(vm);
528    }
529
530    #[test]
531    fn lib_dataset_memory() {
532        let flags = RandomXFlag::default();
533        let key = "Key";
534        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
535        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
536        let memory = dataset.get_data().unwrap_or_else(|_| std::vec::Vec::new());
537        assert!(!memory.is_empty(), "Failed to get dataset memory");
538        let vec = vec![0u8; memory.len()];
539        assert_ne!(memory, vec);
540        drop(dataset);
541        drop(cache);
542    }
543
544    #[test]
545    fn test_null_assignments() {
546        let flags = RandomXFlag::get_recommended_flags();
547        if let Ok(mut vm) = RandomXVM::new(flags, None, None) {
548            let cache = RandomXCache {
549                inner: Arc::new(RandomXCacheInner {
550                    cache_ptr: ptr::null_mut(),
551                }),
552            };
553            assert!(vm.reinit_cache(cache.clone()).is_err());
554            let dataset = RandomXDataset {
555                inner: Arc::new(RandomXDatasetInner {
556                    dataset_ptr: ptr::null_mut(),
557                    dataset_count: 0,
558                    cache,
559                }),
560            };
561            assert!(vm.reinit_dataset(dataset.clone()).is_err());
562        }
563    }
564
565    #[test]
566    fn lib_calculate_hash() {
567        let flags = RandomXFlag::get_recommended_flags();
568        let flags2 = flags | RandomXFlag::FLAG_FULL_MEM;
569        let key = "Key";
570        let input = "Input";
571        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
572        let mut vm1 = RandomXVM::new(flags, Some(cache1.clone()), None).unwrap();
573        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
574        let vec = vec![0u8; hash1.len()];
575        assert_ne!(hash1, vec);
576        let reinit_cache = vm1.reinit_cache(cache1.clone());
577        assert!(reinit_cache.is_ok());
578        let hash2 = vm1.calculate_hash(input.as_bytes()).expect("no data");
579        assert_ne!(hash2, vec);
580        assert_eq!(hash1, hash2);
581
582        let cache2 = RandomXCache::new(flags, key.as_bytes()).unwrap();
583        let vm2 = RandomXVM::new(flags, Some(cache2.clone()), None).unwrap();
584        let hash3 = vm2.calculate_hash(input.as_bytes()).expect("no data");
585        assert_eq!(hash2, hash3);
586
587        let cache3 = RandomXCache::new(flags, key.as_bytes()).unwrap();
588        let dataset3 = RandomXDataset::new(flags, cache3.clone(), 0).unwrap();
589        let mut vm3 = RandomXVM::new(flags2, None, Some(dataset3.clone())).unwrap();
590        let hash4 = vm3.calculate_hash(input.as_bytes()).expect("no data");
591        assert_ne!(hash3, vec);
592        let reinit_dataset = vm3.reinit_dataset(dataset3.clone());
593        assert!(reinit_dataset.is_ok());
594        let hash5 = vm3.calculate_hash(input.as_bytes()).expect("no data");
595        assert_ne!(hash4, vec);
596        assert_eq!(hash4, hash5);
597
598        let cache4 = RandomXCache::new(flags, key.as_bytes()).unwrap();
599        let dataset4 = RandomXDataset::new(flags, cache4.clone(), 0).unwrap();
600        let vm4 = RandomXVM::new(flags2, Some(cache4), Some(dataset4.clone())).unwrap();
601        let hash6 = vm3.calculate_hash(input.as_bytes()).expect("no data");
602        assert_eq!(hash5, hash6);
603
604        drop(dataset3);
605        drop(dataset4);
606        drop(cache1);
607        drop(cache2);
608        drop(cache3);
609        drop(vm1);
610        drop(vm2);
611        drop(vm3);
612        drop(vm4);
613    }
614
615    #[test]
616    fn lib_calculate_hash_set() {
617        let flags = RandomXFlag::default();
618        let key = "Key";
619        let inputs = vec!["Input".as_bytes(), "Input 2".as_bytes(), "Inputs 3".as_bytes()];
620        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
621        let vm = RandomXVM::new(flags, Some(cache.clone()), None).unwrap();
622        let hashes = vm.calculate_hash_set(inputs.as_slice()).expect("no data");
623        assert_eq!(inputs.len(), hashes.len());
624        let mut prev_hash = Vec::new();
625        for (i, hash) in hashes.into_iter().enumerate() {
626            let vec = vec![0u8; hash.len()];
627            assert_ne!(hash, vec);
628            assert_ne!(hash, prev_hash);
629            let compare = vm.calculate_hash(inputs[i]).unwrap(); // sanity check
630            assert_eq!(hash, compare);
631            prev_hash = hash;
632        }
633        drop(cache);
634        drop(vm);
635    }
636
637    #[test]
638    fn lib_calculate_hash_is_consistent() {
639        let flags = RandomXFlag::get_recommended_flags();
640        let key = "Key";
641        let input = "Input";
642        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
643        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
644        let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
645        let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
646        assert_eq!(hash, [
647            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
648            172, 253, 155, 204, 111, 183, 213, 157, 155
649        ]);
650        drop(vm);
651        drop(dataset);
652        drop(cache);
653
654        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
655        let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
656        let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
657        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
658        assert_eq!(hash1, [
659            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
660            172, 253, 155, 204, 111, 183, 213, 157, 155
661        ]);
662        drop(vm1);
663        drop(dataset1);
664        drop(cache1);
665    }
666
667    #[test]
668    fn lib_check_cache_and_dataset_lifetimes() {
669        let flags = RandomXFlag::get_recommended_flags();
670        let key = "Key";
671        let input = "Input";
672        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
673        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
674        let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
675        drop(dataset);
676        drop(cache);
677        let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
678        assert_eq!(hash, [
679            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
680            172, 253, 155, 204, 111, 183, 213, 157, 155
681        ]);
682        drop(vm);
683
684        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
685        let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
686        let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
687        drop(dataset1);
688        drop(cache1);
689        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
690        assert_eq!(hash1, [
691            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
692            172, 253, 155, 204, 111, 183, 213, 157, 155
693        ]);
694        drop(vm1);
695    }
696
697    #[test]
698    fn randomx_hash_fast_vs_light() {
699        let input = b"input";
700        let key = b"key";
701
702        let flags = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM;
703        let cache = RandomXCache::new(flags, key).unwrap();
704        let dataset = RandomXDataset::new(flags, cache, 0).unwrap();
705        let fast_vm = RandomXVM::new(flags, None, Some(dataset)).unwrap();
706
707        let flags = RandomXFlag::get_recommended_flags();
708        let cache = RandomXCache::new(flags, key).unwrap();
709        let light_vm = RandomXVM::new(flags, Some(cache), None).unwrap();
710
711        let fast = fast_vm.calculate_hash(input).unwrap();
712        let light = light_vm.calculate_hash(input).unwrap();
713        assert_eq!(fast, light);
714    }
715
716    #[test]
717    fn test_vectors_fast_mode() {
718        // test vectors from https://github.com/tevador/RandomX/blob/040f4500a6e79d54d84a668013a94507045e786f/src/tests/tests.cpp#L963-L979
719        let key = b"test key 000";
720        let vectors = [
721            (
722                b"This is a test".as_slice(),
723                "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f",
724            ),
725            (
726                b"Lorem ipsum dolor sit amet".as_slice(),
727                "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969",
728            ),
729            (
730                b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(),
731                "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8",
732            ),
733        ];
734
735        let flags = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM;
736        let cache = RandomXCache::new(flags, key).unwrap();
737        let dataset = RandomXDataset::new(flags, cache, 0).unwrap();
738        let vm = RandomXVM::new(flags, None, Some(dataset)).unwrap();
739
740        for (input, expected) in vectors {
741            let hash = vm.calculate_hash(input).unwrap();
742            assert_eq!(hex::decode(expected).unwrap(), hash);
743        }
744    }
745
746    #[test]
747    fn test_vectors_light_mode() {
748        // test vectors from https://github.com/tevador/RandomX/blob/040f4500a6e79d54d84a668013a94507045e786f/src/tests/tests.cpp#L963-L985
749        let vectors = [
750            (
751                b"test key 000",
752                b"This is a test".as_slice(),
753                "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f",
754            ),
755            (
756                b"test key 000",
757                b"Lorem ipsum dolor sit amet".as_slice(),
758                "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969",
759            ),
760            (
761                b"test key 000",
762                b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(),
763                "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8",
764            ),
765            (
766                b"test key 001",
767                b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(),
768                "e9ff4503201c0c2cca26d285c93ae883f9b1d30c9eb240b820756f2d5a7905fc",
769            ),
770        ];
771
772        let flags = RandomXFlag::get_recommended_flags();
773        for (key, input, expected) in vectors {
774            let cache = RandomXCache::new(flags, key).unwrap();
775            let vm = RandomXVM::new(flags, Some(cache), None).unwrap();
776            let hash = vm.calculate_hash(input).unwrap();
777            assert_eq!(hex::decode(expected).unwrap(), hash);
778        }
779    }
780}