snarkvm_synthesizer_program/logic/instruction/operation/
commit.rs

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