use crate::{Opcode, Operand, Registers, Stack};
use console::{
network::prelude::*,
program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Value},
};
pub type CommitBHP256<N> = CommitInstruction<N, { Committer::BHP256 as u8 }>;
pub type CommitBHP512<N> = CommitInstruction<N, { Committer::BHP512 as u8 }>;
pub type CommitBHP768<N> = CommitInstruction<N, { Committer::BHP768 as u8 }>;
pub type CommitBHP1024<N> = CommitInstruction<N, { Committer::BHP1024 as u8 }>;
pub type CommitPED64<N> = CommitInstruction<N, { Committer::PED64 as u8 }>;
pub type CommitPED128<N> = CommitInstruction<N, { Committer::PED128 as u8 }>;
enum Committer {
BHP256,
BHP512,
BHP768,
BHP1024,
PED64,
PED128,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct CommitInstruction<N: Network, const VARIANT: u8> {
operands: Vec<Operand<N>>,
destination: Register<N>,
}
impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
#[inline]
pub const fn opcode() -> Opcode {
match VARIANT {
0 => Opcode::Commit("commit.bhp256"),
1 => Opcode::Commit("commit.bhp512"),
2 => Opcode::Commit("commit.bhp768"),
3 => Opcode::Commit("commit.bhp1024"),
4 => Opcode::Commit("commit.ped64"),
5 => Opcode::Commit("commit.ped128"),
_ => panic!("Invalid 'commit' instruction opcode"),
}
}
#[inline]
pub fn operands(&self) -> &[Operand<N>] {
debug_assert!(self.operands.len() == 2, "Commit operations must have two operands");
&self.operands
}
#[inline]
pub fn destinations(&self) -> Vec<Register<N>> {
vec![self.destination.clone()]
}
}
impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
#[inline]
pub fn evaluate<A: circuit::Aleo<Network = N>>(
&self,
stack: &Stack<N>,
registers: &mut Registers<N, A>,
) -> Result<()> {
if self.operands.len() != 2 {
bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
}
let input = registers.load(stack, &self.operands[0])?;
let randomizer = registers.load(stack, &self.operands[1])?;
let randomizer = match randomizer {
Value::Plaintext(Plaintext::Literal(Literal::Scalar(randomizer), ..)) => randomizer,
_ => bail!("Invalid randomizer type for the commit evaluation, expected a scalar"),
};
let output = match VARIANT {
0 => Literal::Field(N::commit_bhp256(&input.to_bits_le(), &randomizer)?),
1 => Literal::Field(N::commit_bhp512(&input.to_bits_le(), &randomizer)?),
2 => Literal::Field(N::commit_bhp768(&input.to_bits_le(), &randomizer)?),
3 => Literal::Field(N::commit_bhp1024(&input.to_bits_le(), &randomizer)?),
4 => Literal::Group(N::commit_ped64(&input.to_bits_le(), &randomizer)?),
5 => Literal::Group(N::commit_ped128(&input.to_bits_le(), &randomizer)?),
_ => bail!("Invalid 'commit' variant: {VARIANT}"),
};
registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
}
#[inline]
pub fn execute<A: circuit::Aleo<Network = N>>(
&self,
stack: &Stack<N>,
registers: &mut Registers<N, A>,
) -> Result<()> {
use circuit::ToBits;
if self.operands.len() != 2 {
bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
}
let input = registers.load_circuit(stack, &self.operands[0])?;
let randomizer = registers.load_circuit(stack, &self.operands[1])?;
let randomizer = match randomizer {
circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Scalar(randomizer), ..)) => {
randomizer
}
_ => bail!("Invalid randomizer type for the commit execution, expected a scalar"),
};
let output = match VARIANT {
0 => circuit::Literal::Field(A::commit_bhp256(&input.to_bits_le(), &randomizer)),
1 => circuit::Literal::Field(A::commit_bhp512(&input.to_bits_le(), &randomizer)),
2 => circuit::Literal::Field(A::commit_bhp768(&input.to_bits_le(), &randomizer)),
3 => circuit::Literal::Field(A::commit_bhp1024(&input.to_bits_le(), &randomizer)),
4 => circuit::Literal::Group(A::commit_ped64(&input.to_bits_le(), &randomizer)),
5 => circuit::Literal::Group(A::commit_ped128(&input.to_bits_le(), &randomizer)),
_ => bail!("Invalid 'commit' variant: {VARIANT}"),
};
let output = circuit::Value::Plaintext(circuit::Plaintext::Literal(output, Default::default()));
registers.store_circuit(stack, &self.destination, output)
}
#[inline]
pub fn output_types(&self, _stack: &Stack<N>, input_types: &[RegisterType<N>]) -> Result<Vec<RegisterType<N>>> {
if input_types.len() != 2 {
bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len())
}
if self.operands.len() != 2 {
bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
}
match VARIANT {
0 | 1 | 2 | 3 | 4 | 5 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Field))]),
_ => bail!("Invalid 'commit' variant: {VARIANT}"),
}
}
}
impl<N: Network, const VARIANT: u8> Parser for CommitInstruction<N, VARIANT> {
#[inline]
fn parse(string: &str) -> ParserResult<Self> {
let (string, _) = tag(*Self::opcode())(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, first) = Operand::parse(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, second) = 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![first, second], destination }))
}
}
impl<N: Network, const VARIANT: u8> FromStr for CommitInstruction<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 CommitInstruction<N, VARIANT> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl<N: Network, const VARIANT: u8> Display for CommitInstruction<N, VARIANT> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.operands.len() != 2 {
eprintln!("The number of operands must be 2, 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 CommitInstruction<N, VARIANT> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let mut operands = Vec::with_capacity(2);
for _ in 0..2 {
operands.push(Operand::read_le(&mut reader)?);
}
let destination = Register::read_le(&mut reader)?;
Ok(Self { operands, destination })
}
}
impl<N: Network, const VARIANT: u8> ToBytes for CommitInstruction<N, VARIANT> {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
if self.operands.len() != 2 {
return Err(error(format!("The number of operands must be 2, found {}", self.operands.len())));
}
self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
self.destination.write_le(&mut writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use console::network::Testnet3;
type CurrentNetwork = Testnet3;
#[test]
fn test_parse() {
let (string, commit) = CommitBHP512::<CurrentNetwork>::parse("commit.bhp512 r0 r1 into r2").unwrap();
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
assert_eq!(commit.operands.len(), 2, "The number of operands is incorrect");
assert_eq!(commit.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
assert_eq!(commit.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
assert_eq!(commit.destination, Register::Locator(2), "The destination register is incorrect");
}
}