Skip to main content

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    /// Returns whether this instruction refers to an external struct.
118    #[inline]
119    pub fn contains_external_struct(&self) -> bool {
120        false
121    }
122}
123
124// This code is nearly identical in `execute` and `evaluate`; we
125// extract it here in a macro.
126//
127// The `$q` parameter allows us to wrap a value in `Result::Ok`, since
128// the `Aleo` functions don't return a `Result` but the `Network` ones do.
129macro_rules! do_commit {
130    ($N: ident, $variant: expr, $destination_type: expr, $input: expr, $randomizer: expr, $ty: ty, $q: expr) => {{
131        let func = match $variant {
132            0 => $N::commit_to_group_bhp256,
133            1 => $N::commit_to_group_bhp512,
134            2 => $N::commit_to_group_bhp768,
135            3 => $N::commit_to_group_bhp1024,
136            4 => $N::commit_to_group_ped64,
137            5 => $N::commit_to_group_ped128,
138            6.. => bail!("Invalid 'commit' variant: {}", $variant),
139        };
140
141        let literal_output: $ty = $q(func(&$input.to_bits_le(), $randomizer))?.into();
142        literal_output.cast_lossy($destination_type)?
143    }};
144}
145
146/// Evaluate a commit operation.
147///
148/// This allows running the commit without the machinery of stacks and registers.
149/// This is necessary for the Leo interpeter.
150pub fn evaluate_commit<N: Network>(
151    variant: CommitVariant,
152    input: &Value<N>,
153    randomizer: &Scalar<N>,
154    destination_type: LiteralType,
155) -> Result<Literal<N>> {
156    evaluate_commit_internal(variant as u8, input, randomizer, destination_type)
157}
158
159fn evaluate_commit_internal<N: Network>(
160    variant: u8,
161    input: &Value<N>,
162    randomizer: &Scalar<N>,
163    destination_type: LiteralType,
164) -> Result<Literal<N>> {
165    Ok(do_commit!(N, variant, destination_type, input, randomizer, Literal<N>, |x| x))
166}
167
168impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
169    /// Evaluates the instruction.
170    pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
171        // Ensure the number of operands is correct.
172        if self.operands.len() != 2 {
173            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
174        }
175        // Ensure the destination type is valid.
176        ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
177
178        // Retrieve the input and randomizer.
179        let input = registers.load(stack, &self.operands[0])?;
180        let randomizer = registers.load(stack, &self.operands[1])?;
181        // Retrieve the randomizer.
182        let randomizer = match randomizer {
183            Value::Plaintext(Plaintext::Literal(Literal::Scalar(randomizer), ..)) => randomizer,
184            _ => bail!("Invalid randomizer type for the commit evaluation, expected a scalar"),
185        };
186
187        let output = evaluate_commit_internal(VARIANT, &input, &randomizer, self.destination_type)?;
188
189        // Store the output.
190        registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
191    }
192
193    /// Executes the instruction.
194    pub fn execute<A: circuit::Aleo<Network = N>>(
195        &self,
196        stack: &impl StackTrait<N>,
197        registers: &mut impl RegistersCircuit<N, A>,
198    ) -> Result<()> {
199        use circuit::traits::ToBits;
200
201        // Ensure the number of operands is correct.
202        if self.operands.len() != 2 {
203            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
204        }
205        // Ensure the destination type is valid.
206        ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
207
208        // Retrieve the input and randomizer.
209        let input = registers.load_circuit(stack, &self.operands[0])?;
210        let randomizer = registers.load_circuit(stack, &self.operands[1])?;
211        // Retrieve the randomizer.
212        let randomizer = match randomizer {
213            circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Scalar(randomizer), ..)) => {
214                randomizer
215            }
216            _ => bail!("Invalid randomizer type for the commit execution, expected a scalar"),
217        };
218
219        let output =
220            do_commit!(A, VARIANT, self.destination_type, &input, &randomizer, circuit::Literal<A>, Result::<_>::Ok);
221
222        // Convert the output to a stack value.
223        let output = circuit::Value::Plaintext(circuit::Plaintext::Literal(output, Default::default()));
224        // Store the output.
225        registers.store_circuit(stack, &self.destination, output)
226    }
227
228    /// Finalizes the instruction.
229    #[inline]
230    pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
231        self.evaluate(stack, registers)
232    }
233
234    /// Returns the output type from the given program and input types.
235    pub fn output_types(
236        &self,
237        _stack: &impl StackTrait<N>,
238        input_types: &[RegisterType<N>],
239    ) -> Result<Vec<RegisterType<N>>> {
240        // Ensure the number of input types is correct.
241        if input_types.len() != 2 {
242            bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len())
243        }
244        // Ensure the number of operands is correct.
245        if self.operands.len() != 2 {
246            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
247        }
248        // Ensure the destination type is valid.
249        ensure!(is_valid_destination_type(self.destination_type), "Invalid destination type in 'commit' instruction");
250
251        // TODO (howardwu): If the operation is Pedersen, check that it is within the number of bits.
252
253        match VARIANT {
254            0..=5 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(self.destination_type))]),
255            6.. => bail!("Invalid 'commit' variant: {VARIANT}"),
256        }
257    }
258}
259
260impl<N: Network, const VARIANT: u8> Parser for CommitInstruction<N, VARIANT> {
261    /// Parses a string into an operation.
262    fn parse(string: &str) -> ParserResult<Self> {
263        // Parse the opcode from the string.
264        let (string, _) = tag(*Self::opcode())(string)?;
265        // Parse the whitespace from the string.
266        let (string, _) = Sanitizer::parse_whitespaces(string)?;
267        // Parse the first operand from the string.
268        let (string, first) = Operand::parse(string)?;
269        // Parse the whitespace from the string.
270        let (string, _) = Sanitizer::parse_whitespaces(string)?;
271        // Parse the second operand from the string.
272        let (string, second) = Operand::parse(string)?;
273        // Parse the whitespace from the string.
274        let (string, _) = Sanitizer::parse_whitespaces(string)?;
275        // Parse the "into" from the string.
276        let (string, _) = tag("into")(string)?;
277        // Parse the whitespace from the string.
278        let (string, _) = Sanitizer::parse_whitespaces(string)?;
279        // Parse the destination register from the string.
280        let (string, destination) = Register::parse(string)?;
281        // Parse the whitespace from the string.
282        let (string, _) = Sanitizer::parse_whitespaces(string)?;
283        // Parse the "as" from the string.
284        let (string, _) = tag("as")(string)?;
285        // Parse the whitespace from the string.
286        let (string, _) = Sanitizer::parse_whitespaces(string)?;
287        // Parse the destination register type from the string.
288        let (string, destination_type) = LiteralType::parse(string)?;
289        // Ensure the destination type is allowed.
290        match destination_type {
291            LiteralType::Address | LiteralType::Field | LiteralType::Group => {
292                Ok((string, Self { operands: vec![first, second], destination, destination_type }))
293            }
294            _ => map_res(fail, |_: ParserResult<Self>| {
295                Err(error(format!("Failed to parse 'commit': '{destination_type}' is invalid")))
296            })(string),
297        }
298    }
299}
300
301impl<N: Network, const VARIANT: u8> FromStr for CommitInstruction<N, VARIANT> {
302    type Err = Error;
303
304    /// Parses a string into an operation.
305    #[inline]
306    fn from_str(string: &str) -> Result<Self> {
307        match Self::parse(string) {
308            Ok((remainder, object)) => {
309                // Ensure the remainder is empty.
310                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
311                // Return the object.
312                Ok(object)
313            }
314            Err(error) => bail!("Failed to parse string. {error}"),
315        }
316    }
317}
318
319impl<N: Network, const VARIANT: u8> Debug for CommitInstruction<N, VARIANT> {
320    /// Prints the operation as a string.
321    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
322        Display::fmt(self, f)
323    }
324}
325
326impl<N: Network, const VARIANT: u8> Display for CommitInstruction<N, VARIANT> {
327    /// Prints the operation to a string.
328    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
329        // Ensure the number of operands is 2.
330        if self.operands.len() != 2 {
331            return Err(fmt::Error);
332        }
333        // Print the operation.
334        write!(f, "{} ", Self::opcode())?;
335        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
336        write!(f, "into {} as {}", self.destination, self.destination_type)
337    }
338}
339
340impl<N: Network, const VARIANT: u8> FromBytes for CommitInstruction<N, VARIANT> {
341    /// Reads the operation from a buffer.
342    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
343        // Initialize the vector for the operands.
344        let mut operands = Vec::with_capacity(2);
345        // Read the operands.
346        for _ in 0..2 {
347            operands.push(Operand::read_le(&mut reader)?);
348        }
349        // Read the destination register.
350        let destination = Register::read_le(&mut reader)?;
351        // Read the destination register type.
352        let destination_type = LiteralType::read_le(&mut reader)?;
353
354        // Return the operation.
355        Self::new(operands, destination, destination_type).map_err(error)
356    }
357}
358
359impl<N: Network, const VARIANT: u8> ToBytes for CommitInstruction<N, VARIANT> {
360    /// Writes the operation to a buffer.
361    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
362        // Ensure the number of operands is 2.
363        if self.operands.len() != 2 {
364            return Err(error(format!("The number of operands must be 2, found {}", self.operands.len())));
365        }
366        // Write the operands.
367        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
368        // Write the destination register.
369        self.destination.write_le(&mut writer)?;
370        // Write the destination register type.
371        self.destination_type.write_le(&mut writer)
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use console::network::MainnetV0;
379
380    type CurrentNetwork = MainnetV0;
381
382    /// **Attention**: When changing this, also update in `tests/instruction/commit.rs`.
383    fn valid_destination_types() -> &'static [LiteralType] {
384        &[LiteralType::Address, LiteralType::Field, LiteralType::Group]
385    }
386
387    #[test]
388    fn test_parse() {
389        for destination_type in valid_destination_types() {
390            let instruction = format!("commit.bhp512 r0 r1 into r2 as {destination_type}");
391            let (string, commit) = CommitBHP512::<CurrentNetwork>::parse(&instruction).unwrap();
392            assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
393            assert_eq!(commit.operands.len(), 2, "The number of operands is incorrect");
394            assert_eq!(commit.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
395            assert_eq!(commit.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
396            assert_eq!(commit.destination, Register::Locator(2), "The destination register is incorrect");
397            assert_eq!(commit.destination_type, *destination_type, "The destination type is incorrect");
398        }
399    }
400}