concordium_base/id/
id_verifier.rs

1//! This module exposes functions for verifying various proofs of statements
2//! about a credential on accounts.
3
4use 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
25/// Function for opening an attribute inside a commitment. The arguments are
26/// - keys - the commitments keys used to commit to the attribute
27/// - attribute - the attribute claimed to be inside the commitment
28/// - r - the randomness used to commit
29/// - c - the commitment
30///
31/// The function outputs a bool, indicicating whether the commitment contains
32/// the given attribute.
33pub 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/// Function for verifying a range proof about an attribute inside a commitment.
44/// The arguments are
45/// - keys - the commitments keys used to commit to the attribute
46/// - gens - the bulletproof generators needed for range proofs
47/// - lower - the lower bound of the range
48/// - upper - the upper bound of the range
49/// - c - the commitment to the attribute
50/// - proof - the range proof about the attribute inside the commitment
51///
52/// The function outputs a bool, indicating whether the proof is correct or not,
53/// i.e., wether is attribute inside the commitment lies in [lower,upper).
54#[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
99/// Function for verifying account ownership. The arguments are
100/// - public_data - the public keys (and threshold) of the prover. These should
101///   be read from chain by looking up the account. If they are not present on
102///   chain, the verifier should reject the proof.
103/// - account - the account address of the account that the prover claims to
104///   own.
105/// - challenge - the challenge that the verifier gave to the prover.
106/// - proof - the prover's proof.
107///
108/// The function outputs a bool, indicating whether the proof is correct or not,
109/// i.e., it checks the signatures inside the proof.
110pub 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    /// Function for verifying a proof of a statement.
127    /// The arguments are
128    /// - `challenge` - slice to challenge bytes chosen by the verifier
129    /// - `global` - the on-chain cryptographic parameters
130    /// - `commitments` - the on-chain commitments of the relevant credential
131    ///
132    /// The function returns `true` if the statement is true.
133    /// If the statement is false, the function returns false with overwhelming
134    /// probability.
135    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                    // There is a commitment to the relevant attribute. We can then check the
178                    // proof.
179                    let x = attribute.to_field_element();
180                    transcript.add_bytes(b"RevealAttributeDlogProof");
181                    // x is known to the verifier and should go into the transcript
182                    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,                                  // C g^-x = h^r
193                        coeff: global.on_chain_commitment_key.h, // h
194                    };
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                    // There is a commitment to the relevant attribute. We can then check the
209                    // proof.
210                    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    /// Function for verifying a proof of a statement.
288    /// The arguments are
289    /// - `challenge` - slice to challenge bytes chosen by the verifier
290    /// - `global` - the on-chain cryptographic parameters
291    /// - `credential` - the credential for which this statement applies
292    /// - `commitments` - the on-chain commitments of the relevant credential
293    ///
294    /// The function returns `true` if the statement is true.
295    /// If the statement is false, the function returns false with overwhelming
296    /// probability.
297    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(&reg_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    // For testing purposes
516    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")); // first name
545        let attribute_country = AttributeKind(String::from("DK")); // country
546        let attribute_dob = AttributeKind(String::from("19970505")); // dob
547        let attribute_doc_expiry = AttributeKind(String::from("20250505")); // doc expiry
548
549        // Reveal first name
550        let reveal_statement = RevealAttributeStatement {
551            attribute_tag: AttributeTag::from(0u8),
552        };
553
554        // Country (not) in set
555        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        // DOB in range
569        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        // full statement
577        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        // Some other statements constructed using helper functions
595        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        // Commitments and secret randomness
612        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        // the commitments the user will prove stuff about. The commitments are
616        // on-chain, the randomness is only known to the user.
617        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        // the attribute list
635        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(); // May 2022
642        let created_at = YearMonth::try_from(2020 << 8 | 5).unwrap(); // May 2020
643        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        // the commitment randomness in a map so it can be looked up when relevant.
652        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        // Construct proof of statement from secret
661        let challenge = [0u8; 32]; // verifiers challenge
662        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        // Prove the second statement
673        let challenge2 = [1u8; 32]; // verifiers challenge
674        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        // On chain there is a credential with the commitments
685        let mut alist = BTreeMap::new();
686        alist.insert(AttributeTag::from(0u8), name_com); // first name
687        alist.insert(AttributeTag::from(3u8), dob_com); // dob
688        alist.insert(AttributeTag::from(4u8), country_com); // country
689        alist.insert(AttributeTag::from(10u8), expiry_com); // id doc expiry
690
691        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, // The relevant values
697        };
698
699        // the verifier uses these commitments to verify the proofs
700
701        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        // Version 2 proofs
709        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        // Prove the second statement
720        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}