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