1use super::*;
17
18impl<A: Aleo> Request<A> {
19 pub fn verify(
26 &self,
27 input_types: &[console::ValueType<A::Network>],
28 tpk: &Group<A>,
29 root_tvk: Option<Field<A>>,
30 is_root: Boolean<A>,
31 program_checksum: Option<Field<A>>,
32 ) -> Boolean<A> {
33 let function_id = compute_function_id(&self.network_id, &self.program_id, &self.function_name);
35
36 let is_root = Ternary::ternary(&is_root, &Field::<A>::one(), &Field::<A>::zero());
38
39 let mut message = Vec::with_capacity(3 + 4 * self.input_ids.len());
41 message.push(self.tvk.clone());
42 message.push(self.tcm.clone());
43 message.push(function_id);
44 message.push(is_root);
45 if let Some(program_checksum) = program_checksum {
47 message.push(program_checksum);
48 }
49
50 let (input_checks, append_to_message) = Self::check_input_ids::<true>(
52 &self.network_id,
53 &self.program_id,
54 &self.function_name,
55 &self.input_ids,
56 &self.inputs,
57 input_types,
58 &self.signer,
59 &self.sk_tag,
60 &self.tvk,
61 &self.tcm,
62 Some(&self.signature),
63 );
64 match append_to_message {
66 Some(append_to_message) => message.extend(append_to_message),
67 None => A::halt("Missing input elements in request verification"),
68 }
69
70 let root_tvk = root_tvk.unwrap_or(Field::<A>::new(Mode::Private, self.tvk.eject_value()));
72
73 let tpk_checks = {
75 let tcm = A::hash_psd2(&[self.tvk.clone()]);
77 let scm = A::hash_psd2(&[self.signer.to_field(), root_tvk]);
79
80 tpk.is_equal(&self.to_tpk())
82 & tcm.is_equal(&self.tcm)
84 & scm.is_equal(&self.scm)
86 };
87
88 let signature_checks = {
91 let pk_sig = self.signature.compute_key().pk_sig();
93 let pr_sig = self.signature.compute_key().pr_sig();
95
96 let mut preimage = Vec::with_capacity(4 + message.len());
98 preimage.extend([tpk, pk_sig, pr_sig].map(|point| point.to_x_coordinate()));
99 preimage.push(self.signer.to_field());
100 preimage.extend_from_slice(&message);
101
102 let candidate_challenge = A::hash_to_scalar_psd8(&preimage);
104 let candidate_address = self.signature.compute_key().to_address();
106
107 self.signature.challenge().is_equal(&candidate_challenge) & self.signer.is_equal(&candidate_address)
109 };
110
111 signature_checks & input_checks & tpk_checks
113 }
114
115 pub fn check_input_ids<const CREATE_MESSAGE: bool>(
118 network_id: &U16<A>,
119 program_id: &ProgramID<A>,
120 function_name: &Identifier<A>,
121 input_ids: &[InputID<A>],
122 inputs: &[Value<A>],
123 input_types: &[console::ValueType<A::Network>],
124 signer: &Address<A>,
125 sk_tag: &Field<A>,
126 tvk: &Field<A>,
127 tcm: &Field<A>,
128 signature: Option<&Signature<A>>,
129 ) -> (Boolean<A>, Option<Vec<Field<A>>>) {
130 match CREATE_MESSAGE {
132 true => assert!(signature.is_some()),
133 false => assert!(signature.is_none()),
134 }
135
136 let function_id = compute_function_id(network_id, program_id, function_name);
138
139 let mut message = Vec::new();
141
142 let input_checks = input_ids
144 .iter()
145 .zip_eq(inputs)
146 .zip_eq(input_types)
147 .enumerate()
148 .map(|(index, ((input_id, input), input_type))| {
149 match input_id {
150 InputID::Constant(input_hash) => {
152 if CREATE_MESSAGE {
154 message.push(input_hash.clone());
155 }
156
157 let input_index = Field::constant(console::Field::from_u16(index as u16));
159 let mut preimage = Vec::new();
161 preimage.push(function_id.clone());
162 preimage.extend(input.to_fields());
163 preimage.push(tcm.clone());
164 preimage.push(input_index);
165
166 match &input {
168 Value::Plaintext(..) => input_hash.is_equal(&A::hash_psd8(&preimage)),
169 Value::Record(..) => A::halt("Expected a constant plaintext input, found a record input"),
171 Value::Future(..) => A::halt("Expected a constant plaintext input, found a future input"),
172 }
173 }
174 InputID::Public(input_hash) => {
176 if CREATE_MESSAGE {
178 message.push(input_hash.clone());
179 }
180
181 let input_index = Field::constant(console::Field::from_u16(index as u16));
183 let mut preimage = Vec::new();
185 preimage.push(function_id.clone());
186 preimage.extend(input.to_fields());
187 preimage.push(tcm.clone());
188 preimage.push(input_index);
189
190 match &input {
192 Value::Plaintext(..) => input_hash.is_equal(&A::hash_psd8(&preimage)),
193 Value::Record(..) => A::halt("Expected a public plaintext input, found a record input"),
195 Value::Future(..) => A::halt("Expected a public plaintext input, found a future input"),
196 }
197 }
198 InputID::Private(input_hash) => {
200 if CREATE_MESSAGE {
202 message.push(input_hash.clone());
203 }
204
205 let input_index = Field::constant(console::Field::from_u16(index as u16));
207 let input_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), input_index]);
209 let ciphertext = match &input {
211 Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(input_view_key),
212 Value::Record(..) => A::halt("Expected a private plaintext input, found a record input"),
214 Value::Future(..) => A::halt("Expected a private plaintext input, found a future input"),
215 };
216
217 input_hash.is_equal(&A::hash_psd8(&ciphertext.to_fields()))
219 }
220 InputID::Record(commitment, gamma, record_view_key, serial_number, tag) => {
222 let record = match &input {
224 Value::Record(record) => record,
225 Value::Plaintext(..) => A::halt("Expected a record input, found a plaintext input"),
227 Value::Future(..) => A::halt("Expected a record input, found a future input"),
228 };
229 let record_name = match input_type {
231 console::ValueType::Record(record_name) => Identifier::constant(*record_name),
232 _ => A::halt(format!("Expected a record input at input {index}")),
234 };
235 let candidate_commitment = record.to_commitment(program_id, &record_name, record_view_key);
237 let candidate_serial_number =
239 Record::<A, Plaintext<A>>::serial_number_from_gamma(gamma, candidate_commitment.clone());
240 let candidate_tag =
242 Record::<A, Plaintext<A>>::tag(sk_tag.clone(), candidate_commitment.clone());
243
244 if CREATE_MESSAGE {
245 let signature = match signature {
247 Some(signature) => signature,
248 None => A::halt("Missing signature in logic to check input IDs"),
249 };
250 let challenge = signature.challenge();
252 let response = signature.response();
254
255 let h = A::hash_to_group_psd2(&[A::serial_number_domain(), candidate_commitment.clone()]);
257 let h_r = (gamma.deref() * challenge) + (&h * response);
259
260 message.extend([h, h_r, *gamma.clone()].iter().map(|point| point.to_x_coordinate()));
262 message.push(candidate_tag.clone());
263 }
264
265 serial_number.is_equal(&candidate_serial_number)
267 & commitment.is_equal(&candidate_commitment)
269 & tag.is_equal(&candidate_tag)
271 & record.owner().deref().is_equal(signer)
273 }
274 InputID::ExternalRecord(input_hash) => {
276 if CREATE_MESSAGE {
278 message.push(input_hash.clone());
279 }
280
281 let record = match &input {
283 Value::Record(record) => record,
284 Value::Plaintext(..) => {
286 A::halt("Expected an external record input, found a plaintext input")
287 }
288 Value::Future(..) => A::halt("Expected an external record input, found a future input"),
289 };
290
291 let input_index = Field::constant(console::Field::from_u16(index as u16));
293 let mut preimage = Vec::new();
295 preimage.push(function_id.clone());
296 preimage.extend(record.to_fields());
297 preimage.push(tvk.clone());
298 preimage.push(input_index);
299
300 input_hash.is_equal(&A::hash_psd8(&preimage))
302 }
303 }
304 })
305 .fold(Boolean::constant(true), |acc, x| acc & x);
306
307 match CREATE_MESSAGE {
309 true => (input_checks, Some(message)),
310 false => match message.is_empty() {
311 true => (input_checks, None),
312 false => A::halt("Malformed synthesis of the logic to check input IDs"),
313 },
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::Circuit;
322 use snarkvm_utilities::TestRng;
323
324 use anyhow::Result;
325
326 pub(crate) const ITERATIONS: usize = 50;
327
328 fn check_verify(
329 mode: Mode,
330 num_constants: u64,
331 num_public: u64,
332 num_private: u64,
333 num_constraints: u64,
334 set_program_checksum: bool,
335 ) -> Result<()> {
336 let rng = &mut TestRng::default();
337
338 for i in 0..ITERATIONS {
339 let private_key = snarkvm_console_account::PrivateKey::new(rng)?;
341 let address = snarkvm_console_account::Address::try_from(&private_key).unwrap();
342
343 let program_id = console::ProgramID::from_str("token.aleo")?;
345 let function_name = console::Identifier::from_str("transfer")?;
346
347 let record_string = format!(
349 "{{ owner: {address}.private, token_amount: 100u64.private, _nonce: 0group.public, _version: 1u8.public }}"
350 );
351
352 let input_constant =
354 console::Value::<<Circuit as Environment>::Network>::from_str("{ token_amount: 9876543210u128 }")
355 .unwrap();
356 let input_public =
357 console::Value::<<Circuit as Environment>::Network>::from_str("{ token_amount: 9876543210u128 }")
358 .unwrap();
359 let input_private =
360 console::Value::<<Circuit as Environment>::Network>::from_str("{ token_amount: 9876543210u128 }")
361 .unwrap();
362 let input_record = console::Value::<<Circuit as Environment>::Network>::from_str(&record_string).unwrap();
363 let input_external_record =
364 console::Value::<<Circuit as Environment>::Network>::from_str(&record_string).unwrap();
365 let inputs = [input_constant, input_public, input_private, input_record, input_external_record];
366
367 let input_types = vec![
369 console::ValueType::from_str("amount.constant").unwrap(),
370 console::ValueType::from_str("amount.public").unwrap(),
371 console::ValueType::from_str("amount.private").unwrap(),
372 console::ValueType::from_str("token.record").unwrap(),
373 console::ValueType::from_str("token.aleo/token.record").unwrap(),
374 ];
375
376 let root_tvk = None;
378 let is_root = true;
380 let program_checksum = set_program_checksum.then(|| console::Field::from_u64(i as u64));
382
383 let request = console::Request::sign(
385 &private_key,
386 program_id,
387 function_name,
388 inputs.iter(),
389 &input_types,
390 root_tvk,
391 is_root,
392 program_checksum,
393 rng,
394 )?;
395 assert!(request.verify(&input_types, is_root, program_checksum));
396
397 let tpk = Group::<Circuit>::new(mode, request.to_tpk());
399 let request = Request::<Circuit>::new(mode, request);
400 let is_root = Boolean::new(mode, is_root);
401 let program_checksum = program_checksum.map(|hash| Field::<Circuit>::new(mode, hash));
402
403 Circuit::scope(format!("Request {i}"), || {
404 let root_tvk = None;
405 let candidate = request.verify(&input_types, &tpk, root_tvk, is_root, program_checksum);
406 assert!(candidate.eject_value());
407 match mode.is_constant() {
408 true => assert_scope!(<=num_constants, <=num_public, <=num_private, <=num_constraints),
409 false => assert_scope!(<=num_constants, num_public, num_private, num_constraints),
410 }
411 });
412
413 Circuit::scope(format!("Request {i}"), || {
414 let (candidate, _) = Request::check_input_ids::<false>(
415 request.network_id(),
416 request.program_id(),
417 request.function_name(),
418 request.input_ids(),
419 request.inputs(),
420 &input_types,
421 request.signer(),
422 request.sk_tag(),
423 request.tvk(),
424 request.tcm(),
425 None,
426 );
427 assert!(candidate.eject_value());
428 });
429 Circuit::reset();
430 }
431 Ok(())
432 }
433
434 #[test]
435 fn test_sign_and_verify_constant() -> Result<()> {
436 check_verify(Mode::Constant, 43440, 0, 21629, 21656, false)?;
440 check_verify(Mode::Constant, 43440, 0, 21629, 21656, true)
441 }
442
443 #[test]
444 fn test_sign_and_verify_public() -> Result<()> {
445 check_verify(Mode::Public, 40938, 0, 30031, 30062, false)?;
446 check_verify(Mode::Public, 40938, 0, 30546, 30577, true)
447 }
448
449 #[test]
450 fn test_sign_and_verify_private() -> Result<()> {
451 check_verify(Mode::Private, 40938, 0, 30031, 30062, false)?;
452 check_verify(Mode::Private, 40938, 0, 30546, 30577, true)
453 }
454}