#[doc(hidden)]
#[macro_export]
macro_rules! __compute_formula_constraint {
    
    (($public_vars:ident, $secret_vars:ident) ($($x:tt)*)) => {
        
        __compute_formula_constraint!(($public_vars,$secret_vars) $($x)* +)
    };
    
    
    (($public_vars:ident, $secret_vars:ident)
     $( $scalar:ident * $point:ident +)+ ) => {
        vec![ $( ($secret_vars.$scalar , $public_vars.$point), )* ]
    };
}
#[macro_export]
macro_rules! define_proof {
    (
        $proof_module_name:ident 
        ,
        $proof_label_string:expr 
        ,
        ( $($secret_var:ident),+ ) 
        ,
        ( $($instance_var:ident),* ) 
        ,
        ( $($common_var:ident),* ) 
        :
        
        
        $($lhs:ident = $statement:tt),+
    ) => {
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        #[allow(non_snake_case)]
        pub mod $proof_module_name {
            use $crate::curve25519_dalek::scalar::Scalar;
            use $crate::curve25519_dalek::ristretto::RistrettoPoint;
            use $crate::curve25519_dalek::ristretto::CompressedRistretto;
            use $crate::toolbox::prover::Prover;
            use $crate::toolbox::verifier::Verifier;
            pub use $crate::merlin::Transcript;
            pub use $crate::{CompactProof, BatchableProof, ProofError};
            
            
            
            pub mod internal {
                use $crate::toolbox::SchnorrCS;
                
                pub const PROOF_LABEL: &'static str = $proof_label_string;
                
                pub struct TranscriptLabels {
                    $( pub $secret_var: &'static str, )+
                    $( pub $instance_var: &'static str, )+
                    $( pub $common_var: &'static str, )+
                }
                
                pub const TRANSCRIPT_LABELS: TranscriptLabels = TranscriptLabels {
                    $( $secret_var: stringify!($secret_var), )+
                    $( $instance_var: stringify!($instance_var), )+
                    $( $common_var: stringify!($common_var), )+
                };
                
                #[derive(Copy, Clone)]
                pub struct SecretVars<CS: SchnorrCS> {
                    $( pub $secret_var: CS::ScalarVar, )+
                }
                
                #[derive(Copy, Clone)]
                pub struct PublicVars<CS: SchnorrCS> {
                    $( pub $instance_var: CS::PointVar, )+
                    $( pub $common_var: CS::PointVar, )+
                }
                
                
                
                
                
                
                pub fn proof_statement<CS: SchnorrCS>(
                    cs: &mut CS,
                    secrets: SecretVars<CS>,
                    publics: PublicVars<CS>,
                ) {
                    $(
                        cs.constrain(
                            publics.$lhs,
                            __compute_formula_constraint!( (publics, secrets) $statement ),
                        );
                    )+
                }
            }
            
            #[derive(Copy, Clone)]
            pub struct ProveAssignments<'a> {
                $(pub $secret_var: &'a Scalar,)+
                $(pub $instance_var: &'a RistrettoPoint,)+
                $(pub $common_var: &'a RistrettoPoint,)+
            }
            
            #[derive(Copy, Clone)]
            pub struct VerifyAssignments<'a> {
                $(pub $instance_var: &'a CompressedRistretto,)+
                $(pub $common_var: &'a CompressedRistretto,)+
            }
            
            
            
            
            
            #[derive(Copy, Clone)]
            pub struct CompressedPoints {
                $(pub $instance_var: CompressedRistretto,)+
                $(pub $common_var: CompressedRistretto,)+
            }
            
            #[derive(Clone)]
            pub struct BatchVerifyAssignments {
                $(pub $instance_var: Vec<CompressedRistretto>,)+
                $(pub $common_var: CompressedRistretto,)+
            }
            fn build_prover<'a>(
                transcript: &'a mut Transcript,
                assignments: ProveAssignments,
            ) -> (Prover<'a>, CompressedPoints) {
                use self::internal::*;
                use $crate::toolbox::prover::*;
                let mut prover = Prover::new(PROOF_LABEL.as_bytes(), transcript);
                let secret_vars = SecretVars {
                    $(
                        $secret_var: prover.allocate_scalar(
                            TRANSCRIPT_LABELS.$secret_var.as_bytes(),
                            *assignments.$secret_var,
                        ),
                    )+
                };
                struct VarPointPairs {
                    $( pub $instance_var: (PointVar, CompressedRistretto), )+
                    $( pub $common_var: (PointVar, CompressedRistretto), )+
                }
                let pairs = VarPointPairs {
                    $(
                        $instance_var: prover.allocate_point(
                            TRANSCRIPT_LABELS.$instance_var.as_bytes(),
                            *assignments.$instance_var,
                        ),
                    )+
                    $(
                        $common_var: prover.allocate_point(
                            TRANSCRIPT_LABELS.$common_var.as_bytes(),
                            *assignments.$common_var,
                        ),
                    )+
                };
                
                let public_vars = PublicVars {
                    $($instance_var: pairs.$instance_var.0,)+
                    $($common_var: pairs.$common_var.0,)+
                };
                let compressed = CompressedPoints {
                    $($instance_var: pairs.$instance_var.1,)+
                    $($common_var: pairs.$common_var.1,)+
                };
                proof_statement(&mut prover, secret_vars, public_vars);
                (prover, compressed)
            }
            
            pub fn prove_compact(
                transcript: &mut Transcript,
                assignments: ProveAssignments,
            ) -> (CompactProof, CompressedPoints) {
                let (prover, compressed) = build_prover(transcript, assignments);
                (prover.prove_compact(), compressed)
            }
            
            pub fn prove_batchable(
                transcript: &mut Transcript,
                assignments: ProveAssignments,
            ) -> (BatchableProof, CompressedPoints) {
                let (prover, compressed) = build_prover(transcript, assignments);
                (prover.prove_batchable(), compressed)
            }
            fn build_verifier<'a>(
                transcript: &'a mut Transcript,
                assignments: VerifyAssignments,
            ) -> Result<Verifier<'a>, ProofError> {
                use self::internal::*;
                use $crate::toolbox::verifier::*;
                let mut verifier = Verifier::new(PROOF_LABEL.as_bytes(), transcript);
                let secret_vars = SecretVars {
                    $($secret_var: verifier.allocate_scalar(TRANSCRIPT_LABELS.$secret_var.as_bytes()),)+
                };
                let public_vars = PublicVars {
                    $(
                        $instance_var: verifier.allocate_point(
                            TRANSCRIPT_LABELS.$instance_var.as_bytes(),
                            *assignments.$instance_var,
                        )?,
                    )+
                    $(
                        $common_var: verifier.allocate_point(
                            TRANSCRIPT_LABELS.$common_var.as_bytes(),
                            *assignments.$common_var,
                        )?,
                    )+
                };
                proof_statement(&mut verifier, secret_vars, public_vars);
                Ok(verifier)
            }
            
            pub fn verify_compact(
                proof: &CompactProof,
                transcript: &mut Transcript,
                assignments: VerifyAssignments,
            ) -> Result<(), ProofError> {
                let verifier = build_verifier(transcript, assignments)?;
                verifier.verify_compact(proof)
            }
            
            pub fn verify_batchable(
                proof: &BatchableProof,
                transcript: &mut Transcript,
                assignments: VerifyAssignments,
            ) -> Result<(), ProofError> {
                let verifier = build_verifier(transcript, assignments)?;
                verifier.verify_batchable(proof)
            }
            
            pub fn batch_verify(
                proofs: &[BatchableProof],
                transcripts: Vec<&mut Transcript>,
                assignments: BatchVerifyAssignments,
            ) -> Result<(), ProofError> {
                use self::internal::*;
                use $crate::toolbox::batch_verifier::*;
                let batch_size = proofs.len();
                let mut verifier = BatchVerifier::new(PROOF_LABEL.as_bytes(), batch_size, transcripts)?;
                let secret_vars = SecretVars {
                    $($secret_var: verifier.allocate_scalar(TRANSCRIPT_LABELS.$secret_var.as_bytes()),)+
                };
                let public_vars = PublicVars {
                    $(
                        $instance_var: verifier.allocate_instance_point(
                            TRANSCRIPT_LABELS.$instance_var.as_bytes(),
                            assignments.$instance_var,
                        )?,
                    )+
                    $(
                        $common_var: verifier.allocate_static_point(
                            TRANSCRIPT_LABELS.$common_var.as_bytes(),
                            assignments.$common_var,
                        )?,
                    )+
                };
                proof_statement(&mut verifier, secret_vars, public_vars);
                verifier.verify_batchable(proofs)
            }
            #[cfg(all(feature = "bench", test))]
            mod bench {
                use super::*;
                use $crate::rand::thread_rng;
                extern crate test;
                use self::test::Bencher;
                #[bench]
                fn prove(b: &mut Bencher) {
                    let mut rng = thread_rng();
                    struct RandomAssignments {
                        $(pub $secret_var: Scalar,)+
                        $(pub $instance_var: RistrettoPoint,)+
                        $(pub $common_var: RistrettoPoint,)+
                    }
                    let assignments = RandomAssignments {
                        $($secret_var: Scalar::random(&mut rng),)+
                        $($instance_var: RistrettoPoint::random(&mut rng),)+
                        $($common_var: RistrettoPoint::random(&mut rng),)+
                    };
                    
                    
                    b.iter(|| {
                        let mut trans = Transcript::new(b"Benchmark");
                        prove_compact(&mut trans, ProveAssignments {
                            $($secret_var: &assignments.$secret_var,)+
                            $($instance_var: &assignments.$instance_var,)+
                            $($common_var: &assignments.$common_var,)+
                        })
                    });
                }
                #[bench]
                fn verify_compact_proof(b: &mut Bencher) {
                    let mut rng = thread_rng();
                    struct RandomAssignments {
                        $(pub $secret_var: Scalar,)+
                        $(pub $instance_var: RistrettoPoint,)+
                        $(pub $common_var: RistrettoPoint,)+
                    }
                    let assignments = RandomAssignments {
                        $($secret_var: Scalar::random(&mut rng),)+
                        $($instance_var: RistrettoPoint::random(&mut rng),)+
                        $($common_var: RistrettoPoint::random(&mut rng),)+
                    };
                    let mut trans = Transcript::new(b"Benchmark");
                    let (proof, points) = prove_compact(&mut trans, ProveAssignments {
                        $($secret_var: &assignments.$secret_var,)+
                        $($instance_var: &assignments.$instance_var,)+
                        $($common_var: &assignments.$common_var,)+
                    });
                    
                    
                    
                    
                    
                    b.iter(|| {
                        let mut trans = Transcript::new(b"Benchmark");
                        verify_compact(&proof, &mut trans, VerifyAssignments {
                            $($instance_var: &points.$instance_var,)+
                            $($common_var: &points.$common_var,)+
                        })
                    });
                }
                #[bench]
                fn verify_batchable_proof(b: &mut Bencher) {
                    let mut rng = thread_rng();
                    struct RandomAssignments {
                        $(pub $secret_var: Scalar,)+
                        $(pub $instance_var: RistrettoPoint,)+
                        $(pub $common_var: RistrettoPoint,)+
                    }
                    let assignments = RandomAssignments {
                        $($secret_var: Scalar::random(&mut rng),)+
                        $($instance_var: RistrettoPoint::random(&mut rng),)+
                        $($common_var: RistrettoPoint::random(&mut rng),)+
                    };
                    let mut trans = Transcript::new(b"Benchmark");
                    let (proof, points) = prove_batchable(&mut trans, ProveAssignments {
                        $($secret_var: &assignments.$secret_var,)+
                        $($instance_var: &assignments.$instance_var,)+
                        $($common_var: &assignments.$common_var,)+
                    });
                    
                    
                    
                    
                    b.iter(|| {
                        let mut trans = Transcript::new(b"Benchmark");
                        verify_batchable(&proof, &mut trans, VerifyAssignments {
                            $($instance_var: &points.$instance_var,)+
                            $($common_var: &points.$common_var,)+
                        })
                    });
                }
            }
        }
    }
}