use crate::{
FinalizeRegistersState,
FinalizeStoreTrait,
Opcode,
Operand,
RegistersCircuit,
RegistersTrait,
StackTrait,
};
use console::{
network::prelude::*,
program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Scalar, Value},
};
pub type CommitBHP256<N> = CommitInstruction<N, { CommitVariant::CommitBHP256 as u8 }>;
pub type CommitBHP512<N> = CommitInstruction<N, { CommitVariant::CommitBHP512 as u8 }>;
pub type CommitBHP768<N> = CommitInstruction<N, { CommitVariant::CommitBHP768 as u8 }>;
pub type CommitBHP1024<N> = CommitInstruction<N, { CommitVariant::CommitBHP1024 as u8 }>;
pub type CommitPED64<N> = CommitInstruction<N, { CommitVariant::CommitPED64 as u8 }>;
pub type CommitPED128<N> = CommitInstruction<N, { CommitVariant::CommitPED128 as u8 }>;
pub type CommitBHP256Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP256Raw as u8 }>;
pub type CommitBHP512Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP512Raw as u8 }>;
pub type CommitBHP768Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP768Raw as u8 }>;
pub type CommitBHP1024Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP1024Raw as u8 }>;
pub type CommitPED64Raw<N> = CommitInstruction<N, { CommitVariant::CommitPED64Raw as u8 }>;
pub type CommitPED128Raw<N> = CommitInstruction<N, { CommitVariant::CommitPED128Raw as u8 }>;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CommitVariant {
CommitBHP256,
CommitBHP512,
CommitBHP768,
CommitBHP1024,
CommitPED64,
CommitPED128,
CommitBHP256Raw,
CommitBHP512Raw,
CommitBHP768Raw,
CommitBHP1024Raw,
CommitPED64Raw,
CommitPED128Raw,
}
impl CommitVariant {
pub const fn opcode(variant: u8) -> &'static str {
match variant {
0 => "commit.bhp256",
1 => "commit.bhp512",
2 => "commit.bhp768",
3 => "commit.bhp1024",
4 => "commit.ped64",
5 => "commit.ped128",
6 => "commit.bhp256.raw",
7 => "commit.bhp512.raw",
8 => "commit.bhp768.raw",
9 => "commit.bhp1024.raw",
10 => "commit.ped64.raw",
11 => "commit.ped128.raw",
12.. => panic!("Invalid 'commit' instruction opcode"),
}
}
pub const fn is_raw(variant: u8) -> bool {
matches!(variant, 6..=11)
}
}
fn is_valid_destination_type(destination_type: LiteralType) -> bool {
matches!(destination_type, LiteralType::Address | LiteralType::Field | LiteralType::Group)
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct CommitInstruction<N: Network, const VARIANT: u8> {
operands: Vec<Operand<N>>,
destination: Register<N>,
destination_type: LiteralType,
}
impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
#[inline]
pub fn new(operands: Vec<Operand<N>>, destination: Register<N>, destination_type: LiteralType) -> Result<Self> {
ensure!(operands.len() == 2, "Commit instructions must have two operands");
ensure!(is_valid_destination_type(destination_type), "Invalid destination type for 'commit' instruction");
Ok(Self { operands, destination, destination_type })
}
#[inline]
pub const fn opcode() -> Opcode {
Opcode::Commit(CommitVariant::opcode(VARIANT))
}
#[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()]
}
#[inline]
pub const fn destination_type(&self) -> LiteralType {
self.destination_type
}
#[inline]
pub fn contains_external_struct(&self) -> bool {
false
}
}
macro_rules! do_commit {
($N: ident, $variant: expr, $destination_type: expr, $input: expr, $randomizer: expr, $ty: ty, $q: expr) => {{
let func = match $variant {
0 | 6 => $N::commit_to_group_bhp256,
1 | 7 => $N::commit_to_group_bhp512,
2 | 8 => $N::commit_to_group_bhp768,
3 | 9 => $N::commit_to_group_bhp1024,
4 | 10 => $N::commit_to_group_ped64,
5 | 11 => $N::commit_to_group_ped128,
12.. => bail!("Invalid 'commit' variant: {}", $variant),
};
let bits = match CommitVariant::is_raw($variant) {
true => $input.to_bits_raw_le(),
false => $input.to_bits_le(),
};
let literal_output: $ty = $q(func(&bits, $randomizer))?.into();
literal_output.cast_lossy($destination_type)?
}};
}
pub fn evaluate_commit<N: Network>(
variant: CommitVariant,
input: &Value<N>,
randomizer: &Scalar<N>,
destination_type: LiteralType,
) -> Result<Literal<N>> {
evaluate_commit_internal(variant as u8, input, randomizer, destination_type)
}
fn evaluate_commit_internal<N: Network>(
variant: u8,
input: &Value<N>,
randomizer: &Scalar<N>,
destination_type: LiteralType,
) -> Result<Literal<N>> {
Ok(do_commit!(N, variant, destination_type, input, randomizer, Literal<N>, |x| x))
}
impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
if self.operands.len() != 2 {
bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
}
ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
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 = evaluate_commit_internal(VARIANT, &input, &randomizer, self.destination_type)?;
registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
}
pub fn execute<A: circuit::Aleo<Network = N>>(
&self,
stack: &impl StackTrait<N>,
registers: &mut impl RegistersCircuit<N, A>,
) -> Result<()> {
use circuit::traits::{ToBits, ToBitsRaw};
if self.operands.len() != 2 {
bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
}
ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
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 =
do_commit!(A, VARIANT, self.destination_type, &input, &randomizer, circuit::Literal<A>, Result::<_>::Ok);
let output = circuit::Value::Plaintext(circuit::Plaintext::Literal(output, Default::default()));
registers.store_circuit(stack, &self.destination, output)
}
#[inline]
pub fn finalize(
&self,
stack: &impl StackTrait<N>,
_store: Option<&dyn FinalizeStoreTrait<N>>,
registers: &mut impl FinalizeRegistersState<N>,
) -> Result<()> {
self.evaluate(stack, registers)
}
pub fn output_types(
&self,
_stack: &impl StackTrait<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())
}
ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
match VARIANT {
0..=11 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(self.destination_type))]),
12.. => bail!("Invalid 'commit' variant: {VARIANT}"),
}
}
}
impl<N: Network, const VARIANT: u8> Parser for CommitInstruction<N, VARIANT> {
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)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag("as")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, destination_type) = LiteralType::parse(string)?;
match destination_type {
LiteralType::Address | LiteralType::Field | LiteralType::Group => {
Ok((string, Self { operands: vec![first, second], destination, destination_type }))
}
_ => map_res(fail, |_: ParserResult<Self>| {
Err(error(format!("Failed to parse 'commit': '{destination_type}' is invalid")))
})(string),
}
}
}
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 {
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, 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)?;
let destination_type = LiteralType::read_le(&mut reader)?;
Self::new(operands, destination, destination_type).map_err(error)
}
}
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)?;
self.destination_type.write_le(&mut writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use console::network::MainnetV0;
type CurrentNetwork = MainnetV0;
fn valid_destination_types() -> &'static [LiteralType] {
&[LiteralType::Address, LiteralType::Field, LiteralType::Group]
}
#[test]
fn test_parse() {
for destination_type in valid_destination_types() {
let instruction = format!("commit.bhp512 r0 r1 into r2 as {destination_type}");
let (string, commit) = CommitBHP512::<CurrentNetwork>::parse(&instruction).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");
assert_eq!(commit.destination_type, *destination_type, "The destination type is incorrect");
}
}
#[test]
fn test_parse_raw() {
for destination_type in valid_destination_types() {
let instruction = format!("commit.bhp256.raw r0 r1 into r2 as {destination_type}");
let (string, commit) = CommitBHP256Raw::<CurrentNetwork>::parse(&instruction).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");
assert_eq!(commit.destination_type, *destination_type, "The destination type is incorrect");
}
}
#[test]
fn test_raw_differs_from_standard() {
use console::{
program::{Literal, Plaintext, Value},
types::{Field, Scalar},
};
type N = CurrentNetwork;
let input_field = Field::<N>::one();
let randomizer = Scalar::<N>::one();
let value = Value::Plaintext(Plaintext::from(Literal::Field(input_field)));
let standard = evaluate_commit(CommitVariant::CommitBHP256, &value, &randomizer, LiteralType::Field).unwrap();
let raw = evaluate_commit(CommitVariant::CommitBHP256Raw, &value, &randomizer, LiteralType::Field).unwrap();
assert_ne!(
standard, raw,
"commit.bhp256 and commit.bhp256.raw must produce different outputs for the same input"
);
let expected_commitment = N::commit_bhp256(&input_field.to_bits_le(), &randomizer).unwrap();
assert_eq!(Literal::Field(expected_commitment), raw);
}
}