snarkvm_circuit_algorithms/pedersen/
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 super::*;
17
18impl<E: Environment, const NUM_BITS: u8> Commit for Pedersen<E, NUM_BITS> {
19    type Input = Boolean<E>;
20    type Output = Field<E>;
21    type Randomizer = Scalar<E>;
22
23    /// Returns the Pedersen commitment of the given input and randomizer as a field element.
24    fn commit(&self, input: &[Self::Input], randomizer: &Self::Randomizer) -> Self::Output {
25        self.commit_uncompressed(input, randomizer).to_x_coordinate()
26    }
27}
28
29impl<E: Environment, const NUM_BITS: u8>
30    Metrics<dyn Commit<Input = Boolean<E>, Output = Field<E>, Randomizer = Scalar<E>>> for Pedersen<E, NUM_BITS>
31{
32    type Case = (Vec<Mode>, Vec<Mode>);
33
34    fn count(case: &Self::Case) -> Count {
35        let (input_modes, randomizer_modes) = case;
36        let uncompressed_count =
37            count!(Pedersen<E, NUM_BITS>, HashUncompressed<Input = Boolean<E>, Output = Group<E>>, input_modes);
38        let uncompressed_mode =
39            output_mode!(Pedersen<E, NUM_BITS>, HashUncompressed<Input = Boolean<E>, Output = Group<E>>, input_modes);
40
41        // Compute the const of constructing the group elements.
42        let group_initialize_count = randomizer_modes
43            .iter()
44            .map(|mode| {
45                count!(
46                    Group<E>,
47                    Ternary<Boolean = Boolean<E>, Output = Group<E>>,
48                    &(*mode, Mode::Constant, Mode::Constant)
49                )
50            })
51            .fold(Count::zero(), |cumulative, count| cumulative + count);
52
53        // Compute the count for converting the randomizer into bits.
54        let randomizer_to_bits_count =
55            match Mode::combine(randomizer_modes[0], randomizer_modes.iter().copied()).is_constant() {
56                true => Count::is(251, 0, 0, 0),
57                false => Count::is(0, 0, 501, 503),
58            };
59
60        // Determine the modes of each of the group elements.
61        let modes = randomizer_modes.iter().map(|mode| {
62            // The `first` and `second` inputs to `Group::ternary` are always constant so we can directly determine the mode instead of
63            // using the `output_mode` macro. This avoids the need to use `CircuitType` as a parameter, simplifying the logic of this function.
64            match mode.is_constant() {
65                true => Mode::Constant,
66                false => Mode::Private,
67            }
68        });
69
70        // Calculate the cost of summing the group elements.
71        let (_, summation_count) =
72            modes.fold((uncompressed_mode, Count::zero()), |(prev_mode, cumulative), curr_mode| {
73                let mode = output_mode!(Group<E>, Add<Group<E>, Output = Group<E>>, &(prev_mode, curr_mode));
74                let sum_count = count!(Group<E>, Add<Group<E>, Output = Group<E>>, &(prev_mode, curr_mode));
75                (mode, cumulative + sum_count)
76            });
77
78        // Compute the cost of summing the hash and random elements.
79        uncompressed_count + group_initialize_count + randomizer_to_bits_count + summation_count
80    }
81}
82
83impl<E: Environment, const NUM_BITS: u8>
84    OutputMode<dyn Commit<Input = Boolean<E>, Output = Field<E>, Randomizer = Scalar<E>>> for Pedersen<E, NUM_BITS>
85{
86    type Case = (Vec<Mode>, Vec<Mode>);
87
88    fn output_mode(parameters: &Self::Case) -> Mode {
89        let (input_modes, randomizer_modes) = parameters;
90        match input_modes.iter().all(|m| *m == Mode::Constant) && randomizer_modes.iter().all(|m| *m == Mode::Constant)
91        {
92            true => Mode::Constant,
93            false => Mode::Private,
94        }
95    }
96}
97
98#[cfg(all(test, feature = "console"))]
99mod tests {
100    use super::*;
101    use snarkvm_circuit_types::environment::Circuit;
102    use snarkvm_utilities::{TestRng, Uniform};
103
104    const ITERATIONS: u64 = 10;
105    const MESSAGE: &str = "PedersenCircuit0";
106    const NUM_BITS_MULTIPLIER: u8 = 8;
107
108    fn check_commit<const NUM_BITS: u8>(mode: Mode, rng: &mut TestRng) {
109        use console::Commit as C;
110
111        // Initialize Pedersen.
112        let native = console::Pedersen::<<Circuit as Environment>::Network, NUM_BITS>::setup(MESSAGE);
113        let circuit = Pedersen::<Circuit, NUM_BITS>::constant(native.clone());
114
115        for i in 0..ITERATIONS {
116            // Sample a random input.
117            let input = (0..NUM_BITS).map(|_| bool::rand(rng)).collect::<Vec<bool>>();
118            // Sample a randomizer.
119            let randomizer = Uniform::rand(rng);
120            // Compute the expected commitment.
121            let expected = native.commit(&input, &randomizer).expect("Failed to commit native input");
122            // Prepare the circuit input.
123            let circuit_input: Vec<Boolean<_>> = Inject::new(mode, input);
124            // Prepare the circuit randomizer.
125            let circuit_randomizer: Scalar<_> = Inject::new(mode, randomizer);
126
127            Circuit::scope(format!("Pedersen {mode} {i}"), || {
128                // Perform the commit operation.
129                let candidate = circuit.commit(&circuit_input, &circuit_randomizer);
130                assert_eq!(expected, candidate.eject_value());
131
132                // Check constraint counts and output mode.
133                let input_modes = circuit_input.iter().map(|b| b.eject_mode()).collect::<Vec<_>>();
134                let randomizer_modes =
135                    circuit_randomizer.to_bits_le().iter().map(|b| b.eject_mode()).collect::<Vec<_>>();
136                assert_count!(
137                    Pedersen<Circuit, NUM_BITS>,
138                    Commit<Input = Boolean<Circuit>, Output = Field<Circuit>, Randomizer = Scalar<Circuit>>,
139                    &(input_modes.clone(), randomizer_modes.clone())
140                );
141                assert_output_mode!(
142                    Pedersen<Circuit, NUM_BITS>,
143                    Commit<Input = Boolean<Circuit>, Output = Field<Circuit>, Randomizer = Scalar<Circuit>>,
144                    &(input_modes, randomizer_modes),
145                    candidate
146                );
147            });
148        }
149    }
150
151    fn check_homomorphic_addition<
152        C: Display + Eject + Add<Output = C> + ToBits<Boolean = Boolean<Circuit>>,
153        P: Commit<Input = Boolean<Circuit>, Randomizer = Scalar<Circuit>, Output = Field<Circuit>>
154            + CommitUncompressed<Input = Boolean<Circuit>, Randomizer = Scalar<Circuit>, Output = Group<Circuit>>,
155    >(
156        pedersen: &P,
157        first: C,
158        second: C,
159        rng: &mut TestRng,
160    ) {
161        println!("Checking homomorphic addition on {first} + {second}");
162
163        // Sample the circuit randomizers.
164        let first_randomizer: Scalar<_> = Inject::new(Mode::Private, Uniform::rand(rng));
165        let second_randomizer: Scalar<_> = Inject::new(Mode::Private, Uniform::rand(rng));
166
167        // Compute the expected commitment, by committing them individually and summing their results.
168        let a = pedersen.commit_uncompressed(&first.to_bits_le(), &first_randomizer);
169        let b = pedersen.commit_uncompressed(&second.to_bits_le(), &second_randomizer);
170        let expected = (a + b).to_x_coordinate();
171
172        let combined_randomizer = first_randomizer + second_randomizer;
173
174        // Sum the two integers, and then commit the sum.
175        let candidate = pedersen.commit(&(first + second).to_bits_le(), &combined_randomizer);
176        assert_eq!(expected.eject(), candidate.eject());
177        assert!(Circuit::is_satisfied());
178    }
179
180    #[test]
181    fn test_commit_constant() {
182        // Set the number of windows, and modulate the window size.
183        let mut rng = TestRng::default();
184        check_commit::<NUM_BITS_MULTIPLIER>(Mode::Constant, &mut rng);
185        check_commit::<{ 2 * NUM_BITS_MULTIPLIER }>(Mode::Constant, &mut rng);
186        check_commit::<{ 3 * NUM_BITS_MULTIPLIER }>(Mode::Constant, &mut rng);
187        check_commit::<{ 4 * NUM_BITS_MULTIPLIER }>(Mode::Constant, &mut rng);
188        check_commit::<{ 5 * NUM_BITS_MULTIPLIER }>(Mode::Constant, &mut rng);
189    }
190
191    #[test]
192    fn test_commit_public() {
193        // Set the number of windows, and modulate the window size.
194        let mut rng = TestRng::default();
195        check_commit::<NUM_BITS_MULTIPLIER>(Mode::Public, &mut rng);
196        check_commit::<{ 2 * NUM_BITS_MULTIPLIER }>(Mode::Public, &mut rng);
197        check_commit::<{ 3 * NUM_BITS_MULTIPLIER }>(Mode::Public, &mut rng);
198        check_commit::<{ 4 * NUM_BITS_MULTIPLIER }>(Mode::Public, &mut rng);
199        check_commit::<{ 5 * NUM_BITS_MULTIPLIER }>(Mode::Public, &mut rng);
200    }
201
202    #[test]
203    fn test_commit_private() {
204        // Set the number of windows, and modulate the window size.
205        let mut rng = TestRng::default();
206        check_commit::<NUM_BITS_MULTIPLIER>(Mode::Private, &mut rng);
207        check_commit::<{ 2 * NUM_BITS_MULTIPLIER }>(Mode::Private, &mut rng);
208        check_commit::<{ 3 * NUM_BITS_MULTIPLIER }>(Mode::Private, &mut rng);
209        check_commit::<{ 4 * NUM_BITS_MULTIPLIER }>(Mode::Private, &mut rng);
210        check_commit::<{ 5 * NUM_BITS_MULTIPLIER }>(Mode::Private, &mut rng);
211    }
212
213    #[test]
214    fn test_pedersen64_homomorphism_private() {
215        // Initialize Pedersen64.
216        let pedersen = Pedersen64::constant(console::Pedersen64::setup("Pedersen64HomomorphismTest"));
217
218        let mut rng = TestRng::default();
219
220        for _ in 0..ITERATIONS {
221            // Sample two random unsigned integers, with the MSB set to 0.
222            let first = U8::<Circuit>::new(Mode::Private, console::U8::new(u8::rand(&mut rng) >> 1));
223            let second = U8::new(Mode::Private, console::U8::new(u8::rand(&mut rng) >> 1));
224            check_homomorphic_addition(&pedersen, first, second, &mut rng);
225
226            // Sample two random unsigned integers, with the MSB set to 0.
227            let first = U16::<Circuit>::new(Mode::Private, console::U16::new(u16::rand(&mut rng) >> 1));
228            let second = U16::new(Mode::Private, console::U16::new(u16::rand(&mut rng) >> 1));
229            check_homomorphic_addition(&pedersen, first, second, &mut rng);
230
231            // Sample two random unsigned integers, with the MSB set to 0.
232            let first = U32::<Circuit>::new(Mode::Private, console::U32::new(u32::rand(&mut rng) >> 1));
233            let second = U32::new(Mode::Private, console::U32::new(u32::rand(&mut rng) >> 1));
234            check_homomorphic_addition(&pedersen, first, second, &mut rng);
235
236            // Sample two random unsigned integers, with the MSB set to 0.
237            let first = U64::<Circuit>::new(Mode::Private, console::U64::new(u64::rand(&mut rng) >> 1));
238            let second = U64::new(Mode::Private, console::U64::new(u64::rand(&mut rng) >> 1));
239            check_homomorphic_addition(&pedersen, first, second, &mut rng);
240        }
241    }
242
243    #[test]
244    fn test_pedersen_homomorphism_private() {
245        fn check_pedersen_homomorphism<
246            P: Commit<Input = Boolean<Circuit>, Randomizer = Scalar<Circuit>, Output = Field<Circuit>>
247                + CommitUncompressed<Input = Boolean<Circuit>, Randomizer = Scalar<Circuit>, Output = Group<Circuit>>,
248        >(
249            pedersen: &P,
250        ) {
251            let mut rng = TestRng::default();
252
253            for _ in 0..ITERATIONS {
254                // Sample two random unsigned integers, with the MSB set to 0.
255                let first = U8::<Circuit>::new(Mode::Private, console::U8::new(u8::rand(&mut rng) >> 1));
256                let second = U8::new(Mode::Private, console::U8::new(u8::rand(&mut rng) >> 1));
257                check_homomorphic_addition(pedersen, first, second, &mut rng);
258
259                // Sample two random unsigned integers, with the MSB set to 0.
260                let first = U16::<Circuit>::new(Mode::Private, console::U16::new(u16::rand(&mut rng) >> 1));
261                let second = U16::new(Mode::Private, console::U16::new(u16::rand(&mut rng) >> 1));
262                check_homomorphic_addition(pedersen, first, second, &mut rng);
263
264                // Sample two random unsigned integers, with the MSB set to 0.
265                let first = U32::<Circuit>::new(Mode::Private, console::U32::new(u32::rand(&mut rng) >> 1));
266                let second = U32::new(Mode::Private, console::U32::new(u32::rand(&mut rng) >> 1));
267                check_homomorphic_addition(pedersen, first, second, &mut rng);
268
269                // Sample two random unsigned integers, with the MSB set to 0.
270                let first = U64::<Circuit>::new(Mode::Private, console::U64::new(u64::rand(&mut rng) >> 1));
271                let second = U64::new(Mode::Private, console::U64::new(u64::rand(&mut rng) >> 1));
272                check_homomorphic_addition(pedersen, first, second, &mut rng);
273
274                // Sample two random unsigned integers, with the MSB set to 0.
275                let first = U128::<Circuit>::new(Mode::Private, console::U128::new(u128::rand(&mut rng) >> 1));
276                let second = U128::new(Mode::Private, console::U128::new(u128::rand(&mut rng) >> 1));
277                check_homomorphic_addition(pedersen, first, second, &mut rng);
278            }
279        }
280
281        // Check Pedersen128.
282        let pedersen128 = Pedersen128::constant(console::Pedersen128::setup("Pedersen128HomomorphismTest"));
283        check_pedersen_homomorphism(&pedersen128);
284    }
285}