Skip to main content

snarkvm_synthesizer_program/logic/command/
rand_chacha.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use 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
25/// The maximum number of additional seeds that can be provided.
26pub const MAX_ADDITIONAL_SEEDS: usize = 2;
27
28/// A random-number generator command, e.g. `rand.chacha into r1 as field;` or
29/// `rand.chacha r0 into r1 as field;`, with the latter including an optional additional seed(s).
30///
31/// This command samples a deterministic and unique element, and stores the result in `destination`.
32/// When the optional operand(s) are provided, it is used as additional seed(s) to the
33/// random-number generator. Note that the maximum number of additional seeds is currently 2.
34#[derive(Clone, PartialEq, Eq, Hash)]
35pub struct RandChaCha<N: Network> {
36    /// The operand(s) as `seed(s)`.
37    operands: Vec<Operand<N>>,
38    /// The destination register.
39    destination: Register<N>,
40    /// The destination register type.
41    destination_type: LiteralType,
42}
43
44impl<N: Network> RandChaCha<N> {
45    /// Returns the opcode.
46    #[inline]
47    pub const fn opcode() -> Opcode {
48        Opcode::Command("rand.chacha")
49    }
50
51    /// Returns the operands in the operation.
52    #[inline]
53    pub fn operands(&self) -> &[Operand<N>] {
54        &self.operands
55    }
56
57    /// Returns the destination register.
58    #[inline]
59    pub const fn destination(&self) -> &Register<N> {
60        &self.destination
61    }
62
63    /// Returns the destination register type.
64    #[inline]
65    pub const fn destination_type(&self) -> LiteralType {
66        self.destination_type
67    }
68
69    /// Returns whether this command refers to an external struct.
70    #[inline]
71    pub fn contains_external_struct(&self) -> bool {
72        false
73    }
74}
75
76impl<N: Network> RandChaCha<N> {
77    /// Finalizes the command.
78    #[inline]
79    pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl FinalizeRegistersState<N>) -> Result<()> {
80        // Ensure the number of operands is within bounds.
81        if self.operands.len() > MAX_ADDITIONAL_SEEDS {
82            bail!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")
83        }
84
85        // Load the operands values.
86        let seeds: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
87
88        // Construct the random seed.
89        // If the height is greater than or equal to consensus V3, then use the new preimage definition.
90        // The difference is that a nonce is also included in the new definition.
91        //
92        // `transition_id` and `nonce` are `Option`s on the trait — the view path leaves both as
93        // `None`. Views already reject `rand.chacha` at construction, so reaching this code on
94        // a view is unreachable, but we surface a clear runtime error here as a defense in depth.
95        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        // Hash the preimage.
126        let digest = N::hash_bhp1024(&preimage)?.to_bytes_le()?;
127        // Ensure the digest is 32-bytes.
128        ensure!(digest.len() == 32, "The digest for the ChaChaRng seed must be 32-bytes");
129
130        // Construct the ChaChaRng seed.
131        let mut chacha_seed = [0u8; 32];
132        chacha_seed.copy_from_slice(&digest[..32]);
133
134        // Construct the ChaChaRng.
135        let mut rng = rand_chacha::ChaCha20Rng::from_seed(chacha_seed);
136
137        // Sample a random element.
138        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        // Assign the value to the destination register.
160        registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
161    }
162}
163
164impl<N: Network> Parser for RandChaCha<N> {
165    /// Parses a string into an operation.
166    #[inline]
167    fn parse(string: &str) -> ParserResult<Self> {
168        /// Parses an operand from the string.
169        fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
170            // Parse the whitespace from the string.
171            let (string, _) = Sanitizer::parse_whitespaces(string)?;
172            // Parse the operand from the string.
173            Operand::parse(string)
174        }
175
176        // Parse the whitespace and comments from the string.
177        let (string, _) = Sanitizer::parse(string)?;
178        // Parse the opcode from the string.
179        let (string, _) = tag(*Self::opcode())(string)?;
180        // Parse the operands from the string.
181        let (string, operands) = many0(parse_operand)(string)?;
182
183        // Parse the whitespace from the string.
184        let (string, _) = Sanitizer::parse_whitespaces(string)?;
185        // Parse the "into" keyword from the string.
186        let (string, _) = tag("into")(string)?;
187        // Parse the whitespace from the string.
188        let (string, _) = Sanitizer::parse_whitespaces(string)?;
189        // Parse the destination register from the string.
190        let (string, destination) = Register::parse(string)?;
191        // Parse the whitespace from the string.
192        let (string, _) = Sanitizer::parse_whitespaces(string)?;
193        // Parse the "as" from the string.
194        let (string, _) = tag("as")(string)?;
195        // Parse the whitespace from the string.
196        let (string, _) = Sanitizer::parse_whitespaces(string)?;
197        // Parse the destination register type from the string.
198        let (string, destination_type) = LiteralType::parse(string)?;
199
200        // Parse the whitespace from the string.
201        let (string, _) = Sanitizer::parse_whitespaces(string)?;
202        // Parse the ";" from the string.
203        let (string, _) = tag(";")(string)?;
204
205        // Ensure the destination type is allowed.
206        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    /// Parses a string into the command.
225    #[inline]
226    fn from_str(string: &str) -> Result<Self> {
227        match Self::parse(string) {
228            Ok((remainder, object)) => {
229                // Ensure the remainder is empty.
230                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
231                // Return the object.
232                Ok(object)
233            }
234            Err(error) => bail!("Failed to parse string. {error}"),
235        }
236    }
237}
238
239impl<N: Network> Debug for RandChaCha<N> {
240    /// Prints the command as a string.
241    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
242        Display::fmt(self, f)
243    }
244}
245
246impl<N: Network> Display for RandChaCha<N> {
247    /// Prints the command to a string.
248    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
249        // Ensure the number of operands is within the bounds.
250        if self.operands.len() > MAX_ADDITIONAL_SEEDS {
251            return Err(fmt::Error);
252        }
253
254        // Print the command.
255        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    /// Reads the command from a buffer.
263    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
264        // Read the number of operands.
265        let num_operands = u8::read_le(&mut reader)? as usize;
266
267        // Ensure that the number of operands does not exceed the upper bound.
268        if num_operands > MAX_ADDITIONAL_SEEDS {
269            return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
270        }
271
272        // Initialize the vector for the operands.
273        let mut operands = Vec::with_capacity(num_operands);
274        // Read the operands.
275        for _ in 0..num_operands {
276            operands.push(Operand::read_le(&mut reader)?);
277        }
278
279        // Read the destination register.
280        let destination = Register::read_le(&mut reader)?;
281        // Read the destination register type.
282        let destination_type = LiteralType::read_le(&mut reader)?;
283
284        // Ensure the destination type is allowed.
285        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        // Return the command.
290        Ok(Self { operands, destination, destination_type })
291    }
292}
293
294impl<N: Network> ToBytes for RandChaCha<N> {
295    /// Writes the operation to a buffer.
296    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
297        // Ensure the number of operands is within the bounds.
298        if self.operands.len() > MAX_ADDITIONAL_SEEDS {
299            return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
300        }
301
302        // Write the number of operands.
303        u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
304        // Write the operands.
305        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
306        // Write the destination register.
307        self.destination.write_le(&mut writer)?;
308        // Write the destination register type.
309        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}