Skip to main content

rustdom_x/
memory.rs

1extern crate argon2;
2
3use std::sync::{Arc, RwLock};
4use std::time::Instant;
5
6use self::argon2::block::Block;
7
8use super::byte_string;
9use super::superscalar::{Blake2Generator, ScProgram};
10
11const RANDOMX_ARGON_LANES: u32 = 1;
12const RANDOMX_ARGON_MEMORY: u32 = 262144;
13const RANDOMX_ARGON_SALT: &[u8; 8] = b"RandomX\x03";
14const RANDOMX_ARGON_ITERATIONS: u32 = 3;
15const RANDOMX_CACHE_ACCESSES: usize = 8;
16
17const ARGON2_SYNC_POINTS: u32 = 4;
18const ARGON_BLOCK_SIZE: u32 = 1024;
19
20pub const CACHE_LINE_SIZE: u64 = 64;
21pub const DATASET_ITEM_COUNT: usize = (2147483648 + 33554368) / 64; //34.078.719
22
23const SUPERSCALAR_MUL_0: u64 = 6364136223846793005;
24const SUPERSCALAR_ADD_1: u64 = 9298411001130361340;
25const SUPERSCALAR_ADD_2: u64 = 12065312585734608966;
26const SUPERSCALAR_ADD_3: u64 = 9306329213124626780;
27const SUPERSCALAR_ADD_4: u64 = 5281919268842080866;
28const SUPERSCALAR_ADD_5: u64 = 10536153434571861004;
29const SUPERSCALAR_ADD_6: u64 = 3398623926847679864;
30const SUPERSCALAR_ADD_7: u64 = 9549104520008361294;
31
32//256MiB, always used, named randomx_cache in the reference implementation
33pub struct SeedMemory {
34    pub blocks: Box<[Block]>,
35    pub programs: Vec<ScProgram<'static>>,
36}
37
38impl SeedMemory {
39    pub fn no_memory() -> SeedMemory {
40        SeedMemory {
41            blocks: Box::new([]),
42            programs: Vec::with_capacity(0),
43        }
44    }
45
46    /// Creates a new initialised seed memory.
47    pub fn new_initialised(key: &[u8]) -> SeedMemory {
48        let mut mem = argon2::memory::Memory::new(RANDOMX_ARGON_LANES, RANDOMX_ARGON_MEMORY);
49        let context = &create_argon_context(key);
50        argon2::core::initialize(context, &mut mem);
51        argon2::core::fill_memory_blocks(context, &mut mem);
52
53        let mut programs = Vec::with_capacity(RANDOMX_CACHE_ACCESSES);
54        let mut generator = Blake2Generator::new(key, 0);
55        for _ in 0..RANDOMX_CACHE_ACCESSES {
56            programs.push(ScProgram::generate(&mut generator));
57        }
58
59        SeedMemory {
60            blocks: mem.blocks,
61            programs,
62        }
63    }
64}
65
66fn create_argon_context<'a>(key: &'a [u8]) -> argon2::context::Context<'a> {
67    let segment_length = RANDOMX_ARGON_MEMORY / (RANDOMX_ARGON_LANES * ARGON2_SYNC_POINTS);
68    let config = argon2::config::Config {
69        ad: &[],
70        hash_length: 0,
71        lanes: RANDOMX_ARGON_LANES,
72        mem_cost: RANDOMX_ARGON_MEMORY,
73        secret: &[],
74        time_cost: RANDOMX_ARGON_ITERATIONS,
75        variant: argon2::Variant::Argon2d,
76        version: argon2::Version::Version13,
77    };
78    argon2::context::Context {
79        config,
80        memory_blocks: RANDOMX_ARGON_MEMORY,
81        pwd: key,
82        salt: RANDOMX_ARGON_SALT,
83        lane_length: segment_length * ARGON2_SYNC_POINTS,
84        segment_length,
85    }
86}
87
88fn mix_block_value(seed_mem: &SeedMemory, reg_value: u64, r: usize) -> u64 {
89    let mask = (((RANDOMX_ARGON_MEMORY * ARGON_BLOCK_SIZE) as u64) / CACHE_LINE_SIZE) - 1;
90    let byte_offset = ((reg_value & mask) * CACHE_LINE_SIZE) + (8 * r as u64);
91
92    let block_ix = byte_offset / ARGON_BLOCK_SIZE as u64;
93    let block_v_ix = (byte_offset - (block_ix * ARGON_BLOCK_SIZE as u64)) / 8;
94    seed_mem.blocks[block_ix as usize][block_v_ix as usize]
95}
96
97pub fn init_dataset_item(seed_mem: &SeedMemory, item_num: u64) -> [u64; 8] {
98    let mut ds = [0; 8];
99
100    let mut reg_value = item_num;
101    ds[0] = (item_num + 1).wrapping_mul(SUPERSCALAR_MUL_0);
102    ds[1] = ds[0] ^ SUPERSCALAR_ADD_1;
103    ds[2] = ds[0] ^ SUPERSCALAR_ADD_2;
104    ds[3] = ds[0] ^ SUPERSCALAR_ADD_3;
105    ds[4] = ds[0] ^ SUPERSCALAR_ADD_4;
106    ds[5] = ds[0] ^ SUPERSCALAR_ADD_5;
107    ds[6] = ds[0] ^ SUPERSCALAR_ADD_6;
108    ds[7] = ds[0] ^ SUPERSCALAR_ADD_7;
109
110    for prog in &seed_mem.programs {
111        prog.execute(&mut ds);
112
113        for (r, v) in ds.iter_mut().enumerate() {
114            let mix_value = mix_block_value(seed_mem, reg_value, r);
115            *v ^= mix_value;
116        }
117        reg_value = ds[prog.address_reg];
118    }
119    ds
120}
121
122#[derive(Clone)]
123pub struct VmMemoryAllocator {
124    pub vm_memory_seed: String,
125    pub vm_memory: Arc<VmMemory>,
126}
127
128impl VmMemoryAllocator {
129    pub fn initial() -> VmMemoryAllocator {
130        VmMemoryAllocator {
131            vm_memory_seed: "".to_string(),
132            vm_memory: Arc::new(VmMemory::no_memory()),
133        }
134    }
135
136    pub fn reallocate(&mut self, seed: String) {
137        if seed != self.vm_memory_seed {
138            let mem_init_start = Instant::now();
139            self.vm_memory = Arc::new(VmMemory::full(&byte_string::string_to_u8_array(&seed)));
140            self.vm_memory_seed = seed;
141            info!(
142                "memory init took {}ms with seed_hash: {}",
143                mem_init_start.elapsed().as_millis(),
144                self.vm_memory_seed,
145            );
146        }
147    }
148}
149
150pub struct VmMemory {
151    pub seed_memory: SeedMemory,
152    pub dataset_memory: RwLock<Vec<Option<[u64; 8]>>>,
153    pub cache: bool,
154}
155
156impl VmMemory {
157    //only useful for testing
158    pub fn no_memory() -> VmMemory {
159        VmMemory {
160            seed_memory: SeedMemory::no_memory(),
161            cache: false,
162            dataset_memory: RwLock::new(Vec::with_capacity(0)),
163        }
164    }
165
166    pub fn light(key: &[u8]) -> VmMemory {
167        VmMemory {
168            seed_memory: SeedMemory::new_initialised(key),
169            cache: false,
170            dataset_memory: RwLock::new(Vec::with_capacity(0)),
171        }
172    }
173    pub fn full(key: &[u8]) -> VmMemory {
174        let seed_mem = SeedMemory::new_initialised(key);
175        let mem = vec![None; DATASET_ITEM_COUNT];
176        VmMemory {
177            seed_memory: seed_mem,
178            cache: true,
179            dataset_memory: RwLock::new(mem),
180        }
181    }
182
183    pub fn dataset_read(&self, offset: u64, reg: &mut [u64; 8]) {
184        let item_num = offset / CACHE_LINE_SIZE;
185
186        if self.cache {
187            {
188                let mem = self.dataset_memory.read().unwrap();
189                let rl_cached = &mem[item_num as usize];
190                if let Some(rl) = rl_cached {
191                    for i in 0..8 {
192                        reg[i] ^= rl[i];
193                    }
194                    return;
195                }
196            }
197            {
198                let rl = init_dataset_item(&self.seed_memory, item_num);
199                let mut mem_mut = self.dataset_memory.write().unwrap();
200                mem_mut[item_num as usize] = Some(rl);
201                for i in 0..8 {
202                    reg[i] ^= rl[i];
203                }
204            }
205        } else {
206            let rl = init_dataset_item(&self.seed_memory, item_num);
207            for i in 0..8 {
208                reg[i] ^= rl[i];
209            }
210        }
211    }
212}