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
141impl Drop for RandomXCacheInner {
142    /// De-allocates memory for the `cache` object
143    fn drop(&mut self) {
144        unsafe {
145            randomx_release_cache(self.cache_ptr);
146        }
147    }
148}
149
150#[derive(Debug, Clone)]
151/// The Cache is used for light verification and Dataset construction.
152pub struct RandomXCache {
153    inner: Arc<RandomXCacheInner>,
154}
155
156impl RandomXCache {
157    /// Creates and alllcates memory for a new cache object, and initializes it with
158    /// the key value.
159    ///
160    /// `flags` is any combination of the following two flags:
161    /// * FLAG_LARGE_PAGES
162    /// * FLAG_JIT
163    ///
164    /// and (optionally) one of the following flags (depending on instruction set supported):
165    /// * FLAG_ARGON2_SSSE3
166    /// * FLAG_ARGON2_AVX2
167    ///
168    /// `key` is a sequence of u8 used to initialize SuperScalarHash.
169    pub fn new(flags: RandomXFlag, key: &[u8]) -> Result<RandomXCache, RandomXError> {
170        if key.is_empty() {
171            Err(RandomXError::ParameterError("key is empty".to_string()))
172        } else {
173            let cache_ptr = unsafe { randomx_alloc_cache(flags.bits) };
174            if cache_ptr.is_null() {
175                Err(RandomXError::CreationError("Could not allocate cache".to_string()))
176            } else {
177                let inner = RandomXCacheInner { cache_ptr };
178                let result = RandomXCache { inner: Arc::new(inner) };
179                let key_ptr = key.as_ptr() as *mut c_void;
180                let key_size = key.len();
181                unsafe {
182                    randomx_init_cache(result.inner.cache_ptr, key_ptr, key_size);
183                }
184                Ok(result)
185            }
186        }
187    }
188}
189
190#[derive(Debug)]
191struct RandomXDatasetInner {
192    dataset_ptr: *mut randomx_dataset,
193    dataset_count: u32,
194    #[allow(dead_code)]
195    cache: RandomXCache,
196}
197
198impl Drop for RandomXDatasetInner {
199    /// De-allocates memory for the `dataset` object.
200    fn drop(&mut self) {
201        unsafe {
202            randomx_release_dataset(self.dataset_ptr);
203        }
204    }
205}
206
207#[derive(Debug, Clone)]
208/// The Dataset is a read-only memory structure that is used during VM program execution.
209pub struct RandomXDataset {
210    inner: Arc<RandomXDatasetInner>,
211}
212
213impl RandomXDataset {
214    /// Creates a new dataset object, allocates memory to the `dataset` object and initializes it.
215    ///
216    /// `flags` is one of the following:
217    /// * FLAG_DEFAULT
218    /// * FLAG_LARGE_PAGES
219    ///
220    /// `cache` is a cache object.
221    ///
222    /// `start` is the item number where initialization should start, recommended to pass in 0.
223    // Conversions may be lossy on Windows or Linux
224    #[allow(clippy::useless_conversion)]
225    pub fn new(flags: RandomXFlag, cache: RandomXCache, start: u32) -> Result<RandomXDataset, RandomXError> {
226        let item_count = RandomXDataset::count()
227            .map_err(|e| RandomXError::CreationError(format!("Could not get dataset count: {e:?}")))?;
228
229        let test = unsafe { randomx_alloc_dataset(flags.bits) };
230        if test.is_null() {
231            Err(RandomXError::CreationError("Could not allocate dataset".to_string()))
232        } else {
233            let inner = RandomXDatasetInner {
234                dataset_ptr: test,
235                dataset_count: item_count,
236                cache,
237            };
238            let result = RandomXDataset { inner: Arc::new(inner) };
239
240            if start < item_count {
241                unsafe {
242                    randomx_init_dataset(
243                        result.inner.dataset_ptr,
244                        result.inner.cache.inner.cache_ptr,
245                        c_ulong::from(start),
246                        c_ulong::from(item_count),
247                    );
248                }
249                Ok(result)
250            } else {
251                Err(RandomXError::CreationError(format!(
252                    "start must be less than item_count: start: {start}, item_count: {item_count}",
253                )))
254            }
255        }
256    }
257
258    /// Returns the number of items in the `dataset` or an error on failure.
259    pub fn count() -> Result<u32, RandomXError> {
260        match unsafe { randomx_dataset_item_count() } {
261            0 => Err(RandomXError::Other("Dataset item count was 0".to_string())),
262            x => {
263                // This weirdness brought to you by c_ulong being different on Windows and Linux
264                #[cfg(target_os = "windows")]
265                return Ok(x);
266                #[cfg(not(target_os = "windows"))]
267                return Ok(u32::try_from(x)?);
268            },
269        }
270    }
271
272    /// Returns the values of the internal memory buffer of the `dataset` or an error on failure.
273    pub fn get_data(&self) -> Result<Vec<u8>, RandomXError> {
274        let memory = unsafe { randomx_get_dataset_memory(self.inner.dataset_ptr) };
275        if memory.is_null() {
276            Err(RandomXError::Other("Could not get dataset memory".into()))
277        } else {
278            let count = usize::try_from(self.inner.dataset_count)?;
279            let mut result: Vec<u8> = vec![0u8; count];
280            let n = usize::try_from(self.inner.dataset_count)?;
281            unsafe {
282                libc::memcpy(result.as_mut_ptr() as *mut c_void, memory, n);
283            }
284            Ok(result)
285        }
286    }
287}
288
289#[derive(Debug)]
290/// The RandomX Virtual Machine (VM) is a complex instruction set computer that executes generated programs.
291pub struct RandomXVM {
292    flags: RandomXFlag,
293    vm: *mut randomx_vm,
294    linked_cache: Option<RandomXCache>,
295    linked_dataset: Option<RandomXDataset>,
296}
297
298impl Drop for RandomXVM {
299    /// De-allocates memory for the `VM` object.
300    fn drop(&mut self) {
301        unsafe {
302            randomx_destroy_vm(self.vm);
303        }
304    }
305}
306
307impl RandomXVM {
308    /// Creates a new `VM` and initializes it, error on failure.
309    ///
310    /// `flags` is any combination of the following 5 flags:
311    /// * FLAG_LARGE_PAGES
312    /// * FLAG_HARD_AES
313    /// * FLAG_FULL_MEM
314    /// * FLAG_JIT
315    /// * FLAG_SECURE
316    ///
317    /// Or
318    ///
319    /// * FLAG_DEFAULT
320    ///
321    /// `cache` is a cache object, optional if FLAG_FULL_MEM is set.
322    ///
323    /// `dataset` is a dataset object, optional if FLAG_FULL_MEM is not set.
324    pub fn new(
325        flags: RandomXFlag,
326        cache: Option<RandomXCache>,
327        dataset: Option<RandomXDataset>,
328    ) -> Result<RandomXVM, RandomXError> {
329        let is_full_mem = flags.contains(RandomXFlag::FLAG_FULL_MEM);
330        match (cache, dataset) {
331            (None, None) => Err(RandomXError::CreationError("Failed to allocate VM".to_string())),
332            (None, _) if !is_full_mem => Err(RandomXError::FlagConfigError(
333                "No cache and FLAG_FULL_MEM not set".to_string(),
334            )),
335            (_, None) if is_full_mem => Err(RandomXError::FlagConfigError(
336                "No dataset and FLAG_FULL_MEM set".to_string(),
337            )),
338            (cache, dataset) => {
339                let cache_ptr = cache
340                    .as_ref()
341                    .map(|stash| stash.inner.cache_ptr)
342                    .unwrap_or_else(ptr::null_mut);
343                let dataset_ptr = dataset
344                    .as_ref()
345                    .map(|data| data.inner.dataset_ptr)
346                    .unwrap_or_else(ptr::null_mut);
347                let vm = unsafe { randomx_create_vm(flags.bits, cache_ptr, dataset_ptr) };
348                Ok(RandomXVM {
349                    vm,
350                    flags,
351                    linked_cache: cache,
352                    linked_dataset: dataset,
353                })
354            },
355        }
356    }
357
358    /// Re-initializes the `VM` with a new cache that was initialised without
359    /// RandomXFlag::FLAG_FULL_MEM.
360    pub fn reinit_cache(&mut self, cache: RandomXCache) -> Result<(), RandomXError> {
361        if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
362            Err(RandomXError::FlagConfigError(
363                "Cannot reinit cache with FLAG_FULL_MEM set".to_string(),
364            ))
365        } else {
366            unsafe {
367                randomx_vm_set_cache(self.vm, cache.inner.cache_ptr);
368            }
369            self.linked_cache = Some(cache);
370            Ok(())
371        }
372    }
373
374    /// Re-initializes the `VM` with a new dataset that was initialised with
375    /// RandomXFlag::FLAG_FULL_MEM.
376    pub fn reinit_dataset(&mut self, dataset: RandomXDataset) -> Result<(), RandomXError> {
377        if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
378            unsafe {
379                randomx_vm_set_dataset(self.vm, dataset.inner.dataset_ptr);
380            }
381            self.linked_dataset = Some(dataset);
382            Ok(())
383        } else {
384            Err(RandomXError::FlagConfigError(
385                "Cannot reinit dataset without FLAG_FULL_MEM set".to_string(),
386            ))
387        }
388    }
389
390    /// Calculates a RandomX hash value and returns it, error on failure.
391    ///
392    /// `input` is a sequence of u8 to be hashed.
393    pub fn calculate_hash(&self, input: &[u8]) -> Result<Vec<u8>, RandomXError> {
394        if input.is_empty() {
395            Err(RandomXError::ParameterError("input was empty".to_string()))
396        } else {
397            let size_input = input.len();
398            let input_ptr = input.as_ptr() as *mut c_void;
399            let arr = [0; RANDOMX_HASH_SIZE as usize];
400            let output_ptr = arr.as_ptr() as *mut c_void;
401            unsafe {
402                randomx_calculate_hash(self.vm, input_ptr, size_input, output_ptr);
403            }
404            // if this failed, arr should still be empty
405            if arr == [0; RANDOMX_HASH_SIZE as usize] {
406                Err(RandomXError::Other("RandomX calculated hash was empty".to_string()))
407            } else {
408                let result = arr.to_vec();
409                Ok(result)
410            }
411        }
412    }
413
414    /// Calculates hashes from a set of inputs.
415    ///
416    /// `input` is an array of a sequence of u8 to be hashed.
417    #[allow(clippy::needless_range_loop)] // Range loop is not only for indexing `input`
418    pub fn calculate_hash_set(&self, input: &[&[u8]]) -> Result<Vec<Vec<u8>>, RandomXError> {
419        if input.is_empty() {
420            // Empty set
421            return Err(RandomXError::ParameterError("input was empty".to_string()));
422        }
423
424        let mut result = Vec::new();
425        // For single input
426        if input.len() == 1 {
427            let hash = self.calculate_hash(input[0])?;
428            result.push(hash);
429            return Ok(result);
430        }
431
432        // For multiple inputs
433        let mut output_ptr: *mut c_void = ptr::null_mut();
434        let arr = [0; RANDOMX_HASH_SIZE as usize];
435
436        // Not len() as last iteration assigns final hash
437        let iterations = input.len() + 1;
438        for i in 0..iterations {
439            if i == iterations - 1 {
440                // For last iteration
441                unsafe {
442                    randomx_calculate_hash_last(self.vm, output_ptr);
443                }
444            } else {
445                if input[i].is_empty() {
446                    // Stop calculations
447                    if arr != [0; RANDOMX_HASH_SIZE as usize] {
448                        // Complete what was started
449                        unsafe {
450                            randomx_calculate_hash_last(self.vm, output_ptr);
451                        }
452                    }
453                    return Err(RandomXError::ParameterError("input was empty".to_string()));
454                };
455                let size_input = input[i].len();
456                let input_ptr = input[i].as_ptr() as *mut c_void;
457                output_ptr = arr.as_ptr() as *mut c_void;
458                if i == 0 {
459                    // For first iteration
460                    unsafe {
461                        randomx_calculate_hash_first(self.vm, input_ptr, size_input);
462                    }
463                } else {
464                    unsafe {
465                        // For every other iteration
466                        randomx_calculate_hash_next(self.vm, input_ptr, size_input, output_ptr);
467                    }
468                }
469            }
470
471            if i != 0 {
472                // First hash is only available in 2nd iteration
473                if arr == [0; RANDOMX_HASH_SIZE as usize] {
474                    return Err(RandomXError::Other("RandomX hash was zero".to_string()));
475                }
476                let output: Vec<u8> = arr.to_vec();
477                result.push(output);
478            }
479        }
480        Ok(result)
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use std::{ptr, sync::Arc};
487
488    use crate::{RandomXCache, RandomXCacheInner, RandomXDataset, RandomXDatasetInner, RandomXFlag, RandomXVM};
489
490    #[test]
491    fn lib_alloc_cache() {
492        let flags = RandomXFlag::default();
493        let key = "Key";
494        let cache = RandomXCache::new(flags, key.as_bytes()).expect("Failed to allocate cache");
495        drop(cache);
496    }
497
498    #[test]
499    fn lib_alloc_dataset() {
500        let flags = RandomXFlag::default();
501        let key = "Key";
502        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
503        let dataset = RandomXDataset::new(flags, cache.clone(), 0).expect("Failed to allocate dataset");
504        drop(dataset);
505        drop(cache);
506    }
507
508    #[test]
509    fn lib_alloc_vm() {
510        let flags = RandomXFlag::default();
511        let key = "Key";
512        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
513        let mut vm = RandomXVM::new(flags, Some(cache.clone()), None).expect("Failed to allocate VM");
514        drop(vm);
515        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
516        vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).expect("Failed to allocate VM");
517        drop(dataset);
518        drop(cache);
519        drop(vm);
520    }
521
522    #[test]
523    fn lib_dataset_memory() {
524        let flags = RandomXFlag::default();
525        let key = "Key";
526        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
527        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
528        let memory = dataset.get_data().unwrap_or_else(|_| std::vec::Vec::new());
529        assert!(!memory.is_empty(), "Failed to get dataset memory");
530        let vec = vec![0u8; memory.len()];
531        assert_ne!(memory, vec);
532        drop(dataset);
533        drop(cache);
534    }
535
536    #[test]
537    fn test_null_assignments() {
538        let flags = RandomXFlag::get_recommended_flags();
539        if let Ok(mut vm) = RandomXVM::new(flags, None, None) {
540            let cache = RandomXCache {
541                inner: Arc::new(RandomXCacheInner {
542                    cache_ptr: ptr::null_mut(),
543                }),
544            };
545            assert!(vm.reinit_cache(cache.clone()).is_err());
546            let dataset = RandomXDataset {
547                inner: Arc::new(RandomXDatasetInner {
548                    dataset_ptr: ptr::null_mut(),
549                    dataset_count: 0,
550                    cache,
551                }),
552            };
553            assert!(vm.reinit_dataset(dataset.clone()).is_err());
554        }
555    }
556
557    #[test]
558    fn lib_calculate_hash() {
559        let flags = RandomXFlag::get_recommended_flags();
560        let flags2 = flags | RandomXFlag::FLAG_FULL_MEM;
561        let key = "Key";
562        let input = "Input";
563        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
564        let mut vm1 = RandomXVM::new(flags, Some(cache1.clone()), None).unwrap();
565        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
566        let vec = vec![0u8; hash1.len()];
567        assert_ne!(hash1, vec);
568        let reinit_cache = vm1.reinit_cache(cache1.clone());
569        assert!(reinit_cache.is_ok());
570        let hash2 = vm1.calculate_hash(input.as_bytes()).expect("no data");
571        assert_ne!(hash2, vec);
572        assert_eq!(hash1, hash2);
573
574        let cache2 = RandomXCache::new(flags, key.as_bytes()).unwrap();
575        let vm2 = RandomXVM::new(flags, Some(cache2.clone()), None).unwrap();
576        let hash3 = vm2.calculate_hash(input.as_bytes()).expect("no data");
577        assert_eq!(hash2, hash3);
578
579        let cache3 = RandomXCache::new(flags, key.as_bytes()).unwrap();
580        let dataset3 = RandomXDataset::new(flags, cache3.clone(), 0).unwrap();
581        let mut vm3 = RandomXVM::new(flags2, None, Some(dataset3.clone())).unwrap();
582        let hash4 = vm3.calculate_hash(input.as_bytes()).expect("no data");
583        assert_ne!(hash3, vec);
584        let reinit_dataset = vm3.reinit_dataset(dataset3.clone());
585        assert!(reinit_dataset.is_ok());
586        let hash5 = vm3.calculate_hash(input.as_bytes()).expect("no data");
587        assert_ne!(hash4, vec);
588        assert_eq!(hash4, hash5);
589
590        let cache4 = RandomXCache::new(flags, key.as_bytes()).unwrap();
591        let dataset4 = RandomXDataset::new(flags, cache4.clone(), 0).unwrap();
592        let vm4 = RandomXVM::new(flags2, Some(cache4), Some(dataset4.clone())).unwrap();
593        let hash6 = vm3.calculate_hash(input.as_bytes()).expect("no data");
594        assert_eq!(hash5, hash6);
595
596        drop(dataset3);
597        drop(dataset4);
598        drop(cache1);
599        drop(cache2);
600        drop(cache3);
601        drop(vm1);
602        drop(vm2);
603        drop(vm3);
604        drop(vm4);
605    }
606
607    #[test]
608    fn lib_calculate_hash_set() {
609        let flags = RandomXFlag::default();
610        let key = "Key";
611        let inputs = vec!["Input".as_bytes(), "Input 2".as_bytes(), "Inputs 3".as_bytes()];
612        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
613        let vm = RandomXVM::new(flags, Some(cache.clone()), None).unwrap();
614        let hashes = vm.calculate_hash_set(inputs.as_slice()).expect("no data");
615        assert_eq!(inputs.len(), hashes.len());
616        let mut prev_hash = Vec::new();
617        for (i, hash) in hashes.into_iter().enumerate() {
618            let vec = vec![0u8; hash.len()];
619            assert_ne!(hash, vec);
620            assert_ne!(hash, prev_hash);
621            let compare = vm.calculate_hash(inputs[i]).unwrap(); // sanity check
622            assert_eq!(hash, compare);
623            prev_hash = hash;
624        }
625        drop(cache);
626        drop(vm);
627    }
628
629    #[test]
630    fn lib_calculate_hash_is_consistent() {
631        let flags = RandomXFlag::get_recommended_flags();
632        let key = "Key";
633        let input = "Input";
634        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
635        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
636        let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
637        let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
638        assert_eq!(hash, [
639            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
640            172, 253, 155, 204, 111, 183, 213, 157, 155
641        ]);
642        drop(vm);
643        drop(dataset);
644        drop(cache);
645
646        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
647        let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
648        let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
649        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
650        assert_eq!(hash1, [
651            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
652            172, 253, 155, 204, 111, 183, 213, 157, 155
653        ]);
654        drop(vm1);
655        drop(dataset1);
656        drop(cache1);
657    }
658
659    #[test]
660    fn lib_check_cache_and_dataset_lifetimes() {
661        let flags = RandomXFlag::get_recommended_flags();
662        let key = "Key";
663        let input = "Input";
664        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
665        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
666        let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
667        drop(dataset);
668        drop(cache);
669        let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
670        assert_eq!(hash, [
671            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
672            172, 253, 155, 204, 111, 183, 213, 157, 155
673        ]);
674        drop(vm);
675
676        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
677        let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
678        let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
679        drop(dataset1);
680        drop(cache1);
681        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
682        assert_eq!(hash1, [
683            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
684            172, 253, 155, 204, 111, 183, 213, 157, 155
685        ]);
686        drop(vm1);
687    }
688
689    #[test]
690    fn randomx_hash_fast_vs_light() {
691        let input = b"input";
692        let key = b"key";
693
694        let flags = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM;
695        let cache = RandomXCache::new(flags, key).unwrap();
696        let dataset = RandomXDataset::new(flags, cache, 0).unwrap();
697        let fast_vm = RandomXVM::new(flags, None, Some(dataset)).unwrap();
698
699        let flags = RandomXFlag::get_recommended_flags();
700        let cache = RandomXCache::new(flags, key).unwrap();
701        let light_vm = RandomXVM::new(flags, Some(cache), None).unwrap();
702
703        let fast = fast_vm.calculate_hash(input).unwrap();
704        let light = light_vm.calculate_hash(input).unwrap();
705        assert_eq!(fast, light);
706    }
707
708    #[test]
709    fn test_vectors_fast_mode() {
710        // test vectors from https://github.com/tevador/RandomX/blob/040f4500a6e79d54d84a668013a94507045e786f/src/tests/tests.cpp#L963-L979
711        let key = b"test key 000";
712        let vectors = [
713            (
714                b"This is a test".as_slice(),
715                "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f",
716            ),
717            (
718                b"Lorem ipsum dolor sit amet".as_slice(),
719                "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969",
720            ),
721            (
722                b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(),
723                "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8",
724            ),
725        ];
726
727        let flags = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM;
728        let cache = RandomXCache::new(flags, key).unwrap();
729        let dataset = RandomXDataset::new(flags, cache, 0).unwrap();
730        let vm = RandomXVM::new(flags, None, Some(dataset)).unwrap();
731
732        for (input, expected) in vectors {
733            let hash = vm.calculate_hash(input).unwrap();
734            assert_eq!(hex::decode(expected).unwrap(), hash);
735        }
736    }
737
738    #[test]
739    fn test_vectors_light_mode() {
740        // test vectors from https://github.com/tevador/RandomX/blob/040f4500a6e79d54d84a668013a94507045e786f/src/tests/tests.cpp#L963-L985
741        let vectors = [
742            (
743                b"test key 000",
744                b"This is a test".as_slice(),
745                "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f",
746            ),
747            (
748                b"test key 000",
749                b"Lorem ipsum dolor sit amet".as_slice(),
750                "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969",
751            ),
752            (
753                b"test key 000",
754                b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(),
755                "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8",
756            ),
757            (
758                b"test key 001",
759                b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(),
760                "e9ff4503201c0c2cca26d285c93ae883f9b1d30c9eb240b820756f2d5a7905fc",
761            ),
762        ];
763
764        let flags = RandomXFlag::get_recommended_flags();
765        for (key, input, expected) in vectors {
766            let cache = RandomXCache::new(flags, key).unwrap();
767            let vm = RandomXVM::new(flags, Some(cache), None).unwrap();
768            let hash = vm.calculate_hash(input).unwrap();
769            assert_eq!(hex::decode(expected).unwrap(), hash);
770        }
771    }
772}