1use std::{borrow::Borrow, collections::BTreeMap};
5
6use super::{types::*, utils};
7use crate::bulletproofs::{
8 range_proof::{verify_in_range, RangeProof, VerificationError},
9 set_membership_proof::verify as verify_set_membership,
10 set_non_membership_proof::verify as verify_set_non_membership,
11 utils::Generators,
12};
13
14use super::id_proof_types::*;
15use crate::{
16 curve_arithmetic::{Curve, Field},
17 pedersen_commitment::{
18 Commitment, CommitmentKey as PedersenKey, Randomness as PedersenRandomness, Value,
19 },
20 random_oracle::RandomOracle,
21 sigma_protocols::{common::verify as sigma_verify, dlog::Dlog},
22};
23use sha2::{Digest, Sha256};
24
25pub fn verify_attribute<C: Curve, AttributeType: Attribute<C::Scalar>>(
34 keys: &PedersenKey<C>,
35 attribute: &AttributeType,
36 r: &PedersenRandomness<C>,
37 c: &Commitment<C>,
38) -> bool {
39 let s = Value::new(attribute.to_field_element());
40 keys.open(&s, r, c)
41}
42
43#[allow(clippy::too_many_arguments)]
55pub fn verify_attribute_range<C: Curve, AttributeType: Attribute<C::Scalar>>(
56 version: ProofVersion,
57 transcript: &mut RandomOracle,
58 keys: &PedersenKey<C>,
59 gens: &Generators<C>,
60 lower: &AttributeType,
61 upper: &AttributeType,
62 c: &Commitment<C>,
63 proof: &RangeProof<C>,
64) -> Result<(), VerificationError> {
65 let a = lower.to_field_element();
66 let b = upper.to_field_element();
67 match version {
68 ProofVersion::Version1 => {
69 let mut transcript_v1 = RandomOracle::domain("attribute_range_proof");
70 verify_in_range(
71 ProofVersion::Version1,
72 &mut transcript_v1,
73 keys,
74 gens,
75 a,
76 b,
77 c,
78 proof,
79 )
80 }
81 ProofVersion::Version2 => {
82 transcript.add_bytes(b"AttributeRangeProof");
83 transcript.append_message(b"a", &a);
84 transcript.append_message(b"b", &b);
85 verify_in_range(
86 ProofVersion::Version2,
87 transcript,
88 keys,
89 gens,
90 a,
91 b,
92 c,
93 proof,
94 )
95 }
96 }
97}
98
99pub fn verify_account_ownership(
111 public_data: &CredentialPublicKeys,
112 account: AccountAddress,
113 challenge: &[u8],
114 proof: &AccountOwnershipProof,
115) -> bool {
116 let mut hasher = Sha256::new();
117 hasher.update(account.0);
118 hasher.update([0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
119 hasher.update(b"account_ownership_proof");
120 hasher.update(challenge);
121 let to_sign = &hasher.finalize();
122 utils::verify_account_ownership_proof(&public_data.keys, public_data.threshold, proof, to_sign)
123}
124
125impl<C: Curve, AttributeType: Attribute<C::Scalar>> StatementWithContext<C, AttributeType> {
126 pub fn verify(
136 &self,
137 version: ProofVersion,
138 challenge: &[u8],
139 global: &GlobalContext<C>,
140 commitments: &CredentialDeploymentCommitments<C>,
141 proofs: &Proof<C, AttributeType>,
142 ) -> bool {
143 self.statement.verify(
144 version,
145 challenge,
146 global,
147 &self.credential,
148 commitments,
149 proofs,
150 )
151 }
152}
153
154impl<
155 C: Curve,
156 TagType: std::cmp::Ord + crate::common::Serialize,
157 AttributeType: Attribute<C::Scalar>,
158 > AtomicStatement<C, TagType, AttributeType>
159{
160 pub(crate) fn verify<Q: std::cmp::Ord + Borrow<TagType>>(
161 &self,
162 version: ProofVersion,
163 global: &GlobalContext<C>,
164 transcript: &mut RandomOracle,
165 cmm_attributes: &BTreeMap<Q, Commitment<C>>,
166 proof: &AtomicProof<C, AttributeType>,
167 ) -> bool {
168 match (self, proof) {
169 (
170 AtomicStatement::RevealAttribute {
171 statement: RevealAttributeStatement { attribute_tag },
172 },
173 AtomicProof::RevealAttribute { attribute, proof },
174 ) => {
175 let maybe_com = cmm_attributes.get(attribute_tag);
176 if let Some(com) = maybe_com {
177 let x = attribute.to_field_element();
180 transcript.add_bytes(b"RevealAttributeDlogProof");
181 transcript.append_message(b"x", &x);
183 if version >= ProofVersion::Version2 {
184 transcript.append_message(b"keys", &global.on_chain_commitment_key);
185 transcript.append_message(b"C", &com);
186 }
187 let mut minus_x = x;
188 minus_x.negate();
189 let g_minus_x = global.on_chain_commitment_key.g.mul_by_scalar(&minus_x);
190 let public = com.plus_point(&g_minus_x);
191 let verifier = Dlog {
192 public, coeff: global.on_chain_commitment_key.h, };
195 if !sigma_verify(transcript, &verifier, proof) {
196 return false;
197 }
198 } else {
199 return false;
200 }
201 }
202 (
203 AtomicStatement::AttributeInRange { statement },
204 AtomicProof::AttributeInRange { proof },
205 ) => {
206 let maybe_com = cmm_attributes.get(&statement.attribute_tag);
207 if let Some(com) = maybe_com {
208 if super::id_verifier::verify_attribute_range(
211 version,
212 transcript,
213 &global.on_chain_commitment_key,
214 global.bulletproof_generators(),
215 &statement.lower,
216 &statement.upper,
217 com,
218 proof,
219 )
220 .is_err()
221 {
222 return false;
223 }
224 } else {
225 return false;
226 }
227 }
228 (
229 AtomicStatement::AttributeInSet { statement },
230 AtomicProof::AttributeInSet { proof },
231 ) => {
232 let maybe_com = cmm_attributes.get(&statement.attribute_tag);
233 if let Some(com) = maybe_com {
234 let attribute_vec: Vec<_> =
235 statement.set.iter().map(|x| x.to_field_element()).collect();
236 if verify_set_membership(
237 version,
238 transcript,
239 &attribute_vec,
240 com,
241 proof,
242 global.bulletproof_generators(),
243 &global.on_chain_commitment_key,
244 )
245 .is_err()
246 {
247 return false;
248 }
249 } else {
250 return false;
251 }
252 }
253 (
254 AtomicStatement::AttributeNotInSet { statement },
255 AtomicProof::AttributeNotInSet { proof },
256 ) => {
257 let maybe_com = cmm_attributes.get(&statement.attribute_tag);
258 if let Some(com) = maybe_com {
259 let attribute_vec: Vec<_> =
260 statement.set.iter().map(|x| x.to_field_element()).collect();
261 if verify_set_non_membership(
262 version,
263 transcript,
264 &attribute_vec,
265 com,
266 proof,
267 global.bulletproof_generators(),
268 &global.on_chain_commitment_key,
269 )
270 .is_err()
271 {
272 return false;
273 }
274 } else {
275 return false;
276 }
277 }
278 _ => {
279 return false;
280 }
281 }
282 true
283 }
284}
285
286impl<C: Curve, AttributeType: Attribute<C::Scalar>> Statement<C, AttributeType> {
287 pub fn verify(
298 &self,
299 version: ProofVersion,
300 challenge: &[u8],
301 global: &GlobalContext<C>,
302 credential: &CredId<C>,
303 commitments: &CredentialDeploymentCommitments<C>,
304 proofs: &Proof<C, AttributeType>,
305 ) -> bool {
306 let mut transcript = RandomOracle::domain("Concordium ID2.0 proof");
307 transcript.append_message(b"ctx", &global);
308 transcript.add_bytes(challenge);
309 transcript.append_message(b"credential", credential);
310 if self.statements.len() != proofs.proofs.len() {
311 return false;
312 }
313 for (statement, proof) in self.statements.iter().zip(proofs.proofs.iter()) {
314 if !statement.verify(
315 version,
316 global,
317 &mut transcript,
318 &commitments.cmm_attributes,
319 proof,
320 ) {
321 return false;
322 }
323 }
324 true
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::{
332 common::types::{KeyIndex, KeyPair},
333 curve_arithmetic::arkworks_instances::ArkGroup,
334 id::{constants::AttributeKind, id_prover::*},
335 };
336 use ark_bls12_381::G1Projective;
337 use rand::*;
338 use std::{
339 collections::{btree_map::BTreeMap, BTreeSet},
340 convert::TryFrom,
341 marker::PhantomData,
342 };
343
344 type G1 = ArkGroup<G1Projective>;
345
346 #[test]
347 fn test_verify_account_ownership() {
348 let mut csprng = thread_rng();
349
350 let cred_data = CredentialData {
351 keys: {
352 let mut keys = BTreeMap::new();
353 keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
354 keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
355 keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
356 keys
357 },
358 threshold: SignatureThreshold::TWO,
359 };
360
361 let pub_data = cred_data.get_cred_key_info();
362
363 let reg_id: G1 =
364 Curve::hash_to_group(b"some_bytes").expect("Hashing to curve expected to succeed");
365 let account_address = account_address_from_registration_id(®_id);
366 let challenge = b"13549686546546546854651357687354";
367
368 let proof = prove_ownership_of_account(&cred_data, account_address, challenge);
369
370 assert!(verify_account_ownership(
371 &pub_data,
372 account_address,
373 challenge,
374 &proof
375 ));
376 }
377
378 #[test]
379 fn test_verify_attribute() {
380 let mut csprng = thread_rng();
381 let keys = PedersenKey::<G1>::generate(&mut csprng);
382 let attribute = AttributeKind("some attribute value".to_string());
383 let value = Value::<G1>::new(attribute.to_field_element());
384 let (commitment, randomness) = keys.commit(&value, &mut csprng);
385
386 assert!(
387 verify_attribute(&keys, &attribute, &randomness, &commitment),
388 "Incorrect opening of attribute."
389 );
390 }
391
392 #[test]
393 fn test_verify_attribute_in_range() {
394 let mut csprng = thread_rng();
395 let global = GlobalContext::<G1>::generate(String::from("genesis_string"));
396 let keys = global.on_chain_commitment_key;
397 let gens = global.bulletproof_generators();
398 let lower = AttributeKind("20000102".to_string());
399 let attribute = AttributeKind("20000102".to_string());
400 let upper = AttributeKind("20000103".to_string());
401 let value = Value::<G1>::new(attribute.to_field_element());
402 let (commitment, randomness) = keys.commit(&value, &mut csprng);
403 let mut transcript = RandomOracle::domain("Test");
404 let maybe_proof = prove_attribute_in_range(
405 ProofVersion::Version1,
406 &mut transcript.split(),
407 &mut thread_rng(),
408 gens,
409 &keys,
410 &attribute,
411 &lower,
412 &upper,
413 &randomness,
414 );
415 if let Some(proof) = maybe_proof {
416 assert_eq!(
417 verify_attribute_range(
418 ProofVersion::Version1,
419 &mut transcript,
420 &keys,
421 gens,
422 &lower,
423 &upper,
424 &commitment,
425 &proof
426 ),
427 Ok(()),
428 "Incorrect version 1 range proof."
429 );
430 } else {
431 panic!("Failed to produce version 1 proof.");
432 };
433 let mut transcript = RandomOracle::domain("Test");
434 let maybe_proof = prove_attribute_in_range(
435 ProofVersion::Version2,
436 &mut transcript.split(),
437 &mut thread_rng(),
438 gens,
439 &keys,
440 &attribute,
441 &lower,
442 &upper,
443 &randomness,
444 );
445 if let Some(proof) = maybe_proof {
446 assert_eq!(
447 verify_attribute_range(
448 ProofVersion::Version2,
449 &mut transcript,
450 &keys,
451 gens,
452 &lower,
453 &upper,
454 &commitment,
455 &proof
456 ),
457 Ok(()),
458 "Incorrect version 2 range proof."
459 );
460 } else {
461 panic!("Failed to produce version 2 proof.");
462 };
463 }
464
465 #[test]
466 fn test_verify_attribute_in_range_shift_cheat() {
467 let mut csprng = thread_rng();
468 let global = GlobalContext::<G1>::generate(String::from("genesis_string"));
469 let keys = global.on_chain_commitment_key;
470 let gens = global.bulletproof_generators();
471 let lower = AttributeKind("20000102".to_string());
472 let attribute = AttributeKind("20000102".to_string());
473 let upper = AttributeKind("20000103".to_string());
474 let value = Value::<G1>::new(attribute.to_field_element());
475 let (commitment, randomness) = keys.commit(&value, &mut csprng);
476 let mut transcript = RandomOracle::domain("Test");
477 let maybe_proof = prove_attribute_in_range(
478 ProofVersion::Version2,
479 &mut transcript.split(),
480 &mut thread_rng(),
481 gens,
482 &keys,
483 &attribute,
484 &lower,
485 &upper,
486 &randomness,
487 );
488 let lower_shifted = AttributeKind("20000107".to_string());
489 let upper_shifted = AttributeKind("20000108".to_string());
490 let five = G1::scalar_from_u64(5);
491 let five_value: Value<G1> = Value::new(five);
492 let five_com = keys.hide(&five_value, &PedersenRandomness::zero());
493 let commitment_shifted = Commitment(commitment.0.plus_point(&five_com));
494 if let Some(proof) = maybe_proof {
495 assert_eq!(
496 verify_attribute_range(
497 ProofVersion::Version2,
498 &mut transcript,
499 &keys,
500 gens,
501 &lower_shifted,
502 &upper_shifted,
503 &commitment_shifted,
504 &proof
505 )
506 .is_ok(),
507 false,
508 "Shifting statement and commitment using same proof should fail."
509 );
510 } else {
511 panic!("Failed to produce proof.");
512 };
513 }
514
515 struct TestRandomness {
517 randomness: BTreeMap<AttributeTag, PedersenRandomness<G1>>,
518 }
519
520 impl HasAttributeRandomness<G1> for TestRandomness {
521 type ErrorType = ImpossibleError;
522
523 fn get_attribute_commitment_randomness(
524 &self,
525 attribute_tag: &AttributeTag,
526 ) -> Result<PedersenRandomness<G1>, Self::ErrorType> {
527 match self.randomness.get(attribute_tag) {
528 Some(r) => Ok(r.clone()),
529 _ => {
530 let mut csprng = rand::thread_rng();
531 Ok(PedersenRandomness::generate(&mut csprng))
532 }
533 }
534 }
535 }
536
537 #[test]
538 fn test_verify_id_attributes_proofs() {
539 let point: G1 =
540 Curve::hash_to_group(b"some_bytes").expect("Hashing to curve expected to succeed");
541 let cmm_prf = Commitment(point);
542 let cmm_max_accounts = Commitment(point);
543 let cmm_cred_counter = Commitment(point);
544 let attribute_name = AttributeKind(String::from("Foo")); let attribute_country = AttributeKind(String::from("DK")); let attribute_dob = AttributeKind(String::from("19970505")); let attribute_doc_expiry = AttributeKind(String::from("20250505")); let reveal_statement = RevealAttributeStatement {
551 attribute_tag: AttributeTag::from(0u8),
552 };
553
554 let dk = AttributeKind(String::from("DK"));
556 let no = AttributeKind(String::from("NO"));
557 let se = AttributeKind(String::from("SE"));
558 let de = AttributeKind(String::from("DE"));
559 let uk = AttributeKind(String::from("UK"));
560 let set = BTreeSet::from([dk, no, se, de.clone(), uk.clone()]);
561 let set2 = BTreeSet::from([de, uk]);
562 let set_statement = AttributeInSetStatement::<G1, _, AttributeKind> {
563 attribute_tag: AttributeTag::from(4u8),
564 _phantom: PhantomData::default(),
565 set: set.clone(),
566 };
567
568 let range_statement = AttributeInRangeStatement {
570 attribute_tag: AttributeTag::from(3u8),
571 lower: AttributeKind(String::from("19950505")),
572 upper: AttributeKind(String::from("19990505")),
573 _phantom: PhantomData::default(),
574 };
575
576 let full_statement = StatementWithContext {
578 credential: point,
579 statement: Statement {
580 statements: vec![
581 AtomicStatement::RevealAttribute {
582 statement: reveal_statement,
583 },
584 AtomicStatement::AttributeInSet {
585 statement: set_statement,
586 },
587 AtomicStatement::AttributeInRange {
588 statement: range_statement,
589 },
590 ],
591 },
592 };
593
594 let statement2 = Statement::new()
596 .older_than(18)
597 .unwrap()
598 .younger_than(35)
599 .unwrap()
600 .residence_in(set)
601 .unwrap()
602 .residence_not_in(set2)
603 .unwrap()
604 .doc_expiry_no_earlier_than(AttributeKind(String::from("20240304")))
605 .unwrap();
606 let full_statement2 = StatementWithContext {
607 credential: point,
608 statement: statement2,
609 };
610
611 let mut csprng = rand::thread_rng();
613 let global = GlobalContext::generate(String::from("Some genesis string"));
614 let keys = global.on_chain_commitment_key;
615 let (name_com, name_randomness) = keys.commit(
618 &Value::<G1>::new(attribute_name.to_field_element()),
619 &mut csprng,
620 );
621 let (country_com, country_randomness) = keys.commit(
622 &Value::<G1>::new(attribute_country.to_field_element()),
623 &mut csprng,
624 );
625 let (dob_com, dob_randomness) = keys.commit(
626 &Value::<G1>::new(attribute_dob.to_field_element()),
627 &mut csprng,
628 );
629 let (expiry_com, expiry_randomness) = keys.commit(
630 &Value::<G1>::new(attribute_doc_expiry.to_field_element()),
631 &mut csprng,
632 );
633
634 let mut alist = BTreeMap::new();
636 alist.insert(AttributeTag::from(0u8), attribute_name);
637 alist.insert(AttributeTag::from(3u8), attribute_dob);
638 alist.insert(AttributeTag::from(4u8), attribute_country);
639 alist.insert(AttributeTag::from(10u8), attribute_doc_expiry);
640
641 let valid_to = YearMonth::try_from(2022 << 8 | 5).unwrap(); let created_at = YearMonth::try_from(2020 << 8 | 5).unwrap(); let attribute_list: AttributeList<_, AttributeKind> = AttributeList {
644 valid_to,
645 created_at,
646 max_accounts: 237,
647 alist,
648 _phantom: Default::default(),
649 };
650
651 let mut randomness = BTreeMap::new();
653 randomness.insert(AttributeTag::from(0u8), name_randomness);
654 randomness.insert(AttributeTag::from(3u8), dob_randomness);
655 randomness.insert(AttributeTag::from(4u8), country_randomness);
656 randomness.insert(AttributeTag::from(10u8), expiry_randomness);
657
658 let attribute_randomness = TestRandomness { randomness };
659
660 let challenge = [0u8; 32]; let proof = full_statement.prove(
663 ProofVersion::Version1,
664 &global,
665 &challenge,
666 &attribute_list,
667 &attribute_randomness,
668 );
669 assert!(proof.is_some());
670 let proof = proof.unwrap();
671
672 let challenge2 = [1u8; 32]; let proof2 = full_statement2.prove(
675 ProofVersion::Version1,
676 &global,
677 &challenge2,
678 &attribute_list,
679 &attribute_randomness,
680 );
681 assert!(proof2.is_some());
682 let proof2 = proof2.unwrap();
683
684 let mut alist = BTreeMap::new();
686 alist.insert(AttributeTag::from(0u8), name_com); alist.insert(AttributeTag::from(3u8), dob_com); alist.insert(AttributeTag::from(4u8), country_com); alist.insert(AttributeTag::from(10u8), expiry_com); let coms = CredentialDeploymentCommitments {
692 cmm_prf,
693 cmm_max_accounts,
694 cmm_id_cred_sec_sharing_coeff: vec![],
695 cmm_cred_counter,
696 cmm_attributes: alist, };
698
699 let result =
702 full_statement.verify(ProofVersion::Version1, &challenge, &global, &coms, &proof);
703 assert!(result, "Version 1 statement should verify.");
704 let result2 =
705 full_statement2.verify(ProofVersion::Version1, &challenge2, &global, &coms, &proof2);
706 assert!(result2, "Version 1 statement 2 should verify.");
707
708 let proof = full_statement.prove(
710 ProofVersion::Version2,
711 &global,
712 &challenge,
713 &attribute_list,
714 &attribute_randomness,
715 );
716 assert!(proof.is_some());
717 let proof = proof.unwrap();
718
719 let proof2 = full_statement2.prove(
721 ProofVersion::Version2,
722 &global,
723 &challenge2,
724 &attribute_list,
725 &attribute_randomness,
726 );
727 assert!(proof2.is_some());
728 let proof2 = proof2.unwrap();
729
730 let result =
731 full_statement.verify(ProofVersion::Version2, &challenge, &global, &coms, &proof);
732 assert!(result, "Version 2 statement should verify.");
733 let result2 =
734 full_statement2.verify(ProofVersion::Version2, &challenge2, &global, &coms, &proof2);
735 assert!(result2, "Version 2 statement 2 should verify.");
736 }
737}