snarkvm_debug/file/
verifier.rs

1// Copyright (C) 2019-2023 Aleo Systems 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// http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    prelude::{FromBytes, Identifier, IoResult, Network, Read, ToBytes},
17    synthesizer::{snark::VerifyingKey, Program},
18};
19
20use anyhow::{anyhow, bail, ensure, Result};
21use std::{
22    fs::{self, File},
23    io::Write,
24    path::Path,
25};
26
27static VERIFIER_FILE_EXTENSION: &str = "verifier";
28
29pub struct VerifierFile<N: Network> {
30    /// The function name.
31    function_name: Identifier<N>,
32    /// The verifying key.
33    verifying_key: VerifyingKey<N>,
34}
35
36impl<N: Network> VerifierFile<N> {
37    /// Creates a new verifying key file, given the directory path, function name, and verifying key.
38    pub fn create(directory: &Path, function_name: &Identifier<N>, verifying_key: VerifyingKey<N>) -> Result<Self> {
39        // Ensure the directory path exists.
40        ensure!(directory.exists(), "The build directory does not exist: '{}'", directory.display());
41        // Ensure the function name is valid.
42        ensure!(!Program::is_reserved_keyword(function_name), "Function name is invalid (reserved): {}", function_name);
43
44        // Create the candidate verifier file.
45        let verifier_file = Self { function_name: *function_name, verifying_key };
46
47        // Create the file name.
48        let file_name = format!("{function_name}.{VERIFIER_FILE_EXTENSION}");
49        // Construct the file path.
50        let path = directory.join(file_name);
51        // Write the file (overwriting if it already exists).
52        File::create(&path)?.write_all(&verifier_file.to_bytes_le()?)?;
53
54        // Attempt to load the verifier file.
55        Self::from_filepath(&path)
56    }
57
58    /// Opens the verifier file, given the directory path and function name.
59    pub fn open(directory: &Path, function_name: &Identifier<N>) -> Result<Self> {
60        // Ensure the directory path exists.
61        ensure!(directory.exists(), "The build directory does not exist: '{}'", directory.display());
62
63        // Create the file name.
64        let file_name = format!("{function_name}.{VERIFIER_FILE_EXTENSION}");
65        // Construct the file path.
66        let path = directory.join(file_name);
67        // Ensure the file path exists.
68        ensure!(path.exists(), "The verifier file is missing: '{}'", path.display());
69
70        // Load the verifier file.
71        let verifier = Self::from_filepath(&path)?;
72
73        // Ensure the function name matches.
74        if verifier.function_name() != function_name {
75            bail!(
76                "The verifier file for '{}' contains an incorrect function name of '{}'",
77                function_name,
78                verifier.function_name()
79            );
80        }
81
82        Ok(verifier)
83    }
84
85    /// Returns `true` if the verifier file for the given function name exists at the given directory.
86    pub fn exists_at(directory: &Path, function_name: &Identifier<N>) -> bool {
87        // Create the file name.
88        let file_name = format!("{function_name}.{VERIFIER_FILE_EXTENSION}");
89        // Construct the file path.
90        let path = directory.join(file_name);
91        // Ensure the path is well-formed.
92        Self::check_path(&path).is_ok() && path.exists()
93    }
94
95    /// Returns the function name.
96    pub const fn function_name(&self) -> &Identifier<N> {
97        &self.function_name
98    }
99
100    /// Returns the verifying key.
101    pub const fn verifying_key(&self) -> &VerifyingKey<N> {
102        &self.verifying_key
103    }
104
105    /// Removes the file at the given path, if it exists.
106    pub fn remove(&self, path: &Path) -> Result<()> {
107        // If the path does not exist, do nothing.
108        if !path.exists() {
109            Ok(())
110        } else {
111            // Ensure the path is well-formed.
112            Self::check_path(path)?;
113            // Remove the file.
114            Ok(fs::remove_file(path)?)
115        }
116    }
117}
118
119impl<N: Network> VerifierFile<N> {
120    /// Checks that the given path has the correct file extension.
121    fn check_path(path: &Path) -> Result<()> {
122        // Ensure the given path is a file.
123        ensure!(path.is_file(), "The path is not a file.");
124
125        // Ensure the given path has the correct file extension.
126        let extension = path.extension().ok_or_else(|| anyhow!("File extension not found."))?;
127        ensure!(extension == VERIFIER_FILE_EXTENSION, "File extension is incorrect.");
128
129        // Ensure the given path exists.
130        ensure!(path.exists(), "File does not exist: {}", path.display());
131
132        Ok(())
133    }
134
135    /// Reads the verifier from the given file path, if it exists.
136    fn from_filepath(file: &Path) -> Result<Self> {
137        // Ensure the path is well-formed.
138        Self::check_path(file)?;
139        // Parse the verifier file bytes.
140        let verifier = Self::from_bytes_le(&fs::read(file)?)?;
141
142        // Retrieve the file stem.
143        let file_stem = file
144            .file_stem()
145            .ok_or_else(|| anyhow!("File name not found."))?
146            .to_str()
147            .ok_or_else(|| anyhow!("File name not found."))?
148            .to_string();
149        // Ensure the function name matches the file stem.
150        ensure!(verifier.function_name.to_string() == file_stem, "Function name does not match file stem.");
151
152        // Return the verifier file.
153        Ok(verifier)
154    }
155
156    /// Writes the verifier to the file.
157    pub fn write_to(&self, path: &Path) -> Result<()> {
158        // Ensure the path is well-formed.
159        Self::check_path(path)?;
160
161        // Retrieve the file stem.
162        let file_stem = path
163            .file_name()
164            .ok_or_else(|| anyhow!("File name not found."))?
165            .to_str()
166            .ok_or_else(|| anyhow!("File name not found."))?
167            .to_string();
168        // Ensure the function name matches the file stem.
169        ensure!(self.function_name.to_string() == file_stem, "Function name does not match file stem.");
170
171        // Write to the file (overwriting if it already exists).
172        Ok(File::create(path)?.write_all(&self.to_bytes_le()?)?)
173    }
174}
175
176impl<N: Network> FromBytes for VerifierFile<N> {
177    /// Reads the verifier file from a buffer.
178    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
179        let function_name = Identifier::read_le(&mut reader)?;
180        let verifying_key = FromBytes::read_le(&mut reader)?;
181        Ok(Self { function_name, verifying_key })
182    }
183}
184
185impl<N: Network> ToBytes for VerifierFile<N> {
186    /// Writes the verifier file to a buffer.
187    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
188        self.function_name.write_le(&mut writer)?;
189        self.verifying_key.write_le(&mut writer)
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::{
197        prelude::{FromStr, Parser, TestRng},
198        synthesizer::Process,
199    };
200
201    type CurrentNetwork = snarkvm_console::network::Testnet3;
202    type CurrentAleo = snarkvm_circuit::AleoV0;
203
204    fn temp_dir() -> std::path::PathBuf {
205        tempfile::tempdir().expect("Failed to open temporary directory").into_path()
206    }
207
208    #[test]
209    fn test_create_and_open() {
210        // Initialize a temporary directory.
211        let directory = temp_dir();
212
213        let program_string = r"
214program token.aleo;
215
216record token:
217    owner as address.private;
218    token_amount as u64.private;
219
220function compute:
221    input r0 as token.record;
222    add r0.token_amount r0.token_amount into r1;
223    output r1 as u64.private;";
224
225        // Initialize a new program.
226        let (string, program) = Program::<CurrentNetwork>::parse(program_string).unwrap();
227        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
228
229        // Construct the process.
230        let mut process = Process::load().unwrap();
231        // Add the program to the process.
232        process.add_program(&program).unwrap();
233
234        // Prepare the function name.
235        let function_name = Identifier::from_str("compute").unwrap();
236
237        // Sample the verifying key.
238        process.synthesize_key::<CurrentAleo, _>(program.id(), &function_name, &mut TestRng::default()).unwrap();
239
240        // Retrieve the verifying key.
241        let verifying_key = process.get_verifying_key(program.id(), function_name).unwrap();
242
243        // Create the verifier file at the path.
244        let expected = VerifierFile::create(&directory, &function_name, verifying_key).unwrap();
245        // Open the verifier file at the path.
246        let candidate = VerifierFile::open(&directory, &function_name).unwrap();
247        // Ensure the verifier files are equal.
248        assert_eq!(expected.to_bytes_le().unwrap(), candidate.to_bytes_le().unwrap());
249    }
250}