snarkvm_console_program/request/
verify.rs1use super::*;
17
18impl<N: Network> Request<N> {
19 pub fn verify(&self, input_types: &[ValueType<N>], is_root: bool, program_checksum: Option<Field<N>>) -> bool {
25 {
27 match N::hash_psd2(&[self.tvk]) {
29 Ok(tcm) => {
30 if tcm != self.tcm {
32 eprintln!("Invalid transition commitment in request.");
33 return false;
34 }
35 }
36 Err(error) => {
37 eprintln!("Failed to compute transition commitment in request verification: {error}");
38 return false;
39 }
40 }
41 }
42
43 let challenge = self.signature.challenge();
45 let response = self.signature.response();
47
48 let function_id = match compute_function_id(&self.network_id, &self.program_id, &self.function_name) {
50 Ok(function_id) => function_id,
51 Err(error) => {
52 eprintln!("Failed to construct the function ID: {error}");
53 return false;
54 }
55 };
56
57 let is_root = if is_root { Field::<N>::one() } else { Field::<N>::zero() };
59
60 let mut message = Vec::with_capacity(5 + 4 * self.input_ids.len());
63 message.push(self.tvk);
64 message.push(self.tcm);
65 message.push(function_id);
66 message.push(is_root);
67
68 if let Some(program_checksum) = program_checksum {
70 message.push(program_checksum);
71 }
72
73 if let Err(error) = self.input_ids.iter().zip_eq(&self.inputs).zip_eq(input_types).enumerate().try_for_each(
74 |(index, ((input_id, input), input_type))| {
75 let index = u16::try_from(index).or_halt_with::<N>("Input index exceeds u16");
77
78 match input_id {
79 InputID::Constant(input_hash) => {
81 let candidate = InputID::constant(function_id, input, self.tcm, index)?;
82 ensure!(*input_hash == *candidate.id(), "Expected a constant input with the same hash");
83 message.push(*candidate.id());
84 }
85 InputID::Public(input_hash) => {
87 let candidate = InputID::public(function_id, input, self.tcm, index)?;
88 ensure!(*input_hash == *candidate.id(), "Expected a public input with the same hash");
89 message.push(*candidate.id());
90 }
91 InputID::Private(input_hash) => {
93 let candidate = InputID::private(function_id, input, self.tvk, index)?;
94 ensure!(*input_hash == *candidate.id(), "Expected a private input with the same hash");
95 message.push(*candidate.id());
96 }
97 InputID::Record(commitment, gamma, record_view_key, serial_number, tag) => {
99 let record = match &input {
101 Value::Record(record) => record,
102 Value::Plaintext(..) => bail!("Expected a record input, found a plaintext input"),
103 Value::Future(..) => bail!("Expected a record input, found a future input"),
104 Value::DynamicRecord(..) => bail!("Expected a record input, found a dynamic record input"),
105 Value::DynamicFuture(..) => bail!("Expected a record input, found a dynamic future input"),
106 };
107 let record_name = match input_type {
109 ValueType::Record(record_name) => record_name,
110 _ => bail!("Expected a record type at input {index}"),
111 };
112 ensure!(**record.owner() == self.signer, "Input record does not belong to the signer");
114
115 let candidate_commitment =
117 record.to_commitment(&self.program_id, record_name, record_view_key)?;
118 ensure!(
119 *commitment == candidate_commitment,
120 "Expected a record input with the same commitment"
121 );
122
123 let candidate_sn = Record::<N, Plaintext<N>>::serial_number_from_gamma(gamma, *commitment)?;
125 ensure!(*serial_number == candidate_sn, "Expected a record input with the same serial number");
126
127 let h = N::hash_to_group_psd2(&[N::serial_number_domain(), *commitment])?;
129 let h_r = (*gamma * challenge) + (h * response);
131
132 let candidate_tag = N::hash_psd2(&[self.sk_tag, *commitment])?;
134 ensure!(*tag == candidate_tag, "Expected a record input with the same tag");
135
136 message.extend([h, h_r, *gamma].iter().map(|point| point.to_x_coordinate()));
138 message.push(*tag);
139 }
140 InputID::ExternalRecord(input_hash) => {
142 let candidate = InputID::external_record(function_id, input, self.tvk, index)?;
143 ensure!(*input_hash == *candidate.id(), "Expected an external record input with the same hash");
144 message.push(*candidate.id());
145 }
146 InputID::DynamicRecord(input_hash) => {
148 let candidate = InputID::dynamic_record(function_id, input, self.tvk, index)?;
149 ensure!(*input_hash == *candidate.id(), "Expected a dynamic record input with the same hash");
150 message.push(*candidate.id());
151 }
152 }
153 Ok(())
154 },
155 ) {
156 eprintln!("Request verification failed on input checks: {error}");
157 return false;
158 }
159
160 self.signature.verify(&self.signer, &message)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use snarkvm_console_account::PrivateKey;
169 use snarkvm_console_network::MainnetV0;
170
171 type CurrentNetwork = MainnetV0;
172
173 pub(crate) const ITERATIONS: usize = 1000;
174
175 #[test]
176 fn test_sign_and_verify() {
177 let rng = &mut TestRng::default();
178
179 for _i in 0..ITERATIONS {
180 let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
182 let address = Address::try_from(&private_key).unwrap();
183
184 let program_id = ProgramID::from_str("token.aleo").unwrap();
186 let function_name = Identifier::from_str("transfer").unwrap();
187
188 let record_string = format!(
190 "{{ owner: {address}.private, token_amount: 100u64.private, _nonce: 2293253577170800572742339369209137467208538700597121244293392265726446806023group.public }}"
191 );
192
193 let input_constant = Value::from_str("{ token_amount: 9876543210u128 }").unwrap();
195 let input_public = Value::from_str("{ token_amount: 9876543210u128 }").unwrap();
196 let input_private = Value::from_str("{ token_amount: 9876543210u128 }").unwrap();
197 let input_record = Value::from_str(&record_string).unwrap();
198 let input_external_record = Value::from_str(&record_string).unwrap();
199 let inputs = [input_constant, input_public, input_private, input_record, input_external_record];
200
201 let input_types = vec![
203 ValueType::from_str("amount.constant").unwrap(),
204 ValueType::from_str("amount.public").unwrap(),
205 ValueType::from_str("amount.private").unwrap(),
206 ValueType::from_str("token.record").unwrap(),
207 ValueType::from_str("token.aleo/token.record").unwrap(),
208 ];
209
210 let root_tvk = None;
212 let is_root = Uniform::rand(rng);
214 let program_checksum = match bool::rand(rng) {
216 true => Some(Field::rand(rng)),
217 false => None,
218 };
219
220 let is_dynamic = bool::rand(rng);
222 let request = Request::sign(
224 &private_key,
225 program_id,
226 function_name,
227 inputs.into_iter(),
228 &input_types,
229 root_tvk,
230 is_root,
231 program_checksum,
232 is_dynamic,
233 rng,
234 )
235 .unwrap();
236 assert!(request.verify(&input_types, is_root, program_checksum));
237 }
238 }
239
240 #[test]
241 fn test_sign_record_as_dynamic_record() {
242 let rng = &mut TestRng::default();
243
244 for _ in 0..ITERATIONS {
245 let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
247 let address = Address::try_from(&private_key).unwrap();
248
249 let program_id = ProgramID::from_str("token.aleo").unwrap();
251 let function_name = Identifier::from_str("transfer").unwrap();
252
253 let record_string = format!(
255 "{{ owner: {address}.private, token_amount: 100u64.private, _nonce: 2293253577170800572742339369209137467208538700597121244293392265726446806023group.public }}"
256 );
257
258 let input_record = Value::from_str(&record_string).unwrap();
260 assert!(matches!(input_record, Value::Record(..)));
261 let inputs = [input_record];
262
263 let input_types = vec![ValueType::DynamicRecord];
265
266 let is_root = Uniform::rand(rng);
268 let program_checksum = match bool::rand(rng) {
270 true => Some(Field::rand(rng)),
271 false => None,
272 };
273
274 let request = Request::sign(
276 &private_key,
277 program_id,
278 function_name,
279 inputs.into_iter(),
280 &input_types,
281 None,
282 is_root,
283 program_checksum,
284 true,
285 rng,
286 )
287 .unwrap();
288
289 assert!(matches!(request.inputs()[0], Value::DynamicRecord(..)));
291
292 assert!(request.verify(&input_types, is_root, program_checksum));
294 }
295 }
296}