snarkvm_synthesizer_program/logic/command/
rand_chacha.rsuse crate::{
FinalizeRegistersState,
Opcode,
Operand,
traits::{RegistersLoad, RegistersStore, StackMatches, StackProgram},
};
use console::{
network::prelude::*,
program::{Literal, LiteralType, Plaintext, Register, Value},
types::{Address, Boolean, Field, Group, I8, I16, I32, I64, I128, Scalar, U8, U16, U32, U64, U128},
};
use rand::SeedableRng;
pub const MAX_ADDITIONAL_SEEDS: usize = 2;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct RandChaCha<N: Network> {
operands: Vec<Operand<N>>,
destination: Register<N>,
destination_type: LiteralType,
}
impl<N: Network> RandChaCha<N> {
#[inline]
pub const fn opcode() -> Opcode {
Opcode::Command("rand.chacha")
}
#[inline]
pub fn operands(&self) -> Vec<Operand<N>> {
self.operands.clone()
}
#[inline]
pub const fn destination(&self) -> &Register<N> {
&self.destination
}
#[inline]
pub const fn destination_type(&self) -> LiteralType {
self.destination_type
}
}
impl<N: Network> RandChaCha<N> {
#[inline]
pub fn finalize(
&self,
stack: &(impl StackMatches<N> + StackProgram<N>),
registers: &mut (impl RegistersLoad<N> + RegistersStore<N> + FinalizeRegistersState<N>),
) -> Result<()> {
if self.operands.len() > MAX_ADDITIONAL_SEEDS {
bail!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")
}
let seeds: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
let preimage = to_bits_le![
registers.state().random_seed(),
**registers.transition_id(),
stack.program_id(),
registers.function_name(),
self.destination.locator(),
self.destination_type.type_id(),
seeds
];
let digest = N::hash_bhp1024(&preimage)?.to_bytes_le()?;
ensure!(digest.len() == 32, "The digest for the ChaChaRng seed must be 32-bytes");
let mut chacha_seed = [0u8; 32];
chacha_seed.copy_from_slice(&digest[..32]);
let mut rng = rand_chacha::ChaCha20Rng::from_seed(chacha_seed);
let output = match self.destination_type {
LiteralType::Address => Literal::Address(Address::new(Group::rand(&mut rng))),
LiteralType::Boolean => Literal::Boolean(Boolean::rand(&mut rng)),
LiteralType::Field => Literal::Field(Field::rand(&mut rng)),
LiteralType::Group => Literal::Group(Group::rand(&mut rng)),
LiteralType::I8 => Literal::I8(I8::rand(&mut rng)),
LiteralType::I16 => Literal::I16(I16::rand(&mut rng)),
LiteralType::I32 => Literal::I32(I32::rand(&mut rng)),
LiteralType::I64 => Literal::I64(I64::rand(&mut rng)),
LiteralType::I128 => Literal::I128(I128::rand(&mut rng)),
LiteralType::U8 => Literal::U8(U8::rand(&mut rng)),
LiteralType::U16 => Literal::U16(U16::rand(&mut rng)),
LiteralType::U32 => Literal::U32(U32::rand(&mut rng)),
LiteralType::U64 => Literal::U64(U64::rand(&mut rng)),
LiteralType::U128 => Literal::U128(U128::rand(&mut rng)),
LiteralType::Scalar => Literal::Scalar(Scalar::rand(&mut rng)),
LiteralType::Signature => bail!("Cannot 'rand.chacha' into a 'signature'"),
LiteralType::String => bail!("Cannot 'rand.chacha' into a 'string'"),
};
registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
}
}
impl<N: Network> Parser for RandChaCha<N> {
#[inline]
fn parse(string: &str) -> ParserResult<Self> {
fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
let (string, _) = Sanitizer::parse_whitespaces(string)?;
Operand::parse(string)
}
let (string, _) = Sanitizer::parse(string)?;
let (string, _) = tag(*Self::opcode())(string)?;
let (string, operands) = many0(parse_operand)(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag("into")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, destination) = Register::parse(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag("as")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, destination_type) = LiteralType::parse(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag(";")(string)?;
if destination_type == LiteralType::String {
return map_res(fail, |_: ParserResult<Self>| {
Err(error(format!("Failed to parse 'rand.chacha': '{destination_type}' is invalid")))
})(string);
}
match operands.len() <= MAX_ADDITIONAL_SEEDS {
true => Ok((string, Self { operands, destination, destination_type })),
false => map_res(fail, |_: ParserResult<Self>| {
Err(error("Failed to parse 'rand.chacha' opcode: too many operands"))
})(string),
}
}
}
impl<N: Network> FromStr for RandChaCha<N> {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<Self> {
match Self::parse(string) {
Ok((remainder, object)) => {
ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
Ok(object)
}
Err(error) => bail!("Failed to parse string. {error}"),
}
}
}
impl<N: Network> Debug for RandChaCha<N> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl<N: Network> Display for RandChaCha<N> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.operands.len() > MAX_ADDITIONAL_SEEDS {
return Err(fmt::Error);
}
write!(f, "{} ", Self::opcode())?;
self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
write!(f, "into {} as {};", self.destination, self.destination_type)
}
}
impl<N: Network> FromBytes for RandChaCha<N> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let num_operands = u8::read_le(&mut reader)? as usize;
if num_operands > MAX_ADDITIONAL_SEEDS {
return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
}
let mut operands = Vec::with_capacity(num_operands);
for _ in 0..num_operands {
operands.push(Operand::read_le(&mut reader)?);
}
let destination = Register::read_le(&mut reader)?;
let destination_type = LiteralType::read_le(&mut reader)?;
if destination_type == LiteralType::String {
return Err(error(format!("Failed to parse 'rand.chacha': '{destination_type}' is invalid")));
}
Ok(Self { operands, destination, destination_type })
}
}
impl<N: Network> ToBytes for RandChaCha<N> {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
if self.operands.len() > MAX_ADDITIONAL_SEEDS {
return Err(error(format!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}")));
}
u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
self.destination.write_le(&mut writer)?;
self.destination_type.write_le(&mut writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use console::{network::MainnetV0, program::Register};
type CurrentNetwork = MainnetV0;
fn valid_destination_types() -> &'static [LiteralType] {
&[
LiteralType::Address,
LiteralType::Boolean,
LiteralType::Field,
LiteralType::Group,
LiteralType::I8,
LiteralType::I16,
LiteralType::I32,
LiteralType::I64,
LiteralType::I128,
LiteralType::U8,
LiteralType::U16,
LiteralType::U32,
LiteralType::U64,
LiteralType::U128,
LiteralType::Scalar,
]
}
#[test]
fn test_parse() {
for destination_type in valid_destination_types() {
let instruction = format!("rand.chacha into r1 as {destination_type};");
let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
assert_eq!(rand.operands.len(), 0, "The number of operands is incorrect");
assert_eq!(rand.destination, Register::Locator(1), "The destination is incorrect");
assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
let instruction = format!("rand.chacha r0 into r1 as {destination_type};");
let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
assert_eq!(rand.operands.len(), 1, "The number of operands is incorrect");
assert_eq!(rand.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
assert_eq!(rand.destination, Register::Locator(1), "The second operand is incorrect");
assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
let instruction = format!("rand.chacha r0 r1 into r2 as {destination_type};");
let (string, rand) = RandChaCha::<CurrentNetwork>::parse(&instruction).unwrap();
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
assert_eq!(rand.operands.len(), 2, "The number of operands is incorrect");
assert_eq!(rand.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
assert_eq!(rand.operands[1], Operand::Register(Register::Locator(1)), "The first operand is incorrect");
assert_eq!(rand.destination, Register::Locator(2), "The second operand is incorrect");
assert_eq!(rand.destination_type, *destination_type, "The destination type is incorrect");
}
}
}