snarkvm_synthesizer_program/logic/instruction/operation/
commit.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::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait};
17use console::{
18    network::prelude::*,
19    program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Scalar, Value},
20};
21
22/// BHP256 is a collision-resistant function that processes inputs in 256-bit chunks.
23pub type CommitBHP256<N> = CommitInstruction<N, { CommitVariant::CommitBHP256 as u8 }>;
24/// BHP512 is a collision-resistant function that processes inputs in 512-bit chunks.
25pub type CommitBHP512<N> = CommitInstruction<N, { CommitVariant::CommitBHP512 as u8 }>;
26/// BHP768 is a collision-resistant function that processes inputs in 768-bit chunks.
27pub type CommitBHP768<N> = CommitInstruction<N, { CommitVariant::CommitBHP768 as u8 }>;
28/// BHP1024 is a collision-resistant function that processes inputs in 1024-bit chunks.
29pub type CommitBHP1024<N> = CommitInstruction<N, { CommitVariant::CommitBHP1024 as u8 }>;
30
31/// Pedersen64 is a collision-resistant function that processes inputs in 64-bit chunks.
32pub type CommitPED64<N> = CommitInstruction<N, { CommitVariant::CommitPED64 as u8 }>;
33/// Pedersen128 is a collision-resistant function that processes inputs in 128-bit chunks.
34pub type CommitPED128<N> = CommitInstruction<N, { CommitVariant::CommitPED128 as u8 }>;
35
36/// Which commit function to use.
37#[derive(Debug, Clone, Eq, PartialEq)]
38pub enum CommitVariant {
39    CommitBHP256,
40    CommitBHP512,
41    CommitBHP768,
42    CommitBHP1024,
43    CommitPED64,
44    CommitPED128,
45}
46
47impl CommitVariant {
48    // Returns the opcode associated with the variant.
49    pub const fn opcode(variant: u8) -> &'static str {
50        match variant {
51            0 => "commit.bhp256",
52            1 => "commit.bhp512",
53            2 => "commit.bhp768",
54            3 => "commit.bhp1024",
55            4 => "commit.ped64",
56            5 => "commit.ped128",
57            6.. => panic!("Invalid 'commit' instruction opcode"),
58        }
59    }
60}
61
62/// Returns 'true' if the destination type is valid.
63fn is_valid_destination_type(destination_type: LiteralType) -> bool {
64    matches!(destination_type, LiteralType::Address | LiteralType::Field | LiteralType::Group)
65}
66
67/// Commits the operand into the declared type.
68#[derive(Clone, PartialEq, Eq, Hash)]
69pub struct CommitInstruction<N: Network, const VARIANT: u8> {
70    /// The operand as `input`.
71    operands: Vec<Operand<N>>,
72    /// The destination register.
73    destination: Register<N>,
74    /// The destination register type.
75    destination_type: LiteralType,
76}
77
78impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
79    /// Initializes a new `commit` instruction.
80    #[inline]
81    pub fn new(operands: Vec<Operand<N>>, destination: Register<N>, destination_type: LiteralType) -> Result<Self> {
82        // Sanity check that the operands is exactly two inputs.
83        ensure!(operands.len() == 2, "Commit instructions must have two operands");
84        // Sanity check the destination type.
85        ensure!(is_valid_destination_type(destination_type), "Invalid destination type for 'commit' instruction");
86        // Return the instruction.
87        Ok(Self { operands, destination, destination_type })
88    }
89
90    /// Returns the opcode.
91    #[inline]
92    pub const fn opcode() -> Opcode {
93        Opcode::Commit(CommitVariant::opcode(VARIANT))
94    }
95
96    /// Returns the operands in the operation.
97    #[inline]
98    pub fn operands(&self) -> &[Operand<N>] {
99        // Sanity check that the operands is exactly two inputs.
100        debug_assert!(self.operands.len() == 2, "Commit operations must have two operands");
101        // Return the operands.
102        &self.operands
103    }
104
105    /// Returns the destination register.
106    #[inline]
107    pub fn destinations(&self) -> Vec<Register<N>> {
108        vec![self.destination.clone()]
109    }
110
111    /// Returns the destination register type.
112    #[inline]
113    pub const fn destination_type(&self) -> LiteralType {
114        self.destination_type
115    }
116}
117
118// This code is nearly identical in `execute` and `evaluate`; we
119// extract it here in a macro.
120//
121// The `$q` parameter allows us to wrap a value in `Result::Ok`, since
122// the `Aleo` functions don't return a `Result` but the `Network` ones do.
123macro_rules! do_commit {
124    ($N: ident, $variant: expr, $destination_type: expr, $input: expr, $randomizer: expr, $ty: ty, $q: expr) => {{
125        let func = match $variant {
126            0 => $N::commit_to_group_bhp256,
127            1 => $N::commit_to_group_bhp512,
128            2 => $N::commit_to_group_bhp768,
129            3 => $N::commit_to_group_bhp1024,
130            4 => $N::commit_to_group_ped64,
131            5 => $N::commit_to_group_ped128,
132            6.. => bail!("Invalid 'commit' variant: {}", $variant),
133        };
134
135        let literal_output: $ty = $q(func(&$input.to_bits_le(), $randomizer))?.into();
136        literal_output.cast_lossy($destination_type)?
137    }};
138}
139
140/// Evaluate a commit operation.
141///
142/// This allows running the commit without the machinery of stacks and registers.
143/// This is necessary for the Leo interpeter.
144pub fn evaluate_commit<N: Network>(
145    variant: CommitVariant,
146    input: &Value<N>,
147    randomizer: &Scalar<N>,
148    destination_type: LiteralType,
149) -> Result<Literal<N>> {
150    evaluate_commit_internal(variant as u8, input, randomizer, destination_type)
151}
152
153fn evaluate_commit_internal<N: Network>(
154    variant: u8,
155    input: &Value<N>,
156    randomizer: &Scalar<N>,
157    destination_type: LiteralType,
158) -> Result<Literal<N>> {
159    Ok(do_commit!(N, variant, destination_type, input, randomizer, Literal<N>, |x| x))
160}
161
162impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
163    /// Evaluates the instruction.
164    pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
165        // Ensure the number of operands is correct.
166        if self.operands.len() != 2 {
167            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
168        }
169        // Ensure the destination type is valid.
170        ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
171
172        // Retrieve the input and randomizer.
173        let input = registers.load(stack, &self.operands[0])?;
174        let randomizer = registers.load(stack, &self.operands[1])?;
175        // Retrieve the randomizer.
176        let randomizer = match randomizer {
177            Value::Plaintext(Plaintext::Literal(Literal::Scalar(randomizer), ..)) => randomizer,
178            _ => bail!("Invalid randomizer type for the commit evaluation, expected a scalar"),
179        };
180
181        let output = evaluate_commit_internal(VARIANT, &input, &randomizer, self.destination_type)?;
182
183        // Store the output.
184        registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
185    }
186
187    /// Executes the instruction.
188    pub fn execute<A: circuit::Aleo<Network = N>>(
189        &self,
190        stack: &impl StackTrait<N>,
191        registers: &mut impl RegistersCircuit<N, A>,
192    ) -> Result<()> {
193        use circuit::traits::ToBits;
194
195        // Ensure the number of operands is correct.
196        if self.operands.len() != 2 {
197            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
198        }
199        // Ensure the destination type is valid.
200        ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
201
202        // Retrieve the input and randomizer.
203        let input = registers.load_circuit(stack, &self.operands[0])?;
204        let randomizer = registers.load_circuit(stack, &self.operands[1])?;
205        // Retrieve the randomizer.
206        let randomizer = match randomizer {
207            circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Scalar(randomizer), ..)) => {
208                randomizer
209            }
210            _ => bail!("Invalid randomizer type for the commit execution, expected a scalar"),
211        };
212
213        let output =
214            do_commit!(A, VARIANT, self.destination_type, &input, &randomizer, circuit::Literal<A>, Result::<_>::Ok);
215
216        // Convert the output to a stack value.
217        let output = circuit::Value::Plaintext(circuit::Plaintext::Literal(output, Default::default()));
218        // Store the output.
219        registers.store_circuit(stack, &self.destination, output)
220    }
221
222    /// Finalizes the instruction.
223    #[inline]
224    pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
225        self.evaluate(stack, registers)
226    }
227
228    /// Returns the output type from the given program and input types.
229    pub fn output_types(
230        &self,
231        _stack: &impl StackTrait<N>,
232        input_types: &[RegisterType<N>],
233    ) -> Result<Vec<RegisterType<N>>> {
234        // Ensure the number of input types is correct.
235        if input_types.len() != 2 {
236            bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len())
237        }
238        // Ensure the number of operands is correct.
239        if self.operands.len() != 2 {
240            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
241        }
242        // Ensure the destination type is valid.
243        ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
244
245        // TODO (howardwu): If the operation is Pedersen, check that it is within the number of bits.
246
247        match VARIANT {
248            0..=5 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(self.destination_type))]),
249            6.. => bail!("Invalid 'commit' variant: {VARIANT}"),
250        }
251    }
252}
253
254impl<N: Network, const VARIANT: u8> Parser for CommitInstruction<N, VARIANT> {
255    /// Parses a string into an operation.
256    fn parse(string: &str) -> ParserResult<Self> {
257        // Parse the opcode from the string.
258        let (string, _) = tag(*Self::opcode())(string)?;
259        // Parse the whitespace from the string.
260        let (string, _) = Sanitizer::parse_whitespaces(string)?;
261        // Parse the first operand from the string.
262        let (string, first) = Operand::parse(string)?;
263        // Parse the whitespace from the string.
264        let (string, _) = Sanitizer::parse_whitespaces(string)?;
265        // Parse the second operand from the string.
266        let (string, second) = Operand::parse(string)?;
267        // Parse the whitespace from the string.
268        let (string, _) = Sanitizer::parse_whitespaces(string)?;
269        // Parse the "into" from the string.
270        let (string, _) = tag("into")(string)?;
271        // Parse the whitespace from the string.
272        let (string, _) = Sanitizer::parse_whitespaces(string)?;
273        // Parse the destination register from the string.
274        let (string, destination) = Register::parse(string)?;
275        // Parse the whitespace from the string.
276        let (string, _) = Sanitizer::parse_whitespaces(string)?;
277        // Parse the "as" from the string.
278        let (string, _) = tag("as")(string)?;
279        // Parse the whitespace from the string.
280        let (string, _) = Sanitizer::parse_whitespaces(string)?;
281        // Parse the destination register type from the string.
282        let (string, destination_type) = LiteralType::parse(string)?;
283        // Ensure the destination type is allowed.
284        match destination_type {
285            LiteralType::Address | LiteralType::Field | LiteralType::Group => {
286                Ok((string, Self { operands: vec![first, second], destination, destination_type }))
287            }
288            _ => map_res(fail, |_: ParserResult<Self>| {
289                Err(error(format!("Failed to parse 'commit': '{destination_type}' is invalid")))
290            })(string),
291        }
292    }
293}
294
295impl<N: Network, const VARIANT: u8> FromStr for CommitInstruction<N, VARIANT> {
296    type Err = Error;
297
298    /// Parses a string into an operation.
299    #[inline]
300    fn from_str(string: &str) -> Result<Self> {
301        match Self::parse(string) {
302            Ok((remainder, object)) => {
303                // Ensure the remainder is empty.
304                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
305                // Return the object.
306                Ok(object)
307            }
308            Err(error) => bail!("Failed to parse string. {error}"),
309        }
310    }
311}
312
313impl<N: Network, const VARIANT: u8> Debug for CommitInstruction<N, VARIANT> {
314    /// Prints the operation as a string.
315    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
316        Display::fmt(self, f)
317    }
318}
319
320impl<N: Network, const VARIANT: u8> Display for CommitInstruction<N, VARIANT> {
321    /// Prints the operation to a string.
322    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
323        // Ensure the number of operands is 2.
324        if self.operands.len() != 2 {
325            return Err(fmt::Error);
326        }
327        // Print the operation.
328        write!(f, "{} ", Self::opcode())?;
329        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
330        write!(f, "into {} as {}", self.destination, self.destination_type)
331    }
332}
333
334impl<N: Network, const VARIANT: u8> FromBytes for CommitInstruction<N, VARIANT> {
335    /// Reads the operation from a buffer.
336    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
337        // Initialize the vector for the operands.
338        let mut operands = Vec::with_capacity(2);
339        // Read the operands.
340        for _ in 0..2 {
341            operands.push(Operand::read_le(&mut reader)?);
342        }
343        // Read the destination register.
344        let destination = Register::read_le(&mut reader)?;
345        // Read the destination register type.
346        let destination_type = LiteralType::read_le(&mut reader)?;
347
348        // Return the operation.
349        Self::new(operands, destination, destination_type).map_err(error)
350    }
351}
352
353impl<N: Network, const VARIANT: u8> ToBytes for CommitInstruction<N, VARIANT> {
354    /// Writes the operation to a buffer.
355    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
356        // Ensure the number of operands is 2.
357        if self.operands.len() != 2 {
358            return Err(error(format!("The number of operands must be 2, found {}", self.operands.len())));
359        }
360        // Write the operands.
361        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
362        // Write the destination register.
363        self.destination.write_le(&mut writer)?;
364        // Write the destination register type.
365        self.destination_type.write_le(&mut writer)
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use console::network::MainnetV0;
373
374    type CurrentNetwork = MainnetV0;
375
376    /// **Attention**: When changing this, also update in `tests/instruction/commit.rs`.
377    fn valid_destination_types() -> &'static [LiteralType] {
378        &[LiteralType::Address, LiteralType::Field, LiteralType::Group]
379    }
380
381    #[test]
382    fn test_parse() {
383        for destination_type in valid_destination_types() {
384            let instruction = format!("commit.bhp512 r0 r1 into r2 as {destination_type}");
385            let (string, commit) = CommitBHP512::<CurrentNetwork>::parse(&instruction).unwrap();
386            assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
387            assert_eq!(commit.operands.len(), 2, "The number of operands is incorrect");
388            assert_eq!(commit.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
389            assert_eq!(commit.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
390            assert_eq!(commit.destination, Register::Locator(2), "The destination register is incorrect");
391            assert_eq!(commit.destination_type, *destination_type, "The destination type is incorrect");
392        }
393    }
394}