1mod bindings;
38use std::{convert::TryFrom, num::TryFromIntError, ptr, sync::Arc};
39
40use bindings::{
41 randomx_alloc_cache,
42 randomx_alloc_dataset,
43 randomx_cache,
44 randomx_calculate_hash,
45 randomx_create_vm,
46 randomx_dataset,
47 randomx_dataset_item_count,
48 randomx_destroy_vm,
49 randomx_get_dataset_memory,
50 randomx_init_cache,
51 randomx_init_dataset,
52 randomx_release_cache,
53 randomx_release_dataset,
54 randomx_vm,
55 randomx_vm_set_cache,
56 randomx_vm_set_dataset,
57 RANDOMX_DATASET_ITEM_SIZE,
58 RANDOMX_HASH_SIZE,
59};
60use bitflags::bitflags;
61use libc::{c_ulong, c_void};
62use thiserror::Error;
63
64use crate::bindings::{
65 randomx_calculate_hash_first,
66 randomx_calculate_hash_last,
67 randomx_calculate_hash_next,
68 randomx_get_flags,
69};
70
71bitflags! {
72 pub struct RandomXFlag: u32 {
74 const FLAG_DEFAULT = 0b0000_0000;
76 const FLAG_LARGE_PAGES = 0b0000_0001;
78 const FLAG_HARD_AES = 0b0000_0010;
80 const FLAG_FULL_MEM = 0b0000_0100;
82 const FLAG_JIT = 0b0000_1000;
84 const FLAG_SECURE = 0b0001_0000;
87 const FLAG_ARGON2_SSSE3 = 0b0010_0000;
89 const FLAG_ARGON2_AVX2 = 0b0100_0000;
91 const FLAG_ARGON2 = 0b0110_0000;
93 }
94}
95
96impl RandomXFlag {
97 pub fn get_recommended_flags() -> RandomXFlag {
106 RandomXFlag {
107 bits: unsafe { randomx_get_flags() },
108 }
109 }
110}
111
112impl Default for RandomXFlag {
113 fn default() -> RandomXFlag {
115 RandomXFlag::FLAG_DEFAULT
116 }
117}
118
119#[derive(Debug, Clone, Error)]
120pub enum RandomXError {
122 #[error("Problem creating the RandomX object: {0}")]
123 CreationError(String),
124 #[error("Problem with configuration flags: {0}")]
125 FlagConfigError(String),
126 #[error("Problem with parameters supplied: {0}")]
127 ParameterError(String),
128 #[error("Failed to convert Int to usize")]
129 TryFromIntError(#[from] TryFromIntError),
130 #[error("Unknown problem running RandomX: {0}")]
131 Other(String),
132}
133
134#[derive(Debug)]
135struct RandomXCacheInner {
136 cache_ptr: *mut randomx_cache,
137}
138
139impl Drop for RandomXCacheInner {
140 fn drop(&mut self) {
142 unsafe {
143 randomx_release_cache(self.cache_ptr);
144 }
145 }
146}
147
148#[derive(Debug, Clone)]
149pub struct RandomXCache {
151 inner: Arc<RandomXCacheInner>,
152}
153
154impl RandomXCache {
155 pub fn new(flags: RandomXFlag, key: &[u8]) -> Result<RandomXCache, RandomXError> {
168 if key.is_empty() {
169 Err(RandomXError::ParameterError("key is empty".to_string()))
170 } else {
171 let test = unsafe { randomx_alloc_cache(flags.bits) };
172 if test.is_null() {
173 Err(RandomXError::CreationError("Could not allocate cache".to_string()))
174 } else {
175 let inner = RandomXCacheInner { cache_ptr: test };
176 let result = RandomXCache { inner: Arc::new(inner) };
177 let key_ptr = key.as_ptr() as *mut c_void;
178 let key_size = key.len() as usize;
179 unsafe {
180 randomx_init_cache(result.inner.cache_ptr, key_ptr, key_size);
181 }
182 Ok(result)
183 }
184 }
185 }
186}
187
188#[derive(Debug)]
189struct RandomXDatasetInner {
190 dataset_ptr: *mut randomx_dataset,
191 dataset_count: u32,
192 #[allow(dead_code)]
193 cache: RandomXCache,
194}
195
196impl Drop for RandomXDatasetInner {
197 fn drop(&mut self) {
199 unsafe {
200 randomx_release_dataset(self.dataset_ptr);
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
206pub struct RandomXDataset {
208 inner: Arc<RandomXDatasetInner>,
209}
210
211impl RandomXDataset {
212 #[allow(clippy::useless_conversion)]
223 pub fn new(flags: RandomXFlag, cache: RandomXCache, start: u32) -> Result<RandomXDataset, RandomXError> {
224 let count = RANDOMX_DATASET_ITEM_SIZE - 1 - start;
225 let test = unsafe { randomx_alloc_dataset(flags.bits) };
226 if test.is_null() {
227 Err(RandomXError::CreationError("Could not allocate dataset".to_string()))
228 } else {
229 let inner = RandomXDatasetInner {
230 dataset_ptr: test,
231 dataset_count: count,
232 cache,
233 };
234 let result = RandomXDataset { inner: Arc::new(inner) };
235 let item_count = result
236 .count()
237 .map_err(|err| RandomXError::CreationError(format!("Could not get dataset count:{}", err)))?;
238 if (start < item_count && count <= item_count) || (start + item_count <= count) {
240 unsafe {
241 randomx_init_dataset(
242 result.inner.dataset_ptr,
243 result.inner.cache.inner.cache_ptr,
244 c_ulong::from(start),
245 c_ulong::from(count),
246 );
247 }
248 Ok(result)
249 } else {
250 let reason = format!(
251 "Dataset `start` or `count` was out of bounds: start: {}, count: {}, actual count: {}",
252 start, count, item_count
253 );
254 Err(RandomXError::CreationError(reason))
255 }
256 }
257 }
258
259 pub fn count(&self) -> Result<u32, RandomXError> {
261 match unsafe { randomx_dataset_item_count() } {
262 0 => Err(RandomXError::Other("Dataset item count was 0".to_string())),
263 x => {
264 Ok(u32::try_from(u64::from(x))?)
266 },
267 }
268 }
269
270 pub fn get_data(&self) -> Result<Vec<u8>, RandomXError> {
272 let memory = unsafe { randomx_get_dataset_memory(self.inner.dataset_ptr) };
273 if memory.is_null() {
274 Err(RandomXError::Other("Could not get dataset memory".into()))
275 } else {
276 let count = usize::try_from(self.inner.dataset_count)?;
277 let mut result: Vec<u8> = vec![0u8; count];
278 let n = usize::try_from(self.inner.dataset_count)?;
279 unsafe {
280 libc::memcpy(result.as_mut_ptr() as *mut c_void, memory, n);
281 }
282 Ok(result)
283 }
284 }
285}
286
287#[derive(Debug)]
288pub struct RandomXVM {
290 flags: RandomXFlag,
291 vm: *mut randomx_vm,
292 linked_cache: Option<RandomXCache>,
293 linked_dataset: Option<RandomXDataset>,
294}
295
296impl Drop for RandomXVM {
297 fn drop(&mut self) {
299 unsafe {
300 randomx_destroy_vm(self.vm);
301 }
302 }
303}
304
305impl RandomXVM {
306 pub fn new(
323 flags: RandomXFlag,
324 cache: Option<RandomXCache>,
325 dataset: Option<RandomXDataset>,
326 ) -> Result<RandomXVM, RandomXError> {
327 let is_full_mem = flags.contains(RandomXFlag::FLAG_FULL_MEM);
328 match (cache, dataset) {
329 (None, None) => Err(RandomXError::CreationError("Failed to allocate VM".to_string())),
330 (None, _) if !is_full_mem => Err(RandomXError::FlagConfigError(
331 "No cache and FLAG_FULL_MEM not set".to_string(),
332 )),
333 (_, None) if is_full_mem => Err(RandomXError::FlagConfigError(
334 "No dataset and FLAG_FULL_MEM set".to_string(),
335 )),
336 (cache, dataset) => {
337 let cache_ptr = cache
338 .as_ref()
339 .map(|stash| stash.inner.cache_ptr)
340 .unwrap_or_else(ptr::null_mut);
341 let dataset_ptr = dataset
342 .as_ref()
343 .map(|data| data.inner.dataset_ptr)
344 .unwrap_or_else(ptr::null_mut);
345 let vm = unsafe { randomx_create_vm(flags.bits, cache_ptr, dataset_ptr) };
346 Ok(RandomXVM {
347 vm,
348 flags,
349 linked_cache: cache,
350 linked_dataset: dataset,
351 })
352 },
353 }
354 }
355
356 pub fn reinit_cache(&mut self, cache: RandomXCache) -> Result<(), RandomXError> {
359 if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
360 Err(RandomXError::FlagConfigError(
361 "Cannot reinit cache with FLAG_FULL_MEM set".to_string(),
362 ))
363 } else {
364 unsafe {
365 randomx_vm_set_cache(self.vm, cache.inner.cache_ptr);
366 }
367 self.linked_cache = Some(cache);
368 Ok(())
369 }
370 }
371
372 pub fn reinit_dataset(&mut self, dataset: RandomXDataset) -> Result<(), RandomXError> {
375 if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
376 unsafe {
377 randomx_vm_set_dataset(self.vm, dataset.inner.dataset_ptr);
378 }
379 self.linked_dataset = Some(dataset);
380 Ok(())
381 } else {
382 Err(RandomXError::FlagConfigError(
383 "Cannot reinit dataset without FLAG_FULL_MEM set".to_string(),
384 ))
385 }
386 }
387
388 pub fn calculate_hash(&self, input: &[u8]) -> Result<Vec<u8>, RandomXError> {
392 if input.is_empty() {
393 Err(RandomXError::ParameterError("input was empty".to_string()))
394 } else {
395 let size_input = input.len() as usize;
396 let input_ptr = input.as_ptr() as *mut c_void;
397 let arr = [0; RANDOMX_HASH_SIZE as usize];
398 let output_ptr = arr.as_ptr() as *mut c_void;
399 unsafe {
400 randomx_calculate_hash(self.vm, input_ptr, size_input, output_ptr);
401 }
402 if arr == [0; RANDOMX_HASH_SIZE as usize] {
404 Err(RandomXError::Other("RandomX calculated hash was empty".to_string()))
405 } else {
406 let result = arr.to_vec();
407 Ok(result)
408 }
409 }
410 }
411
412 #[allow(clippy::needless_range_loop)] pub fn calculate_hash_set(&self, input: &[&[u8]]) -> Result<Vec<Vec<u8>>, RandomXError> {
417 if input.is_empty() {
418 return Err(RandomXError::ParameterError("input was empty".to_string()));
420 }
421
422 let mut result = Vec::new();
423 if input.len() == 1 {
425 let hash = self.calculate_hash(input[0])?;
426 result.push(hash);
427 return Ok(result);
428 }
429
430 let mut output_ptr: *mut c_void = ptr::null_mut();
432 let arr = [0; RANDOMX_HASH_SIZE as usize];
433
434 let iterations = input.len() + 1;
436 for i in 0..iterations {
437 if i == iterations - 1 {
438 unsafe {
440 randomx_calculate_hash_last(self.vm, output_ptr);
441 }
442 } else {
443 if input[i].is_empty() {
444 if arr != [0; RANDOMX_HASH_SIZE as usize] {
446 unsafe {
448 randomx_calculate_hash_last(self.vm, output_ptr);
449 }
450 }
451 return Err(RandomXError::ParameterError("input was empty".to_string()));
452 };
453 let size_input = input[i].len() as usize;
454 let input_ptr = input[i].as_ptr() as *mut c_void;
455 output_ptr = arr.as_ptr() as *mut c_void;
456 if i == 0 {
457 unsafe {
459 randomx_calculate_hash_first(self.vm, input_ptr, size_input);
460 }
461 } else {
462 unsafe {
463 randomx_calculate_hash_next(self.vm, input_ptr, size_input, output_ptr);
465 }
466 }
467 }
468
469 if i != 0 {
470 if arr == [0; RANDOMX_HASH_SIZE as usize] {
472 return Err(RandomXError::Other("RandomX hash was zero".to_string()));
473 }
474 let output: Vec<u8> = arr.to_vec();
475 result.push(output);
476 }
477 }
478 Ok(result)
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use crate::{RandomXCache, RandomXDataset, RandomXFlag, RandomXVM};
485
486 #[test]
487 fn lib_alloc_cache() {
488 let flags = RandomXFlag::default();
489 let key = "Key";
490 let cache = RandomXCache::new(flags, key.as_bytes()).expect("Failed to allocate cache");
491 drop(cache);
492 }
493
494 #[test]
495 fn lib_alloc_dataset() {
496 let flags = RandomXFlag::default();
497 let key = "Key";
498 let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
499 let dataset = RandomXDataset::new(flags, cache.clone(), 0).expect("Failed to allocate dataset");
500 drop(dataset);
501 drop(cache);
502 }
503
504 #[test]
505 fn lib_alloc_vm() {
506 let flags = RandomXFlag::default();
507 let key = "Key";
508 let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
509 let mut vm = RandomXVM::new(flags, Some(cache.clone()), None).expect("Failed to allocate VM");
510 drop(vm);
511 let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
512 vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).expect("Failed to allocate VM");
513 drop(dataset);
514 drop(cache);
515 drop(vm);
516 }
517
518 #[test]
519 fn lib_dataset_memory() {
520 let flags = RandomXFlag::default();
521 let key = "Key";
522 let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
523 let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
524 let memory = dataset.get_data().unwrap_or_else(|_| std::vec::Vec::new());
525 assert!(!memory.is_empty(), "Failed to get dataset memory");
526 let vec = vec![0u8; memory.len() as usize];
527 assert_ne!(memory, vec);
528 drop(dataset);
529 drop(cache);
530 }
531
532 #[test]
533 fn lib_calculate_hash() {
534 let flags = RandomXFlag::get_recommended_flags();
535 let flags2 = flags | RandomXFlag::FLAG_FULL_MEM;
536 let key = "Key";
537 let input = "Input";
538 let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
539 let mut vm1 = RandomXVM::new(flags, Some(cache1.clone()), None).unwrap();
540 let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
541 let vec = vec![0u8; hash1.len() as usize];
542 assert_ne!(hash1, vec);
543 let reinit_cache = vm1.reinit_cache(cache1.clone());
544 assert!(reinit_cache.is_ok());
545 let hash2 = vm1.calculate_hash(input.as_bytes()).expect("no data");
546 assert_ne!(hash2, vec);
547 assert_eq!(hash1, hash2);
548
549 let cache2 = RandomXCache::new(flags, key.as_bytes()).unwrap();
550 let vm2 = RandomXVM::new(flags, Some(cache2.clone()), None).unwrap();
551 let hash3 = vm2.calculate_hash(input.as_bytes()).expect("no data");
552 assert_eq!(hash2, hash3);
553
554 let cache3 = RandomXCache::new(flags, key.as_bytes()).unwrap();
555 let dataset3 = RandomXDataset::new(flags, cache3.clone(), 0).unwrap();
556 let mut vm3 = RandomXVM::new(flags2, None, Some(dataset3.clone())).unwrap();
557 let hash4 = vm3.calculate_hash(input.as_bytes()).expect("no data");
558 assert_ne!(hash3, vec);
559 let reinit_dataset = vm3.reinit_dataset(dataset3.clone());
560 assert!(reinit_dataset.is_ok());
561 let hash5 = vm3.calculate_hash(input.as_bytes()).expect("no data");
562 assert_ne!(hash4, vec);
563 assert_eq!(hash4, hash5);
564
565 let cache4 = RandomXCache::new(flags, key.as_bytes()).unwrap();
566 let dataset4 = RandomXDataset::new(flags, cache4.clone(), 0).unwrap();
567 let vm4 = RandomXVM::new(flags2, Some(cache4), Some(dataset4.clone())).unwrap();
568 let hash6 = vm3.calculate_hash(input.as_bytes()).expect("no data");
569 assert_eq!(hash5, hash6);
570
571 drop(dataset3);
572 drop(dataset4);
573 drop(cache1);
574 drop(cache2);
575 drop(cache3);
576 drop(vm1);
577 drop(vm2);
578 drop(vm3);
579 drop(vm4);
580 }
581
582 #[test]
583 fn lib_calculate_hash_set() {
584 let flags = RandomXFlag::default();
585 let key = "Key";
586 let inputs = vec!["Input".as_bytes(), "Input 2".as_bytes(), "Inputs 3".as_bytes()];
587 let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
588 let vm = RandomXVM::new(flags, Some(cache.clone()), None).unwrap();
589 let hashes = vm.calculate_hash_set(inputs.as_slice()).expect("no data");
590 assert_eq!(inputs.len(), hashes.len());
591 let mut prev_hash = Vec::new();
592 for (i, hash) in hashes.into_iter().enumerate() {
593 let vec = vec![0u8; hash.len() as usize];
594 assert_ne!(hash, vec);
595 assert_ne!(hash, prev_hash);
596 let compare = vm.calculate_hash(inputs[i]).unwrap(); assert_eq!(hash, compare);
598 prev_hash = hash;
599 }
600 drop(cache);
601 drop(vm);
602 }
603
604 #[test]
605 fn lib_calculate_hash_is_consistent() {
606 let flags = RandomXFlag::get_recommended_flags();
607 let key = "Key";
608 let input = "Input";
609 let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
610 let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
611 let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
612 let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
613 assert_eq!(hash, [
614 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
615 172, 253, 155, 204, 111, 183, 213, 157, 155
616 ]);
617 drop(vm);
618 drop(dataset);
619 drop(cache);
620
621 let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
622 let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
623 let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
624 let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
625 assert_eq!(hash1, [
626 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
627 172, 253, 155, 204, 111, 183, 213, 157, 155
628 ]);
629 drop(vm1);
630 drop(dataset1);
631 drop(cache1);
632 }
633
634 #[test]
635 fn lib_check_cache_and_dataset_lifetimes() {
636 let flags = RandomXFlag::get_recommended_flags();
637 let key = "Key";
638 let input = "Input";
639 let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
640 let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
641 let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
642 drop(dataset);
643 drop(cache);
644 let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
645 assert_eq!(hash, [
646 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
647 172, 253, 155, 204, 111, 183, 213, 157, 155
648 ]);
649 drop(vm);
650
651 let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
652 let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
653 let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
654 drop(dataset1);
655 drop(cache1);
656 let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
657 assert_eq!(hash1, [
658 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
659 172, 253, 155, 204, 111, 183, 213, 157, 155
660 ]);
661 drop(vm1);
662 }
663}