ソースを参照

feat: integrate sigma-rs protocol implementation

This commit adds complete sigma-rs integration to the compiler:

- Add sigma-rs dependency and configuration
- Create codegen_sigmars.rs module with sigma-rs specific implementation
- Extract sigma-rs code generation logic into separate, organized module
- Add comprehensive protocol tests for various proof scenarios
- Update main implementation to use sigma-rs backend
- Add .gitignore for Rust projects
- Preserve original codegen.rs for comparison

The new architecture clearly separates the original codegen approach
from the sigma-rs implementation, making it easy to compare and
understand the differences between the two approaches.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Michele Orrù 3 ヶ月 前
コミット
02cc687d6c

+ 10 - 0
.gitignore

@@ -0,0 +1,10 @@
+# Rust build artifacts
+/target/
+Cargo.lock
+
+# IDE files
+.vscode/
+
+# OS files
+.DS_Store
+Thumbs.db

+ 7 - 1
Cargo.toml

@@ -6,6 +6,12 @@ edition = "2021"
 [dependencies]
 sigma_compiler_derive = { path = "sigma_compiler_derive" }
 group = "0.13"
+sigma_compiler_core = { path = "sigma_compiler_core" }
+sigma-rs = { git = "https://github.com/mmaker/sigma-rs" }
+syn = { version = "2.0", features = ["extra-traits", "visit-mut", "full"] }
+quote = "1.0"
+proc-macro2 = "1.0"
+rand = "0.8.5"
 
 [dev-dependencies]
-curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }
+curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }

+ 1 - 0
sigma_compiler_core/Cargo.toml

@@ -7,6 +7,7 @@ edition = "2021"
 syn = { version = "2.0", features = ["extra-traits", "visit", "visit-mut", "full"] }
 quote = "1.0"
 proc-macro2 = "1.0"
+sigma-rs = { git = "https://github.com/mmaker/sigma-rs" }
 
 [features]
 # Dump (to stdout) the public params on both the prover's and verifier's

+ 491 - 0
sigma_compiler_core/src/codegen_sigmars.rs

@@ -0,0 +1,491 @@
+//! Sigma-RS specific code generation module.
+//! 
+//! This module contains the sigma-rs implementation that was in the feature/sigma-rs branch.
+
+use super::syntax::*;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use std::collections::HashMap;
+use syn::visit_mut::{self, VisitMut};
+use syn::{parse_quote, Expr, Ident, Token};
+use syn::{ExprBinary, BinOp, ExprPath};
+use syn::Result;
+
+// Names and types of fields that might end up in a generated struct
+enum StructField {
+    Scalar(Ident),
+    VecScalar(Ident),
+    Point(Ident),
+    VecPoint(Ident),
+}
+
+// A list of StructField items
+#[derive(Default)]
+struct StructFieldList {
+    fields: Vec<StructField>,
+}
+
+impl StructFieldList {
+    pub fn push_scalar(&mut self, s: &Ident) {
+        self.fields.push(StructField::Scalar(s.clone()));
+    }
+    pub fn push_vecscalar(&mut self, s: &Ident) {
+        self.fields.push(StructField::VecScalar(s.clone()));
+    }
+    pub fn push_point(&mut self, s: &Ident) {
+        self.fields.push(StructField::Point(s.clone()));
+    }
+    pub fn push_vecpoint(&mut self, s: &Ident) {
+        self.fields.push(StructField::VecPoint(s.clone()));
+    }
+    pub fn push_vars(&mut self, vardict: &TaggedVarDict, is_pub: bool) {
+        let mut entries: Vec<_> = vardict.iter().collect();
+        entries.sort_by_key(|(k, _)| (*k).clone());
+        for (_, ti) in entries {
+            match ti {
+                TaggedIdent::Scalar(st) => {
+                    if st.is_pub == is_pub {
+                        if st.is_vec {
+                            self.push_vecscalar(&st.id)
+                        } else {
+                            self.push_scalar(&st.id)
+                        }
+                    }
+                }
+                TaggedIdent::Point(pt) => {
+                    if is_pub {
+                        if pt.is_vec {
+                            self.push_vecpoint(&pt.id)
+                        } else {
+                            self.push_point(&pt.id)
+                        }
+                    }
+                }
+            }
+        }
+    }
+    /// Output a ToTokens of the fields as they would appear in a struct
+    /// definition
+    pub fn field_decls(&self) -> impl ToTokens {
+        let decls = self.fields.iter().map(|f| match f {
+            StructField::Scalar(id) => quote! {
+                pub #id: Scalar,
+            },
+            StructField::VecScalar(id) => quote! {
+                pub #id: Vec<Scalar>,
+            },
+            StructField::Point(id) => quote! {
+                pub #id: Point,
+            },
+            StructField::VecPoint(id) => quote! {
+                pub #id: Vec<Point>,
+            },
+        });
+        quote! { #(#decls)* }
+    }
+}
+
+/// An implementation of the
+/// [`VisitMut`](https://docs.rs/syn/latest/syn/visit_mut/trait.VisitMut.html)
+/// trait that massages the provided statements.
+///
+/// This massaging currently consists of:
+///   - Changing equality from = to ==
+///   - Changing any identifier `id` to either `params.id` or
+///     `witness.id` depending on whether it is public or private
+struct StatementFixup {
+    idmap: HashMap<String, Expr>,
+}
+
+impl StatementFixup {
+    pub fn new(spec: &SigmaCompSpec) -> Self {
+        let mut idmap: HashMap<String, Expr> = HashMap::new();
+
+        // For each public identifier id (Points, or Scalars marked
+        // "pub"), add to the map "id" -> params.id.  For each private
+        // identifier (Scalars not marked "pub"), add to the map "id" ->
+        // witness.id.
+        for (id, is_pub) in spec.vars.values().map(|ti| match ti {
+            TaggedIdent::Scalar(st) => (&st.id, st.is_pub),
+            TaggedIdent::Point(pt) => (&pt.id, true),
+        }) {
+            let idexpr: Expr = if is_pub {
+                parse_quote! { params.#id }
+            } else {
+                parse_quote! { witness.#id }
+            };
+            idmap.insert(id.to_string(), idexpr);
+        }
+
+        Self { idmap }
+    }
+}
+
+impl VisitMut for StatementFixup {
+    fn visit_expr_mut(&mut self, node: &mut Expr) {
+        if let Expr::Assign(assn) = node {
+            *node = Expr::Binary(syn::ExprBinary {
+                attrs: assn.attrs.clone(),
+                left: assn.left.clone(),
+                op: syn::BinOp::Eq(Token![==](assn.eq_token.span)),
+                right: assn.right.clone(),
+            });
+        }
+        if let Expr::Path(expath) = node {
+            if let Some(id) = expath.path.get_ident() {
+                if let Some(expr) = self.idmap.get(&id.to_string()) {
+                    *node = expr.clone();
+                    // Don't recurse
+                    return;
+                }
+            }
+        }
+        // Unless we bailed out above, continue with the default
+        // traversal
+        visit_mut::visit_expr_mut(self, node);
+    }
+}
+
+/// Tries to extract an identifier from an expression of type `Expr`
+fn expr_to_ident(expr: &Expr) -> Option<&Ident> {
+    if let Expr::Path(ExprPath { path, .. }) = expr {
+        if path.segments.len() == 1 {
+            return Some(&path.segments[0].ident);
+        }
+    }
+    None
+}
+
+/// Analyzes an expression `Expr` representing a sum (x * Q + y * H + ...)
+/// and returns a vector of couples `(base_point_ident, scalar_ident)`
+fn extract_terms(expr: &Expr) -> Result<Vec<(Ident, Ident)>> {
+    let mut terms = Vec::new();
+
+    match expr {
+        Expr::Binary(ExprBinary { op: BinOp::Add(_), left, right, .. }) => {
+            terms.extend(extract_terms(left)?);
+            terms.extend(extract_terms(right)?);
+        }
+        Expr::Binary(ExprBinary { op: BinOp::Mul(_), left, right, .. }) => {
+            let (scalar, point) = match (expr_to_ident(left), expr_to_ident(right)) {
+                (Some(a), Some(b)) => {
+                    (a.clone(), b.clone())
+                }
+                _ => {
+                    return Err(syn::Error::new_spanned(expr, "Expected product of two identifiers like `x * Q`"));
+                }
+            };
+            terms.push((point, scalar));
+        }
+        _ => {
+            return Err(syn::Error::new_spanned(expr, "Expected addition or multiplication expression"));
+        }
+    }
+
+    Ok(terms)
+}
+
+/// Extracts a linear combination from an expression of the form `lhs = x * Q + y * H + ...`
+pub fn extract_linear_combination(expr: &Expr) -> Result<(Ident, Vec<(Ident, Ident)>)> {
+    if let Expr::Assign(assign) = expr {
+        let lhs_ident = expr_to_ident(&assign.left)
+            .ok_or_else(|| syn::Error::new_spanned(&assign.left, "Expected left-hand side to be a single identifier"))?
+            .clone();
+
+        let terms = extract_terms(&assign.right)?;
+        Ok((lhs_ident, terms))
+    } else {
+        Err(syn::Error::new_spanned(expr, "Expected an assignment expression like `A = x * G + y * H`"))
+    }
+}
+
+/// Generate the sigma-rs implementation code (from feature/sigma-rs branch)
+pub fn sigma_compiler_core_sigmars(
+    spec: &mut SigmaCompSpec,
+    emit_prover: bool,
+    emit_verifier: bool,
+) -> TokenStream {
+    let proto_name = &spec.proto_name;
+    let group_name = &spec.group_name;
+
+    // Save a copy of the input TaggedVarDict, because the Params and
+    // Witness structs will be built from that TaggedVarDict, not the
+    // one modified by apply_substitutions below.
+    let input_vars = spec.vars.clone();
+
+    // Apply any substitution transformations
+    // Note: We create a dummy CodeGen since sigma-rs implementation doesn't use it
+    let mut dummy_codegen = crate::codegen::CodeGen::new(spec);
+    crate::transform::apply_substitutions(&mut dummy_codegen, &mut spec.statements, &mut spec.vars).unwrap();
+
+    let group_types = quote! {
+        pub type Scalar = <super::#group_name as super::Group>::Scalar;
+        pub type Point = super::#group_name;
+    };
+
+    // Generate the public params struct definition
+    let params_def = {
+        let mut pub_params_fields = StructFieldList::default();
+        pub_params_fields.push_vars(&input_vars, true);
+
+        let decls = pub_params_fields.field_decls();
+
+        let new_args = pub_params_fields.fields.iter().map(|f| match f {
+            StructField::Scalar(id) => quote! { #id: Scalar },
+            StructField::VecScalar(id) => quote! { #id: Vec<Scalar> },
+            StructField::Point(id) => quote! { #id: Point },
+            StructField::VecPoint(id) => quote! { #id: Vec<Point> },
+        });
+
+        // Allowance of the secret scalars of LinearRelation (see sigma-rs::linear_relation)
+        let scalar_allocs = {
+            let mut sec_params_fields = StructFieldList::default();
+            sec_params_fields.push_vars(&input_vars, false);
+            sec_params_fields.fields.iter().map(|f|
+                match f {
+                    StructField::Scalar(id) => quote! {
+                        let scalar_var = protocol.sigmap.0.allocate_scalar();
+                        scalar_bindings.push((::syn::parse_str::<::syn::Ident>(stringify!(#id)).unwrap(), None, scalar_var));
+                    },
+                    _ => quote! {},
+                }
+            ).collect::<Vec<_>>()
+        };
+
+        // Allowance of (public) group elements of the LinearRelation (see sigma-rs::linear_relation)
+        let point_allocs = {
+            pub_params_fields.fields.iter().map(|f| {
+                if let StructField::Point(id) = f {
+                    quote! {
+                        let point_var = protocol.sigmap.0.allocate_element();
+                        protocol.sigmap.0.assign_elements([(point_var, #id.clone())]);
+                        point_bindings.push((::syn::parse_str::<::syn::Ident>(stringify!(#id)).unwrap(), None, point_var));
+                    }
+                } else {
+                    quote! {}
+                }
+            }).collect::<Vec<_>>()
+        };
+
+        // Allowance of LinearRelation constraints for the construction of induced sigma protocol
+        let statements_alloc = {
+            spec.statements.clone().leaves_mut().iter().map(|f| {
+                let (lhs, terms) = extract_linear_combination(f).unwrap();
+
+                let pairs = terms.iter().map(|(point, scalar)| {
+                    quote! {
+                        {
+                            let scalar_var = scalar_bindings
+                                .iter()
+                                .find(|(id, _, _)| id == &::syn::parse_str::<::syn::Ident>(stringify!(#scalar)).unwrap())
+                                .expect(&format!("Missing scalar binding for {}", stringify!(#scalar)))
+                                .2;
+
+                            let point_var = point_bindings
+                                .iter()
+                                .find(|(id, _, _)| id == &::syn::parse_str::<::syn::Ident>(stringify!(#point)).unwrap())
+                                .expect(&format!("Missing point binding for {}", stringify!(#point)))
+                                .2;
+
+                            (scalar_var, point_var)
+                        }
+                    }
+                });
+
+                quote! {
+                    {
+                        let lhs = point_bindings
+                            .iter()
+                            .find(|(id, _, _)| id == &::syn::parse_str::<::syn::Ident>(stringify!(#lhs)).unwrap())
+                            .expect(&format!("Missing point binding for {}", stringify!(#lhs)))
+                            .2;
+
+                        let rhs = vec![#(#pairs),*];
+                        protocol.sigmap.0.constrain(lhs, rhs)
+                    }
+                }
+            }).collect::<Vec<_>>()
+        };
+
+        let dump_impl = if cfg!(feature = "dump") {
+            let dump_chunks = pub_params_fields.fields.iter().map(|f| match f {
+                StructField::Scalar(id) => quote! {
+                    print!("  {}: ", stringify!(#id));
+                    Params::dump_scalar(&self.#id);
+                    println!("");
+                },
+                StructField::VecScalar(id) => quote! {
+                    print!("  {}: [", stringify!(#id));
+                    for s in self.#id.iter() {
+                        print!("    ");
+                        Params::dump_scalar(s);
+                        println!(",");
+                    }
+                    println!("  ]");
+                },
+                StructField::Point(id) => quote! {
+                    print!("  {}: ", stringify!(#id));
+                    Params::dump_point(&self.#id);
+                    println!("");
+                },
+                StructField::VecPoint(id) => quote! {
+                    print!("  {}: [", stringify!(#id));
+                    for p in self.#id.iter() {
+                        print!("    ");
+                        Params::dump_point(p);
+                        println!(",");
+                    }
+                    println!("  ]");
+                },
+            });
+            quote! {
+                impl Params {
+                    fn dump_scalar(s: &Scalar) {
+                        let bytes: &[u8] = &s.to_repr();
+                        print!("{:02x?}", bytes);
+                    }
+
+                    fn dump_point(p: &Point) {
+                        let bytes: &[u8] = &p.to_bytes();
+                        print!("{:02x?}", bytes);
+                    }
+
+                    pub fn dump(&self) {
+                        println!("params: {:?}", self);
+                        #(#dump_chunks)*
+                    }
+                }
+            }
+        } else {
+            quote! {
+                impl Protocol {
+                    pub fn new(#(#new_args),*) -> Self {
+                        let mut scalar_bindings: Vec<(::syn::Ident, Option<usize>, ScalarVar)> = Vec::new();
+                        let mut point_bindings: Vec<(::syn::Ident, Option<usize>, GroupVar)> = Vec::new();
+                        let mut protocol = NISigma::new(b"hello world", SchnorrProtocol(LinearRelation::new()));
+                        #(#scalar_allocs)*
+                        #(#point_allocs)*
+                        #(#statements_alloc)*
+
+                        Protocol { protocol, scalar_bindings, point_bindings }
+                    }
+                }
+            }
+        };
+        quote! {
+            pub struct Params {
+                #decls
+            }
+
+            pub struct Protocol {
+                pub protocol: NISigmaProtocol<SchnorrProtocol<super::#group_name>, ShakeCodec<super::#group_name>>,
+                pub scalar_bindings: Vec<(::syn::Ident, Option<usize>, ScalarVar)>,
+                pub point_bindings: Vec<(::syn::Ident, Option<usize>, GroupVar)>
+            }
+
+            #dump_impl
+        }
+    };
+
+    // Generate the witness struct definition
+    let witness_def = if emit_prover {
+        let mut _witness_fields = StructFieldList::default();
+        _witness_fields.push_vars(&input_vars, false);
+
+        // For the case where the struct witness supports the field system
+        // let decls = witness_fields.field_decls();
+        quote! {
+            pub struct Witness (pub Vec<<super::#group_name as super::Group>::Scalar>);
+        }
+    } else {
+        quote! {}
+    };
+
+    // Generate the prove function
+    let prove_func = if emit_prover {
+        let dumper = if cfg!(feature = "dump") {
+            quote! {
+                println!("prover params = {{");
+                params.dump();
+                println!("}}");
+            }
+        } else {
+            quote! {
+                let mut rng = OsRng;
+                let result = protocol.protocol.prove_batchable(&witness.0, &mut rng).is_ok();
+                match result {
+                    true => Ok(protocol.protocol.prove_batchable(&witness.0, &mut rng).unwrap()),
+                    false => Err(())
+                }
+            }
+        };
+
+        quote! {
+            pub fn prove(protocol: &Protocol, witness: &Witness) -> Result<Vec<u8>,()> {
+                #dumper
+            }
+        }
+    } else {
+        quote! {}
+    };
+
+    // Generate the verify function
+    let verify_func = if emit_verifier {
+        let dumper = if cfg!(feature = "dump") {
+            quote! {
+                println!("verifier params = {{");
+                params.dump();
+                println!("}}");
+            }
+        } else {
+            quote! {
+                let result = protocol.protocol.verify_batchable(proof).is_ok();
+                match result {
+                    true => Ok(()),
+                    false => Err(())
+                }
+            }
+        };
+        quote! {
+            pub fn verify(protocol: &Protocol, proof: &[u8]) -> Result<(),()> {
+                #dumper
+            }
+        }
+    } else {
+        quote! {}
+    };
+
+    // Output the generated module for this protocol
+    let dump_use = if cfg!(feature = "dump") {
+        quote! {
+            use ff::PrimeField;
+            use group::GroupEncoding;
+        }
+    } else {
+        quote! {
+            use sigma_rs::schnorr_protocol::SchnorrProtocol;
+            use sigma_rs::linear_relation::{LinearCombination, LinearRelation};
+            use sigma_rs::linear_relation::{GroupVar, ScalarVar};
+            use sigma_rs::fiat_shamir::NISigmaProtocol;
+            use sigma_rs::codec::ShakeCodec;
+            use syn::Ident;
+            use std::collections::HashMap;
+            use rand::rngs::OsRng;
+
+            type NISigma = NISigmaProtocol<SchnorrProtocol<super::#group_name>, ShakeCodec<super::#group_name>>;
+        }
+    };
+    quote! {
+        #[allow(non_snake_case)]
+        pub mod #proto_name {
+            #dump_use
+
+            #group_types
+            #params_def
+            #witness_def
+
+            #prove_func
+            #verify_func
+        }
+    }
+}

+ 3 - 6
sigma_compiler_core/src/lib.rs

@@ -8,6 +8,7 @@ pub mod sigma {
     pub mod types;
 }
 mod codegen;
+mod codegen_sigmars;
 mod syntax;
 mod transform;
 
@@ -30,10 +31,6 @@ pub fn sigma_compiler_core(
     emit_prover: bool,
     emit_verifier: bool,
 ) -> TokenStream {
-    let mut codegen = codegen::CodeGen::new(spec);
-
-    // Apply any substitution transformations
-    transform::apply_substitutions(&mut codegen, &mut spec.statements, &mut spec.vars).unwrap();
-
-    codegen.generate(spec, emit_prover, emit_verifier)
+    // Use the sigma-rs implementation
+    codegen_sigmars::sigma_compiler_core_sigmars(spec, emit_prover, emit_verifier)
 }

+ 1 - 0
src/lib.rs

@@ -1,2 +1,3 @@
+pub use sigma_compiler_core::*;
 pub use sigma_compiler_derive::sigma_compiler;
 pub use group::{self, Group};

+ 206 - 0
tests/macro_test.rs

@@ -0,0 +1,206 @@
+use curve25519_dalek::{RistrettoPoint, Scalar};
+use group::Group;
+use rand::rngs::OsRng;
+use sigma_compiler::sigma_compiler;
+
+type G = RistrettoPoint;
+
+#[allow(non_snake_case)]
+#[test]
+fn test_cli_proof1() {
+    sigma_compiler! {
+        client_proof < G >,
+        (rand s_iss_cred_UAC, iss_Jattr_UAC_nym_id, pub
+        _iss_Rattr_UAC_measurement_count, pub _iss_Rattr_UAC_age,),
+        (cind X_iss_Jattr_UAC_nym_id, C_iss_cred_UAC, cind const A_generator, cind
+        const B_generator,),
+        C_iss_cred_UAC = s_iss_cred_UAC * A_generator + iss_Jattr_UAC_nym_id * X_iss_Jattr_UAC_nym_id,
+    };
+
+    // Rng
+    let mut rng = OsRng;
+    // Witness
+    let s_iss_cred_UAC = Scalar::random(&mut rng);
+    let iss_Jattr_UAC_nym_id = Scalar::random(&mut rng);
+    // Parameters
+    let X_iss_Jattr_UAC_nym_id = G::random(&mut rng);
+    let A_generator = G::generator();
+    let C_iss_cred_UAC =
+        s_iss_cred_UAC * A_generator + iss_Jattr_UAC_nym_id * X_iss_Jattr_UAC_nym_id;
+    let B_generator = G::default();
+    let iss_Rattr_UAC_measurement_count = Scalar::default();
+    let iss_Rattr_UAC_age = Scalar::default();
+
+    // Protocol
+    let protocol = client_proof::Protocol::new(
+        A_generator,
+        B_generator,
+        C_iss_cred_UAC,
+        X_iss_Jattr_UAC_nym_id,
+        iss_Rattr_UAC_age,
+        iss_Rattr_UAC_measurement_count,
+    );
+
+    let witness = client_proof::Witness(vec![iss_Jattr_UAC_nym_id, s_iss_cred_UAC]);
+
+    let proof = client_proof::prove(&protocol, &witness);
+    assert!(proof.is_ok());
+    let verify_result = client_proof::verify(&protocol, &proof.unwrap());
+    assert!(verify_result.is_ok());
+}
+
+#[allow(non_snake_case)]
+#[test]
+fn test_iss_proof() {
+    sigma_compiler! {
+        issuer_proof < G > ,
+        (rand b_iss_cred_UAC, x0_iss_cred_UAC,),
+        (P_iss_cred_UAC, X0_iss_cred_UAC, K_iss_cred_UAC, R_iss_cred_UAC, cind
+        const A_generator, cind const B_generator,),
+        P_iss_cred_UAC = b_iss_cred_UAC * A_generator,
+        X0_iss_cred_UAC = x0_iss_cred_UAC * B_generator,
+        R_iss_cred_UAC = x0_iss_cred_UAC * P_iss_cred_UAC + b_iss_cred_UAC * K_iss_cred_UAC,
+    };
+
+    // Rng
+    let mut rng = OsRng;
+    // Witness
+    let b_iss_cred_UAC = Scalar::random(&mut rng);
+    let x0_iss_cred_UAC = Scalar::random(&mut rng);
+    // Parameters
+    let A_generator = G::generator();
+    let B_generator = G::default();
+    let K_iss_cred_UAC = G::random(&mut rng);
+    let P_iss_cred_UAC = b_iss_cred_UAC * A_generator;
+    let R_iss_cred_UAC = x0_iss_cred_UAC * P_iss_cred_UAC + b_iss_cred_UAC * K_iss_cred_UAC;
+    let X0_iss_cred_UAC = x0_iss_cred_UAC * B_generator;
+
+    // Protocol
+    let protocol = issuer_proof::Protocol::new(
+        A_generator,
+        B_generator,
+        K_iss_cred_UAC,
+        P_iss_cred_UAC,
+        R_iss_cred_UAC,
+        X0_iss_cred_UAC,
+    );
+
+    let witness = issuer_proof::Witness(vec![b_iss_cred_UAC, x0_iss_cred_UAC]);
+
+    let proof = issuer_proof::prove(&protocol, &witness);
+    assert!(proof.is_ok());
+    let verify_result = issuer_proof::verify(&protocol, &proof.unwrap());
+    assert!(verify_result.is_ok());
+}
+
+#[allow(non_snake_case)]
+#[test]
+fn test_cli_proof2() {
+    sigma_compiler! {
+        client_proof < G > ,
+        (rand s_iss_cred_New, rand zQ_show_cred_Old, rand z_show_Hattr_Old_nym_id,
+        rand z_show_Hattr_Old_age, rand z_show_Hattr_Old_measurement_count,
+        iss_Hattr_New_measurement_count, iss_Hattr_New_age, iss_Hattr_New_nym_id,
+        show_Hattr_Old_nym_id, show_Hattr_Old_age,
+        show_Hattr_Old_measurement_count,),
+        (cind X_iss_Hattr_New_measurement_count, cind X_iss_Hattr_New_age, cind
+        X_iss_Hattr_New_nym_id, cind P_show_cred_Old, cind
+        X_show_Hattr_Old_nym_id, cind X_show_Hattr_Old_age, cind
+        X_show_Hattr_Old_measurement_count, C_iss_cred_New, V_show_cred_Old,
+        C_show_Hattr_Old_nym_id, C_show_Hattr_Old_age,
+        C_show_Hattr_Old_measurement_count, cind const A_generator, cind const
+        B_generator,),
+        // show_Hattr_Old_nym_id == iss_Hattr_New_nym_id &&
+        // show_Hattr_Old_age == iss_Hattr_New_age &&
+        // show_Hattr_Old_measurement_count + Scalar :: from(1u64) == iss_Hattr_New_measurement_count,
+        C_iss_cred_New = s_iss_cred_New * A_generator + iss_Hattr_New_measurement_count *
+            X_iss_Hattr_New_measurement_count + iss_Hattr_New_age *
+            X_iss_Hattr_New_age + iss_Hattr_New_nym_id * X_iss_Hattr_New_nym_id,
+        C_show_Hattr_Old_nym_id = show_Hattr_Old_nym_id * P_show_cred_Old +
+            z_show_Hattr_Old_nym_id * A_generator,
+        C_show_Hattr_Old_age = show_Hattr_Old_age * P_show_cred_Old + z_show_Hattr_Old_age * A_generator,
+        C_show_Hattr_Old_measurement_count = show_Hattr_Old_measurement_count *
+            P_show_cred_Old + z_show_Hattr_Old_measurement_count * A_generator,
+        V_show_cred_Old = zQ_show_cred_Old * B_generator + z_show_Hattr_Old_nym_id
+            * X_show_Hattr_Old_nym_id + z_show_Hattr_Old_age * X_show_Hattr_Old_age +
+            z_show_Hattr_Old_measurement_count * X_show_Hattr_Old_measurement_count,
+    }
+
+    // Rng
+    let mut rng = OsRng;
+    // Witness
+    let iss_Hattr_New_age = Scalar::random(&mut rng);
+    let iss_Hattr_New_measurement_count = Scalar::random(&mut rng);
+    let iss_Hattr_New_nym_id = Scalar::random(&mut rng);
+    let show_Hattr_Old_age = Scalar::random(&mut rng);
+    let show_Hattr_Old_measurement_count = Scalar::random(&mut rng);
+    let show_Hattr_Old_nym_id = Scalar::random(&mut rng);
+    let s_iss_cred_New = Scalar::random(&mut rng);
+    let zQ_show_cred_Old = Scalar::random(&mut rng);
+    let z_show_Hattr_Old_age = Scalar::random(&mut rng);
+    let z_show_Hattr_Old_measurement_count = Scalar::random(&mut rng);
+    let z_show_Hattr_Old_nym_id = Scalar::random(&mut rng);
+
+    // Parameters
+    let A_generator = G::generator();
+    let B_generator = G::default();
+    let P_show_cred_Old = G::random(&mut rng);
+    let X_show_Hattr_Old_age = G::random(&mut rng);
+    let X_show_Hattr_Old_nym_id = G::random(&mut rng);
+    let X_show_Hattr_Old_measurement_count = G::random(&mut rng);
+    let X_iss_Hattr_New_age = G::random(&mut rng);
+    let X_iss_Hattr_New_nym_id = G::random(&mut rng);
+    let X_iss_Hattr_New_measurement_count = G::random(&mut rng);
+
+    let C_iss_cred_New = s_iss_cred_New * A_generator
+        + iss_Hattr_New_measurement_count * X_iss_Hattr_New_measurement_count
+        + iss_Hattr_New_age * X_iss_Hattr_New_age
+        + iss_Hattr_New_nym_id * X_iss_Hattr_New_nym_id;
+    let C_show_Hattr_Old_age =
+        show_Hattr_Old_age * P_show_cred_Old + z_show_Hattr_Old_age * A_generator;
+    let C_show_Hattr_Old_nym_id =
+        show_Hattr_Old_nym_id * P_show_cred_Old + z_show_Hattr_Old_nym_id * A_generator;
+    let C_show_Hattr_Old_measurement_count = show_Hattr_Old_measurement_count * P_show_cred_Old
+        + z_show_Hattr_Old_measurement_count * A_generator;
+    let V_show_cred_Old = zQ_show_cred_Old * B_generator
+        + z_show_Hattr_Old_nym_id * X_show_Hattr_Old_nym_id
+        + z_show_Hattr_Old_age * X_show_Hattr_Old_age
+        + z_show_Hattr_Old_measurement_count * X_show_Hattr_Old_measurement_count;
+
+    // Protocol
+    let protocol = client_proof::Protocol::new(
+        A_generator,
+        B_generator,
+        C_iss_cred_New,
+        C_show_Hattr_Old_age,
+        C_show_Hattr_Old_measurement_count,
+        C_show_Hattr_Old_nym_id,
+        P_show_cred_Old,
+        V_show_cred_Old,
+        X_iss_Hattr_New_age,
+        X_iss_Hattr_New_measurement_count,
+        X_iss_Hattr_New_nym_id,
+        X_show_Hattr_Old_age,
+        X_show_Hattr_Old_measurement_count,
+        X_show_Hattr_Old_nym_id,
+    );
+
+    let witness = client_proof::Witness(vec![
+        iss_Hattr_New_age,
+        iss_Hattr_New_measurement_count,
+        iss_Hattr_New_nym_id,
+        s_iss_cred_New,
+        show_Hattr_Old_age,
+        show_Hattr_Old_measurement_count,
+        show_Hattr_Old_nym_id,
+        zQ_show_cred_Old,
+        z_show_Hattr_Old_age,
+        z_show_Hattr_Old_measurement_count,
+        z_show_Hattr_Old_nym_id,
+    ]);
+
+    let proof = client_proof::prove(&protocol, &witness);
+    assert!(proof.is_ok());
+    let verify_result = client_proof::verify(&protocol, &proof.unwrap());
+    assert!(verify_result.is_ok());
+}