snarkvm_ledger_puzzle/solutions/
mod.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
16mod bytes;
17mod serialize;
18mod string;
19
20use crate::{Solution, SolutionID};
21use console::{network::prelude::*, prelude::DeserializeExt, types::Field};
22use indexmap::IndexMap;
23
24/// The individual solutions.
25#[derive(Clone, Eq, PartialEq)]
26pub struct PuzzleSolutions<N: Network> {
27    /// The solutions for the puzzle.
28    solutions: IndexMap<SolutionID<N>, Solution<N>>,
29}
30
31impl<N: Network> PuzzleSolutions<N> {
32    /// Initializes a new instance of the solutions.
33    pub fn new(solutions: Vec<Solution<N>>) -> Result<Self> {
34        // Ensure the solutions are not empty.
35        ensure!(!solutions.is_empty(), "There are no solutions to verify for the puzzle");
36        // Ensure the number of solutions does not exceed `MAX_SOLUTIONS`.
37        if solutions.len() > N::MAX_SOLUTIONS {
38            bail!("Exceeded the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS);
39        }
40        // Ensure the solution IDs are unique.
41        if has_duplicates(solutions.iter().map(Solution::id)) {
42            bail!("The solutions contain duplicate solution IDs");
43        }
44        // Return the solutions.
45        Ok(Self { solutions: solutions.into_iter().map(|solution| (solution.id(), solution)).collect() })
46    }
47
48    /// Returns the solution IDs.
49    pub fn solution_ids(&self) -> impl '_ + Iterator<Item = &SolutionID<N>> {
50        self.solutions.keys()
51    }
52
53    /// Returns the number of solutions.
54    pub fn len(&self) -> usize {
55        self.solutions.len()
56    }
57
58    /// Returns `true` if there are no solutions.
59    pub fn is_empty(&self) -> bool {
60        self.solutions.is_empty()
61    }
62
63    /// Returns the solution for the given solution ID.
64    pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Option<&Solution<N>> {
65        self.solutions.get(solution_id)
66    }
67
68    /// Returns the accumulator challenge point.
69    pub fn to_accumulator_point(&self) -> Result<Field<N>> {
70        // Encode the solution IDs as field elements.
71        let mut preimage = self.solution_ids().map(|id| Field::from_u64(**id)).collect::<Vec<_>>();
72        // Pad the preimage to the required length.
73        preimage.resize(N::MAX_SOLUTIONS, Field::zero());
74        // Hash the preimage to obtain the accumulator point.
75        N::hash_psd8(&preimage)
76    }
77}
78
79impl<N: Network> Deref for PuzzleSolutions<N> {
80    type Target = IndexMap<SolutionID<N>, Solution<N>>;
81
82    fn deref(&self) -> &Self::Target {
83        &self.solutions
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::PartialSolution;
91    use console::account::{Address, PrivateKey};
92
93    use std::collections::HashSet;
94
95    type CurrentNetwork = console::network::MainnetV0;
96
97    /// Returns the solutions for the given number of solutions.
98    pub(crate) fn sample_solutions_with_count(
99        num_solutions: usize,
100        rng: &mut TestRng,
101    ) -> PuzzleSolutions<CurrentNetwork> {
102        // Sample a new solutions.
103        let mut solutions = vec![];
104        for _ in 0..num_solutions {
105            let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
106            let address = Address::try_from(private_key).unwrap();
107
108            let partial_solution = PartialSolution::new(rng.r#gen(), address, u64::rand(rng)).unwrap();
109            let solution = Solution::new(partial_solution, u64::rand(rng));
110            solutions.push(solution);
111        }
112        PuzzleSolutions::new(solutions).unwrap()
113    }
114
115    #[test]
116    fn test_new_is_not_empty() {
117        // Ensure the solutions are not empty.
118        assert!(PuzzleSolutions::<CurrentNetwork>::new(vec![]).is_err());
119    }
120
121    #[test]
122    fn test_len() {
123        let mut rng = TestRng::default();
124
125        for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
126            // Sample random solutions.
127            let solutions = sample_solutions_with_count(num_solutions, &mut rng);
128            // Ensure the number of solutions is correct.
129            assert_eq!(num_solutions, solutions.len());
130        }
131    }
132
133    #[test]
134    fn test_is_empty() {
135        let mut rng = TestRng::default();
136
137        for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
138            // Sample random solutions.
139            let solutions = sample_solutions_with_count(num_solutions, &mut rng);
140            // Ensure the solutions are not empty.
141            assert!(!solutions.is_empty());
142        }
143    }
144
145    #[test]
146    fn test_solution_ids() {
147        let mut rng = TestRng::default();
148
149        for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
150            // Sample random solutions.
151            let solutions = sample_solutions_with_count(num_solutions, &mut rng);
152            // Ensure the solution IDs are unique.
153            assert_eq!(num_solutions, solutions.solution_ids().collect::<HashSet<_>>().len());
154        }
155    }
156
157    #[test]
158    fn test_get_solution() {
159        let mut rng = TestRng::default();
160
161        for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
162            // Sample random solutions.
163            let solutions = sample_solutions_with_count(num_solutions, &mut rng);
164            // Ensure the solutions are not empty.
165            for solution_id in solutions.solution_ids() {
166                assert_eq!(solutions.get_solution(solution_id).unwrap().id(), *solution_id);
167            }
168        }
169    }
170
171    #[test]
172    fn test_to_accumulator_point() {
173        let mut rng = TestRng::default();
174
175        for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
176            // Sample random solutions.
177            let solutions = crate::solutions::tests::sample_solutions_with_count(num_solutions, &mut rng);
178            // Compute the candidate accumulator point.
179            let candidate = solutions.to_accumulator_point().unwrap();
180            // Compute the expected accumulator point.
181            let mut preimage = vec![Field::zero(); <CurrentNetwork as Network>::MAX_SOLUTIONS];
182            for (i, id) in solutions.keys().enumerate() {
183                preimage[i] = Field::from_u64(**id);
184            }
185            let expected = <CurrentNetwork as Network>::hash_psd8(&preimage).unwrap();
186            assert_eq!(expected, candidate);
187        }
188    }
189}