snarkvm_synthesizer_program/logic/command/
rand_chacha.rs1use crate::{FinalizeRegistersState, Opcode, Operand, StackTrait};
17use console::{
18 network::prelude::*,
19 program::{Literal, LiteralType, Plaintext, Register, Value},
20 types::{Address, Boolean, Field, Group, I8, I16, I32, I64, I128, Scalar, U8, U16, U32, U64, U128},
21};
22
23use rand::SeedableRng;
24
25pub const MAX_ADDITIONAL_SEEDS: usize = 2;
27
28#[derive(Clone, PartialEq, Eq, Hash)]
35pub struct RandChaCha<N: Network> {
36 operands: Vec<Operand<N>>,
38 destination: Register<N>,
40 destination_type: LiteralType,
42}
43
44impl<N: Network> RandChaCha<N> {
45 #[inline]
47 pub const fn opcode() -> Opcode {
48 Opcode::Command("rand.chacha")
49 }
50
51 #[inline]
53 pub fn operands(&self) -> &[Operand<N>] {
54 &self.operands
55 }
56
57 #[inline]
59 pub const fn destination(&self) -> &Register<N> {
60 &self.destination
61 }
62
63 #[inline]
65 pub const fn destination_type(&self) -> LiteralType {
66 self.destination_type
67 }
68
69 #[inline]
71 pub fn contains_external_struct(&self) -> bool {
72 false
73 }
74}
75
76impl<N: Network> RandChaCha<N> {
77 #[inline]
79 pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl FinalizeRegistersState<N>) -> Result<()> {
80 if self.operands.len() > MAX_ADDITIONAL_SEEDS {
82 bail!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")
83 }
84
85 let seeds: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
87
88 let consensus_version = N::CONSENSUS_VERSION(registers.state().block_height())?;
96 let transition_id = registers
97 .transition_id()
98 .ok_or_else(|| anyhow!("'rand.chacha' requires a transition ID, which is not available in this scope"))?;
99 let preimage = if (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
100 to_bits_le![
101 registers.state().random_seed(),
102 **transition_id,
103 stack.program_id(),
104 registers.function_name(),
105 self.destination.locator(),
106 self.destination_type.type_id(),
107 seeds
108 ]
109 } else {
110 let nonce = registers
111 .nonce()
112 .ok_or_else(|| anyhow!("'rand.chacha' requires a nonce, which is not available in this scope"))?;
113 to_bits_le![
114 registers.state().random_seed(),
115 **transition_id,
116 stack.program_id(),
117 registers.function_name(),
118 nonce,
119 self.destination.locator(),
120 self.destination_type.type_id(),
121 seeds
122 ]
123 };
124
125 let digest = N::hash_bhp1024(&preimage)?.to_bytes_le()?;
127 ensure!(digest.len() == 32, "The digest for the ChaChaRng seed must be 32-bytes");
129
130 let mut chacha_seed = [0u8; 32];
132 chacha_seed.copy_from_slice(&digest[..32]);
133
134 let mut rng = rand_chacha::ChaCha20Rng::from_seed(chacha_seed);
136
137 let output = match self.destination_type {
139 LiteralType::Address => Literal::Address(Address::new(Group::rand(&mut rng))),
140 LiteralType::Boolean => Literal::Boolean(Boolean::rand(&mut rng)),
141 LiteralType::Field => Literal::Field(Field::rand(&mut rng)),
142 LiteralType::Group => Literal::Group(Group::rand(&mut rng)),
143 LiteralType::I8 => Literal::I8(I8::rand(&mut rng)),
144 LiteralType::I16 => Literal::I16(I16::rand(&mut rng)),
145 LiteralType::I32 => Literal::I32(I32::rand(&mut rng)),
146 LiteralType::I64 => Literal::I64(I64::rand(&mut rng)),
147 LiteralType::I128 => Literal::I128(I128::rand(&mut rng)),
148 LiteralType::U8 => Literal::U8(U8::rand(&mut rng)),
149 LiteralType::U16 => Literal::U16(U16::rand(&mut rng)),
150 LiteralType::U32 => Literal::U32(U32::rand(&mut rng)),
151 LiteralType::U64 => Literal::U64(U64::rand(&mut rng)),
152 LiteralType::U128 => Literal::U128(U128::rand(&mut rng)),
153 LiteralType::Scalar => Literal::Scalar(Scalar::rand(&mut rng)),
154 LiteralType::Signature => bail!("Cannot 'rand.chacha' into a 'signature'"),
155 LiteralType::String => bail!("Cannot 'rand.chacha' into a 'string'"),
156 LiteralType::Identifier => bail!("Cannot 'rand.chacha' into an 'identifier'"),
157 };
158
159 registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
161 }
162}
163
164impl<N: Network> Parser for RandChaCha<N> {
165 #[inline]
167 fn parse(string: &str) -> ParserResult<Self> {
168 fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
170 let (string, _) = Sanitizer::parse_whitespaces(string)?;
172 Operand::parse(string)
174 }
175
176 let (string, _) = Sanitizer::parse(string)?;
178 let (string, _) = tag(*Self::opcode())(string)?;
180 let (string, operands) = many0(parse_operand)(string)?;
182
183 let (string, _) = Sanitizer::parse_whitespaces(string)?;
185 let (string, _) = tag("into")(string)?;
187 let (string, _) = Sanitizer::parse_whitespaces(string)?;
189 let (string, destination) = Register::parse(string)?;
191 let (string, _) = Sanitizer::parse_whitespaces(string)?;
193 let (string, _) = tag("as")(string)?;
195 let (string, _) = Sanitizer::parse_whitespaces(string)?;
197 let (string, destination_type) = LiteralType::parse(string)?;
199
200 let (string, _) = Sanitizer::parse_whitespaces(string)?;
202 let (string, _) = tag(";")(string)?;
204
205 if matches!(destination_type, LiteralType::String | LiteralType::Identifier) {
207 return map_res(fail, |_: ParserResult<Self>| {
208 Err(error(format!("Failed to parse 'rand.chacha': '{destination_type}' is invalid")))
209 })(string);
210 }
211
212 match operands.len() <= MAX_ADDITIONAL_SEEDS {
213 true => Ok((string, Self { operands, destination, destination_type })),
214 false => map_res(fail, |_: ParserResult<Self>| {
215 Err(error("Failed to parse 'rand.chacha' opcode: too many operands"))
216 })(string),
217 }
218 }
219}
220
221impl<N: Network> FromStr for RandChaCha<N> {
222 type Err = Error;
223
224 #[inline]
226 fn from_str(string: &str) -> Result<Self> {
227 match Self::parse(string) {
228 Ok((remainder, object)) => {
229 ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
231 Ok(object)
233 }
234 Err(error) => bail!("Failed to parse string. {error}"),
235 }
236 }
237}
238
239impl<N: Network> Debug for RandChaCha<N> {
240 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
242 Display::fmt(self, f)
243 }
244}
245
246impl<N: Network> Display for RandChaCha<N> {
247 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
249 if self.operands.len() > MAX_ADDITIONAL_SEEDS {
251 return Err(fmt::Error);
252 }
253
254 write!(f, "{} ", Self::opcode())?;
256 self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
257 write!(f, "into {} as {};", self.destination, self.destination_type)
258 }
259}
260
261impl<N: Network> FromBytes for RandChaCha<N> {
262 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
264 let num_operands = u8::read_le(&mut reader)? as usize;
266
267 if num_operands > MAX_ADDITIONAL_SEEDS {
269 return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
270 }
271
272 let mut operands = Vec::with_capacity(num_operands);
274 for _ in 0..num_operands {
276 operands.push(Operand::read_le(&mut reader)?);
277 }
278
279 let destination = Register::read_le(&mut reader)?;
281 let destination_type = LiteralType::read_le(&mut reader)?;
283
284 if matches!(destination_type, LiteralType::String | LiteralType::Identifier) {
286 return Err(error(format!("Failed to parse 'rand.chacha': '{destination_type}' is invalid")));
287 }
288
289 Ok(Self { operands, destination, destination_type })
291 }
292}
293
294impl<N: Network> ToBytes for RandChaCha<N> {
295 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
297 if self.operands.len() > MAX_ADDITIONAL_SEEDS {
299 return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
300 }
301
302 u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
304 self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
306 self.destination.write_le(&mut writer)?;
308 self.destination_type.write_le(&mut writer)
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use console::{network::MainnetV0, program::Register};
317
318 type CurrentNetwork = MainnetV0;
319
320 fn valid_destination_types() -> &'static [LiteralType] {
321 &[
322 LiteralType::Address,
323 LiteralType::Boolean,
324 LiteralType::Field,
325 LiteralType::Group,
326 LiteralType::I8,
327 LiteralType::I16,
328 LiteralType::I32,
329 LiteralType::I64,
330 LiteralType::I128,
331 LiteralType::U8,
332 LiteralType::U16,
333 LiteralType::U32,
334 LiteralType::U64,
335 LiteralType::U128,
336 LiteralType::Scalar,
337 ]
338 }
339
340 #[test]
341 fn test_parse() {
342 for destination_type in valid_destination_types() {
343 let instruction = format!("rand.chacha into r1 as {destination_type};");
344 let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
345 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
346 assert_eq!(rand.operands.len(), 0, "The number of operands is incorrect");
347 assert_eq!(rand.destination, Register::Locator(1), "The destination is incorrect");
348 assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
349
350 let instruction = format!("rand.chacha r0 into r1 as {destination_type};");
351 let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
352 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
353 assert_eq!(rand.operands.len(), 1, "The number of operands is incorrect");
354 assert_eq!(rand.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
355 assert_eq!(rand.destination, Register::Locator(1), "The second operand is incorrect");
356 assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
357
358 let instruction = format!("rand.chacha r0 r1 into r2 as {destination_type};");
359 let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
360 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
361 assert_eq!(rand.operands.len(), 2, "The number of operands is incorrect");
362 assert_eq!(rand.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
363 assert_eq!(rand.operands[1], Operand::Register(Register::Locator(1)), "The first operand is incorrect");
364 assert_eq!(rand.destination, Register::Locator(2), "The second operand is incorrect");
365 assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
366 }
367 }
368}