use crate::{
Opcode,
Operand,
RegistersLoad,
RegistersLoadCircuit,
RegistersStore,
RegistersStoreCircuit,
StackMatches,
StackProgram,
};
use console::{
network::prelude::*,
program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Value},
};
pub type HashBHP256<N> = HashInstruction<N, { Hasher::BHP256 as u8 }>;
pub type HashBHP512<N> = HashInstruction<N, { Hasher::BHP512 as u8 }>;
pub type HashBHP768<N> = HashInstruction<N, { Hasher::BHP768 as u8 }>;
pub type HashBHP1024<N> = HashInstruction<N, { Hasher::BHP1024 as u8 }>;
pub type HashPED64<N> = HashInstruction<N, { Hasher::PED64 as u8 }>;
pub type HashPED128<N> = HashInstruction<N, { Hasher::PED128 as u8 }>;
pub type HashPSD2<N> = HashInstruction<N, { Hasher::PSD2 as u8 }>;
pub type HashPSD4<N> = HashInstruction<N, { Hasher::PSD4 as u8 }>;
pub type HashPSD8<N> = HashInstruction<N, { Hasher::PSD8 as u8 }>;
enum Hasher {
BHP256,
BHP512,
BHP768,
BHP1024,
PED64,
PED128,
PSD2,
PSD4,
PSD8,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct HashInstruction<N: Network, const VARIANT: u8> {
operands: Vec<Operand<N>>,
destination: Register<N>,
}
impl<N: Network, const VARIANT: u8> HashInstruction<N, VARIANT> {
#[inline]
pub const fn opcode() -> Opcode {
match VARIANT {
0 => Opcode::Hash("hash.bhp256"),
1 => Opcode::Hash("hash.bhp512"),
2 => Opcode::Hash("hash.bhp768"),
3 => Opcode::Hash("hash.bhp1024"),
4 => Opcode::Hash("hash.ped64"),
5 => Opcode::Hash("hash.ped128"),
6 => Opcode::Hash("hash.psd2"),
7 => Opcode::Hash("hash.psd4"),
8 => Opcode::Hash("hash.psd8"),
_ => panic!("Invalid 'hash' instruction opcode"),
}
}
#[inline]
pub fn operands(&self) -> &[Operand<N>] {
debug_assert!(self.operands.len() == 1, "Hash operation must have one operand");
&self.operands
}
#[inline]
pub fn destinations(&self) -> Vec<Register<N>> {
vec![self.destination.clone()]
}
}
impl<N: Network, const VARIANT: u8> HashInstruction<N, VARIANT> {
#[inline]
pub fn evaluate(
&self,
stack: &(impl StackMatches<N> + StackProgram<N>),
registers: &mut (impl RegistersLoad<N> + RegistersStore<N>),
) -> Result<()> {
if self.operands.len() != 1 {
bail!("Instruction '{}' expects 1 operands, found {} operands", Self::opcode(), self.operands.len())
}
let input = registers.load(stack, &self.operands[0])?;
let output = match VARIANT {
0 => N::hash_bhp256(&input.to_bits_le())?,
1 => N::hash_bhp512(&input.to_bits_le())?,
2 => N::hash_bhp768(&input.to_bits_le())?,
3 => N::hash_bhp1024(&input.to_bits_le())?,
4 => N::hash_ped64(&input.to_bits_le())?,
5 => N::hash_ped128(&input.to_bits_le())?,
6 => N::hash_psd2(&input.to_fields()?)?,
7 => N::hash_psd4(&input.to_fields()?)?,
8 => N::hash_psd8(&input.to_fields()?)?,
_ => bail!("Invalid 'hash' variant: {VARIANT}"),
};
registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(output))))
}
#[inline]
pub fn execute<A: circuit::Aleo<Network = N>>(
&self,
stack: &(impl StackMatches<N> + StackProgram<N>),
registers: &mut (impl RegistersLoadCircuit<N, A> + RegistersStoreCircuit<N, A>),
) -> Result<()> {
use circuit::{ToBits, ToFields};
if self.operands.len() != 1 {
bail!("Instruction '{}' expects 1 operands, found {} operands", Self::opcode(), self.operands.len())
}
let input = registers.load_circuit(stack, &self.operands[0])?;
let output = match VARIANT {
0 => A::hash_bhp256(&input.to_bits_le()),
1 => A::hash_bhp512(&input.to_bits_le()),
2 => A::hash_bhp768(&input.to_bits_le()),
3 => A::hash_bhp1024(&input.to_bits_le()),
4 => A::hash_ped64(&input.to_bits_le()),
5 => A::hash_ped128(&input.to_bits_le()),
6 => A::hash_psd2(&input.to_fields()),
7 => A::hash_psd4(&input.to_fields()),
8 => A::hash_psd8(&input.to_fields()),
_ => bail!("Invalid 'hash' variant: {VARIANT}"),
};
let output =
circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Field(output), Default::default()));
registers.store_circuit(stack, &self.destination, output)
}
#[inline]
pub fn finalize(
&self,
stack: &(impl StackMatches<N> + StackProgram<N>),
registers: &mut (impl RegistersLoad<N> + RegistersStore<N>),
) -> Result<()> {
self.evaluate(stack, registers)
}
#[inline]
pub fn output_types(
&self,
_stack: &impl StackProgram<N>,
input_types: &[RegisterType<N>],
) -> Result<Vec<RegisterType<N>>> {
if input_types.len() != 1 {
bail!("Instruction '{}' expects 1 inputs, found {} inputs", Self::opcode(), input_types.len())
}
if self.operands.len() != 1 {
bail!("Instruction '{}' expects 1 operands, found {} operands", Self::opcode(), self.operands.len())
}
match VARIANT {
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 => {
Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Field))])
}
_ => bail!("Invalid 'hash' variant: {VARIANT}"),
}
}
}
impl<N: Network, const VARIANT: u8> Parser for HashInstruction<N, VARIANT> {
#[inline]
fn parse(string: &str) -> ParserResult<Self> {
let (string, _) = tag(*Self::opcode())(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, operand) = Operand::parse(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag("into")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, destination) = Register::parse(string)?;
Ok((string, Self { operands: vec![operand], destination }))
}
}
impl<N: Network, const VARIANT: u8> FromStr for HashInstruction<N, VARIANT> {
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, const VARIANT: u8> Debug for HashInstruction<N, VARIANT> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl<N: Network, const VARIANT: u8> Display for HashInstruction<N, VARIANT> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.operands.len() != 1 {
eprintln!("The number of operands must be 1, found {}", self.operands.len());
return Err(fmt::Error);
}
write!(f, "{} ", Self::opcode())?;
self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
write!(f, "into {}", self.destination)
}
}
impl<N: Network, const VARIANT: u8> FromBytes for HashInstruction<N, VARIANT> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let operands = vec![Operand::read_le(&mut reader)?];
let destination = Register::read_le(&mut reader)?;
Ok(Self { operands, destination })
}
}
impl<N: Network, const VARIANT: u8> ToBytes for HashInstruction<N, VARIANT> {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
if self.operands.len() != 1 {
return Err(error(format!("The number of operands must be 1, found {}", self.operands.len())));
}
self.operands[0].write_le(&mut writer)?;
self.destination.write_le(&mut writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
process::Stack,
program::test_helpers::{sample_finalize_registers, sample_registers},
};
use circuit::{AleoV0, Eject};
use console::{network::Testnet3, program::Identifier};
use snarkvm_synthesizer_snark::{ProvingKey, VerifyingKey};
use std::collections::HashMap;
type CurrentNetwork = Testnet3;
type CurrentAleo = AleoV0;
const ITERATIONS: usize = 100;
#[allow(clippy::type_complexity)]
fn sample_stack(
opcode: Opcode,
type_: LiteralType,
mode: circuit::Mode,
cache: &mut HashMap<String, (ProvingKey<CurrentNetwork>, VerifyingKey<CurrentNetwork>)>,
) -> Result<(Stack<CurrentNetwork>, Vec<Operand<CurrentNetwork>>, Register<CurrentNetwork>)> {
use crate::{Process, Program};
let opcode = opcode.to_string();
let function_name = Identifier::<CurrentNetwork>::from_str("run")?;
let r0 = Register::Locator(0);
let r1 = Register::Locator(1);
let program = Program::from_str(&format!(
"program testing.aleo;
function {function_name}:
input {r0} as {type_}.{mode};
{opcode} {r0} into {r1};
finalize {r0};
finalize {function_name}:
input {r0} as {type_}.public;
{opcode} {r0} into {r1};
"
))?;
let operands = vec![Operand::Register(r0)];
let stack = Stack::new(&Process::load_with_cache(cache)?, &program)?;
Ok((stack, operands, r1))
}
fn check_hash<const VARIANT: u8>(
operation: impl FnOnce(
Vec<Operand<CurrentNetwork>>,
Register<CurrentNetwork>,
) -> HashInstruction<CurrentNetwork, VARIANT>,
opcode: Opcode,
literal: &Literal<CurrentNetwork>,
mode: &circuit::Mode,
cache: &mut HashMap<String, (ProvingKey<CurrentNetwork>, VerifyingKey<CurrentNetwork>)>,
) {
println!("Checking '{opcode}' for '{literal}.{mode}'");
let type_ = literal.to_type();
let (stack, operands, destination) = sample_stack(opcode, type_, *mode, cache).unwrap();
let operation = operation(operands, destination.clone());
let function_name = Identifier::from_str("run").unwrap();
let destination_operand = Operand::Register(destination);
let mut evaluate_registers = sample_registers(&stack, &function_name, &[(literal, None)]).unwrap();
let result_a = operation.evaluate(&stack, &mut evaluate_registers);
let mut execute_registers = sample_registers(&stack, &function_name, &[(literal, Some(*mode))]).unwrap();
let result_b = operation.execute::<CurrentAleo>(&stack, &mut execute_registers);
let mut finalize_registers = sample_finalize_registers(&stack, &function_name, &[literal]).unwrap();
let result_c = operation.finalize(&stack, &mut finalize_registers);
let all_failed = result_a.is_err() && result_b.is_err() && result_c.is_err();
let all_succeeded = result_a.is_ok() && result_b.is_ok() && result_c.is_ok();
assert!(
all_failed || all_succeeded,
"The results of the evaluation, execution, and finalization should either all succeed or all fail"
);
if all_succeeded {
let output_a = evaluate_registers.load(&stack, &destination_operand).unwrap();
let output_b = execute_registers.load_circuit(&stack, &destination_operand).unwrap();
let output_c = finalize_registers.load(&stack, &destination_operand).unwrap();
assert_eq!(
output_a,
output_b.eject_value(),
"The results of the evaluation and execution are inconsistent"
);
assert_eq!(output_a, output_c, "The results of the evaluation and finalization are inconsistent");
}
<CurrentAleo as circuit::Environment>::reset();
}
macro_rules! test_hash {
($name: tt, $hash:ident) => {
paste::paste! {
#[test]
fn [<test _ $name _ is _ consistent>]() {
let operation = |operands, destination| $hash::<CurrentNetwork> { operands, destination };
let opcode = $hash::<CurrentNetwork>::opcode();
let mut rng = TestRng::default();
let modes = [circuit::Mode::Public, circuit::Mode::Private];
let mut cache = Default::default();
for _ in 0..ITERATIONS {
let literals = crate::sample_literals!(CurrentNetwork, &mut rng);
for literal in literals.iter() {
for mode in modes.iter() {
check_hash(operation, opcode, literal, mode, &mut cache);
}
}
}
}
}
};
}
test_hash!(hash_bhp256, HashBHP256);
test_hash!(hash_bhp512, HashBHP512);
test_hash!(hash_bhp768, HashBHP768);
test_hash!(hash_bhp1024, HashBHP1024);
test_hash!(hash_psd2, HashPSD2);
test_hash!(hash_psd4, HashPSD4);
test_hash!(hash_psd8, HashPSD8);
#[test]
fn test_hash_ped64_is_consistent() {
let operation = |operands, destination| HashPED64::<CurrentNetwork> { operands, destination };
let opcode = HashPED64::<CurrentNetwork>::opcode();
let mut rng = TestRng::default();
let modes = [circuit::Mode::Public, circuit::Mode::Private];
let mut cache = Default::default();
for _ in 0..ITERATIONS {
let literals = [
Literal::Boolean(console::types::Boolean::rand(&mut rng)),
Literal::I8(console::types::I8::rand(&mut rng)),
Literal::I16(console::types::I16::rand(&mut rng)),
Literal::I32(console::types::I32::rand(&mut rng)),
Literal::U8(console::types::U8::rand(&mut rng)),
Literal::U16(console::types::U16::rand(&mut rng)),
Literal::U32(console::types::U32::rand(&mut rng)),
];
for literal in literals.iter() {
for mode in modes.iter() {
check_hash(operation, opcode, literal, mode, &mut cache);
}
}
}
}
#[test]
fn test_hash_ped128_is_consistent() {
let operation = |operands, destination| HashPED128::<CurrentNetwork> { operands, destination };
let opcode = HashPED128::<CurrentNetwork>::opcode();
let mut rng = TestRng::default();
let modes = [circuit::Mode::Public, circuit::Mode::Private];
let mut cache = Default::default();
for _ in 0..ITERATIONS {
let literals = [
Literal::Boolean(console::types::Boolean::rand(&mut rng)),
Literal::I8(console::types::I8::rand(&mut rng)),
Literal::I16(console::types::I16::rand(&mut rng)),
Literal::I32(console::types::I32::rand(&mut rng)),
Literal::I64(console::types::I64::rand(&mut rng)),
Literal::U8(console::types::U8::rand(&mut rng)),
Literal::U16(console::types::U16::rand(&mut rng)),
Literal::U32(console::types::U32::rand(&mut rng)),
Literal::U64(console::types::U64::rand(&mut rng)),
];
for literal in literals.iter() {
for mode in modes.iter() {
check_hash(operation, opcode, literal, mode, &mut cache);
}
}
}
}
#[test]
fn test_parse() {
let (string, hash) = HashBHP512::<CurrentNetwork>::parse("hash.bhp512 r0 into r1").unwrap();
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
assert_eq!(hash.operands.len(), 1, "The number of operands is incorrect");
assert_eq!(hash.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
assert_eq!(hash.destination, Register::Locator(1), "The destination register is incorrect");
}
}