snarkvm_synthesizer_program/logic/command/
rand_chacha.rs

1// Copyright (c) 2019-2025 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::{
17    FinalizeRegistersState,
18    Opcode,
19    Operand,
20    traits::{RegistersLoad, RegistersStore, StackMatches, StackProgram},
21};
22use console::{
23    network::prelude::*,
24    program::{Literal, LiteralType, Plaintext, Register, Value},
25    types::{Address, Boolean, Field, Group, I8, I16, I32, I64, I128, Scalar, U8, U16, U32, U64, U128},
26};
27
28use rand::SeedableRng;
29
30/// The maximum number of additional seeds that can be provided.
31pub const MAX_ADDITIONAL_SEEDS: usize = 2;
32
33/// A random-number generator command, e.g. `rand.chacha into r1 as field;` or
34/// `rand.chacha r0 into r1 as field;`, with the latter including an optional additional seed(s).
35///
36/// This command samples a deterministic and unique element, and stores the result in `destination`.
37/// When the optional operand(s) are provided, it is used as additional seed(s) to the
38/// random-number generator. Note that the maximum number of additional seeds is currently 2.
39#[derive(Clone, PartialEq, Eq, Hash)]
40pub struct RandChaCha<N: Network> {
41    /// The operand(s) as `seed(s)`.
42    operands: Vec<Operand<N>>,
43    /// The destination register.
44    destination: Register<N>,
45    /// The destination register type.
46    destination_type: LiteralType,
47}
48
49impl<N: Network> RandChaCha<N> {
50    /// Returns the opcode.
51    #[inline]
52    pub const fn opcode() -> Opcode {
53        Opcode::Command("rand.chacha")
54    }
55
56    /// Returns the operands in the operation.
57    #[inline]
58    pub fn operands(&self) -> Vec<Operand<N>> {
59        self.operands.clone()
60    }
61
62    /// Returns the destination register.
63    #[inline]
64    pub const fn destination(&self) -> &Register<N> {
65        &self.destination
66    }
67
68    /// Returns the destination register type.
69    #[inline]
70    pub const fn destination_type(&self) -> LiteralType {
71        self.destination_type
72    }
73}
74
75impl<N: Network> RandChaCha<N> {
76    /// Finalizes the command.
77    #[inline]
78    pub fn finalize(
79        &self,
80        stack: &(impl StackMatches<N> + StackProgram<N>),
81        registers: &mut (impl RegistersLoad<N> + RegistersStore<N> + FinalizeRegistersState<N>),
82    ) -> Result<()> {
83        // Ensure the number of operands is within bounds.
84        if self.operands.len() > MAX_ADDITIONAL_SEEDS {
85            bail!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")
86        }
87
88        // Load the operands values.
89        let seeds: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
90
91        // Construct the random seed.
92        // If the height is greater than or equal to consensus V3, then use the new preimage definition.
93        // The difference is that a nonce is also included in the new definition.
94        let consensus_version = N::CONSENSUS_VERSION(registers.state().block_height())?;
95        let preimage = if (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
96            to_bits_le![
97                registers.state().random_seed(),
98                **registers.transition_id(),
99                stack.program_id(),
100                registers.function_name(),
101                self.destination.locator(),
102                self.destination_type.type_id(),
103                seeds
104            ]
105        } else {
106            to_bits_le![
107                registers.state().random_seed(),
108                **registers.transition_id(),
109                stack.program_id(),
110                registers.function_name(),
111                registers.nonce(),
112                self.destination.locator(),
113                self.destination_type.type_id(),
114                seeds
115            ]
116        };
117
118        // Hash the preimage.
119        let digest = N::hash_bhp1024(&preimage)?.to_bytes_le()?;
120        // Ensure the digest is 32-bytes.
121        ensure!(digest.len() == 32, "The digest for the ChaChaRng seed must be 32-bytes");
122
123        // Construct the ChaChaRng seed.
124        let mut chacha_seed = [0u8; 32];
125        chacha_seed.copy_from_slice(&digest[..32]);
126
127        // Construct the ChaChaRng.
128        let mut rng = rand_chacha::ChaCha20Rng::from_seed(chacha_seed);
129
130        // Sample a random element.
131        let output = match self.destination_type {
132            LiteralType::Address => Literal::Address(Address::new(Group::rand(&mut rng))),
133            LiteralType::Boolean => Literal::Boolean(Boolean::rand(&mut rng)),
134            LiteralType::Field => Literal::Field(Field::rand(&mut rng)),
135            LiteralType::Group => Literal::Group(Group::rand(&mut rng)),
136            LiteralType::I8 => Literal::I8(I8::rand(&mut rng)),
137            LiteralType::I16 => Literal::I16(I16::rand(&mut rng)),
138            LiteralType::I32 => Literal::I32(I32::rand(&mut rng)),
139            LiteralType::I64 => Literal::I64(I64::rand(&mut rng)),
140            LiteralType::I128 => Literal::I128(I128::rand(&mut rng)),
141            LiteralType::U8 => Literal::U8(U8::rand(&mut rng)),
142            LiteralType::U16 => Literal::U16(U16::rand(&mut rng)),
143            LiteralType::U32 => Literal::U32(U32::rand(&mut rng)),
144            LiteralType::U64 => Literal::U64(U64::rand(&mut rng)),
145            LiteralType::U128 => Literal::U128(U128::rand(&mut rng)),
146            LiteralType::Scalar => Literal::Scalar(Scalar::rand(&mut rng)),
147            LiteralType::Signature => bail!("Cannot 'rand.chacha' into a 'signature'"),
148            LiteralType::String => bail!("Cannot 'rand.chacha' into a 'string'"),
149        };
150
151        // Assign the value to the destination register.
152        registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
153    }
154}
155
156impl<N: Network> Parser for RandChaCha<N> {
157    /// Parses a string into an operation.
158    #[inline]
159    fn parse(string: &str) -> ParserResult<Self> {
160        /// Parses an operand from the string.
161        fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
162            // Parse the whitespace from the string.
163            let (string, _) = Sanitizer::parse_whitespaces(string)?;
164            // Parse the operand from the string.
165            Operand::parse(string)
166        }
167
168        // Parse the whitespace and comments from the string.
169        let (string, _) = Sanitizer::parse(string)?;
170        // Parse the opcode from the string.
171        let (string, _) = tag(*Self::opcode())(string)?;
172        // Parse the operands from the string.
173        let (string, operands) = many0(parse_operand)(string)?;
174
175        // Parse the whitespace from the string.
176        let (string, _) = Sanitizer::parse_whitespaces(string)?;
177        // Parse the "into" keyword from the string.
178        let (string, _) = tag("into")(string)?;
179        // Parse the whitespace from the string.
180        let (string, _) = Sanitizer::parse_whitespaces(string)?;
181        // Parse the destination register from the string.
182        let (string, destination) = Register::parse(string)?;
183        // Parse the whitespace from the string.
184        let (string, _) = Sanitizer::parse_whitespaces(string)?;
185        // Parse the "as" from the string.
186        let (string, _) = tag("as")(string)?;
187        // Parse the whitespace from the string.
188        let (string, _) = Sanitizer::parse_whitespaces(string)?;
189        // Parse the destination register type from the string.
190        let (string, destination_type) = LiteralType::parse(string)?;
191
192        // Parse the whitespace from the string.
193        let (string, _) = Sanitizer::parse_whitespaces(string)?;
194        // Parse the ";" from the string.
195        let (string, _) = tag(";")(string)?;
196
197        // Ensure the destination type is allowed.
198        if destination_type == LiteralType::String {
199            return map_res(fail, |_: ParserResult<Self>| {
200                Err(error(format!("Failed to parse 'rand.chacha': '{destination_type}' is invalid")))
201            })(string);
202        }
203
204        match operands.len() <= MAX_ADDITIONAL_SEEDS {
205            true => Ok((string, Self { operands, destination, destination_type })),
206            false => map_res(fail, |_: ParserResult<Self>| {
207                Err(error("Failed to parse 'rand.chacha' opcode: too many operands"))
208            })(string),
209        }
210    }
211}
212
213impl<N: Network> FromStr for RandChaCha<N> {
214    type Err = Error;
215
216    /// Parses a string into the command.
217    #[inline]
218    fn from_str(string: &str) -> Result<Self> {
219        match Self::parse(string) {
220            Ok((remainder, object)) => {
221                // Ensure the remainder is empty.
222                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
223                // Return the object.
224                Ok(object)
225            }
226            Err(error) => bail!("Failed to parse string. {error}"),
227        }
228    }
229}
230
231impl<N: Network> Debug for RandChaCha<N> {
232    /// Prints the command as a string.
233    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
234        Display::fmt(self, f)
235    }
236}
237
238impl<N: Network> Display for RandChaCha<N> {
239    /// Prints the command to a string.
240    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
241        // Ensure the number of operands is within the bounds.
242        if self.operands.len() > MAX_ADDITIONAL_SEEDS {
243            return Err(fmt::Error);
244        }
245
246        // Print the command.
247        write!(f, "{} ", Self::opcode())?;
248        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
249        write!(f, "into {} as {};", self.destination, self.destination_type)
250    }
251}
252
253impl<N: Network> FromBytes for RandChaCha<N> {
254    /// Reads the command from a buffer.
255    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
256        // Read the number of operands.
257        let num_operands = u8::read_le(&mut reader)? as usize;
258
259        // Ensure that the number of operands does not exceed the upper bound.
260        if num_operands > MAX_ADDITIONAL_SEEDS {
261            return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
262        }
263
264        // Initialize the vector for the operands.
265        let mut operands = Vec::with_capacity(num_operands);
266        // Read the operands.
267        for _ in 0..num_operands {
268            operands.push(Operand::read_le(&mut reader)?);
269        }
270
271        // Read the destination register.
272        let destination = Register::read_le(&mut reader)?;
273        // Read the destination register type.
274        let destination_type = LiteralType::read_le(&mut reader)?;
275
276        // Ensure the destination type is allowed.
277        if destination_type == LiteralType::String {
278            return Err(error(format!("Failed to parse 'rand.chacha': '{destination_type}' is invalid")));
279        }
280
281        // Return the command.
282        Ok(Self { operands, destination, destination_type })
283    }
284}
285
286impl<N: Network> ToBytes for RandChaCha<N> {
287    /// Writes the operation to a buffer.
288    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
289        // Ensure the number of operands is within the bounds.
290        if self.operands.len() > MAX_ADDITIONAL_SEEDS {
291            return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
292        }
293
294        // Write the number of operands.
295        u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
296        // Write the operands.
297        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
298        // Write the destination register.
299        self.destination.write_le(&mut writer)?;
300        // Write the destination register type.
301        self.destination_type.write_le(&mut writer)
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use console::{network::MainnetV0, program::Register};
309
310    type CurrentNetwork = MainnetV0;
311
312    fn valid_destination_types() -> &'static [LiteralType] {
313        &[
314            LiteralType::Address,
315            LiteralType::Boolean,
316            LiteralType::Field,
317            LiteralType::Group,
318            LiteralType::I8,
319            LiteralType::I16,
320            LiteralType::I32,
321            LiteralType::I64,
322            LiteralType::I128,
323            LiteralType::U8,
324            LiteralType::U16,
325            LiteralType::U32,
326            LiteralType::U64,
327            LiteralType::U128,
328            LiteralType::Scalar,
329        ]
330    }
331
332    #[test]
333    fn test_parse() {
334        for destination_type in valid_destination_types() {
335            let instruction = format!("rand.chacha into r1 as {destination_type};");
336            let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
337            assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
338            assert_eq!(rand.operands.len(), 0, "The number of operands is incorrect");
339            assert_eq!(rand.destination, Register::Locator(1), "The destination is incorrect");
340            assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
341
342            let instruction = format!("rand.chacha r0 into r1 as {destination_type};");
343            let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
344            assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
345            assert_eq!(rand.operands.len(), 1, "The number of operands is incorrect");
346            assert_eq!(rand.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
347            assert_eq!(rand.destination, Register::Locator(1), "The second operand is incorrect");
348            assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
349
350            let instruction = format!("rand.chacha r0 r1 into r2 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(), 2, "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.operands[1], Operand::Register(Register::Locator(1)), "The first operand is incorrect");
356            assert_eq!(rand.destination, Register::Locator(2), "The second operand is incorrect");
357            assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
358        }
359    }
360}