Просмотр исходного кода

fix: port the code generator to the sigma-proofs draft API

Michele Orrù 4 дней назад
Родитель
Сommit
998e45dee5

+ 9 - 7
sigma-compiler-core/src/codegen.rs

@@ -412,7 +412,7 @@ impl CodeGen {
                     #witness_var: &Witness,
                     #sid_var: &[u8],
                     #rng_var: &mut (impl CryptoRng + RngCore),
-                ) -> Result<Vec<u8>, SigmaError> {
+                ) -> Result<Vec<u8>, InvalidWitness> {
                     #dumper
                     let Instance { #instance_ids } = #instance_var.clone();
                     let Witness { #witness_ids } = #witness_var.clone();
@@ -468,7 +468,7 @@ impl CodeGen {
                         let #id: Point = {
                             let end = #offset_var + #element_len_var;
                             if #proof_var.len() < end {
-                                return Err(SigmaError::VerificationFailure);
+                                return Err(VerificationError);
                             }
                             let mut repr = <Point as group::GroupEncoding>::Repr::default();
                             repr.as_mut()
@@ -477,7 +477,7 @@ impl CodeGen {
                             Option::<Point>::from(
                                 <Point as group::GroupEncoding>::from_bytes(&repr)
                             )
-                            .ok_or(SigmaError::VerificationFailure)?
+                            .ok_or(VerificationError)?
                         };
                     },
                     StructField::VecPoint(id) => quote! {
@@ -487,7 +487,7 @@ impl CodeGen {
                             for _ in 0..expected_len {
                                 let end = #offset_var + #element_len_var;
                                 if #proof_var.len() < end {
-                                    return Err(SigmaError::VerificationFailure);
+                                    return Err(VerificationError);
                                 }
                                 let mut repr =
                                     <Point as group::GroupEncoding>::Repr::default();
@@ -497,7 +497,7 @@ impl CodeGen {
                                 let point = Option::<Point>::from(
                                     <Point as group::GroupEncoding>::from_bytes(&repr)
                                 )
-                                .ok_or(SigmaError::VerificationFailure)?;
+                                .ok_or(VerificationError)?;
                                 points.push(point);
                             }
                             #id = points;
@@ -538,7 +538,7 @@ impl CodeGen {
                     #instance_var: &Instance,
                     #proof_var: &[u8],
                     #sid_var: &[u8],
-                ) -> Result<(), SigmaError> {
+                ) -> Result<(), VerificationError> {
                     #dumper
                     let Instance { #instance_ids } = #instance_var.clone();
                     #verify_pre_instance_code
@@ -576,7 +576,9 @@ impl CodeGen {
                 use sigma_compiler::group::ff::{Field, PrimeField};
                 use sigma_compiler::rand::{CryptoRng, RngCore};
                 use sigma_compiler::sigma_proofs;
-                use sigma_compiler::sigma_proofs::errors::Error as SigmaError;
+                use sigma_compiler::sigma_proofs::errors::{
+                    InvalidInstance, InvalidWitness, VerificationError,
+                };
                 use sigma_compiler::subtle::ConditionallySelectable;
                 use sigma_compiler::vecutils::*;
                 use std::ops::Neg;

+ 1 - 1
sigma-compiler-core/src/notequals.rs

@@ -273,7 +273,7 @@ pub fn transform(
             let #Lx_var = #Lx_code;
             let #j_var = <Scalar as Field>::invert(&#Lx_var)
                 .into_option()
-                .ok_or(SigmaError::VerificationFailure)?;
+                .ok_or(InvalidWitness)?;
             let #s_var = -#rand_var * #j_var;
         });
 

+ 9 - 1
sigma-compiler-core/src/pubscalareq.rs

@@ -78,9 +78,17 @@ pub fn transform(
                                     // If we're in the root disjunction branch,
                                     // add code to both the prover and the
                                     // verifier to directly check the statement.
+                                    // The check is on public values only, so
+                                    // both parties reach the same verdict; the
+                                    // shared snippet names the instance error
+                                    // and `?` converts it to whichever of the
+                                    // two functions it landed in.
                                     codegen.prove_verify_append(quote! {
                                         if #id != #right_tokens {
-                                            return Err(SigmaError::VerificationFailure);
+                                            return Err(InvalidInstance::new(concat!(
+                                                "public scalar equality does not hold: ",
+                                                stringify!(#id),
+                                            )).into());
                                         }
                                     });
 

+ 3 - 1
sigma-compiler-core/src/rangeproof.rs

@@ -403,7 +403,9 @@ pub fn transform(
             if #bitrep_scalars_var.is_empty() {
                 // The upper bound was either less than 2, or more than
                 // i128::MAX
-                return Err(SigmaError::VerificationFailure);
+                return Err(InvalidInstance::new(
+                    "range upper bound has no bit representation",
+                ).into());
             }
             let #nbits_var = #bitrep_scalars_var.len();
         });

+ 34 - 38
sigma-compiler-core/src/sigma/codegen.rs

@@ -202,10 +202,10 @@ impl<'a> CodeGen<'a> {
     }
 
     /// Generate the code for the `protocol` and `protocol_witness`
-    /// functions that create the `ComposedRelation` and `ComposedWitness`
+    /// functions that create the `ComposedInstance` and `ComposedWitness`
     /// structs, respectively, given a slice of [`Expr`]s that will be
     /// bundled into a single `LinearRelation`.  The `protocol` code
-    /// must evaluate to a `Result<ComposedRelation>` and the `protocol_witness`
+    /// must evaluate to a `Result<ComposedInstance>` and the `protocol_witness`
     /// code must evaluate to a `Result<ComposedWitness>`.
     fn linear_relation_codegen(&self, exprs: &[&Expr]) -> (TokenStream, TokenStream) {
         let instance_var = format_ident!("{}instance", self.unique_prefix);
@@ -260,7 +260,7 @@ impl<'a> CodeGen<'a> {
                         vec_param_vars.insert(id.clone());
                         Ok(quote! {#instance_var.#id})
                     }
-                })
+                }, false)
                 .unwrap();
             let AExprType::Point {
                 is_pub: true,
@@ -356,7 +356,7 @@ impl<'a> CodeGen<'a> {
                         }
                         Ok(quote! { #id })
                     }
-                })
+                }, true)
             else {
                 let expr_str = quote! { #expr }.to_string();
                 panic!("Right side of = is not a valid arithmetic expression: {expr_str}");
@@ -386,12 +386,11 @@ impl<'a> CodeGen<'a> {
                     param_vec_code = quote! {
                         #param_vec_code
                         if #vec_len_var != #instance_var.#thisvar.len() {
-                            eprintln!(
+                            return Err(InvalidInstance::new(format!(
                                 "Instance variables {} and {} must have the same length",
                                 stringify!(#firstvar),
                                 stringify!(#thisvar),
-                            );
-                            return Err(SigmaError::VerificationFailure);
+                            )));
                         }
                     };
                 }
@@ -410,7 +409,7 @@ impl<'a> CodeGen<'a> {
                                 stringify!(#firstvar),
                                 stringify!(#witvar),
                             );
-                            return Err(SigmaError::VerificationFailure);
+                            return Err(InvalidWitness);
                         }
                     }
                 }
@@ -453,7 +452,7 @@ impl<'a> CodeGen<'a> {
                     #eq_code
                     #element_assigns
 
-                    SigmaOk(ComposedRelation::try_from(#lr_var).unwrap())
+                    ComposedInstance::try_from(#lr_var)
                 }
             },
             quote! {
@@ -461,7 +460,7 @@ impl<'a> CodeGen<'a> {
                     #witness_vec_code
                     let mut witnessvec = Vec::new();
                     #witness_code
-                    SigmaOk(ComposedWitness::Simple(witnessvec))
+                    ProverResult::Ok(ComposedWitness::Simple(witnessvec))
                 }
             },
         )
@@ -481,10 +480,10 @@ impl<'a> CodeGen<'a> {
             // leaf "true")
             StatementTree::Leaf(_) if statement.is_leaf_true() => (
                 quote! {
-                    Ok(ComposedRelation::try_from(LinearRelation::<Point>::new()).unwrap())
+                    InstanceResult::Ok(ComposedInstance::try_from(LinearRelation::<Point>::new()).unwrap())
                 },
                 quote! {
-                    Ok(ComposedWitness::Simple(vec![]))
+                    ProverResult::Ok(ComposedWitness::Simple(vec![]))
                 },
             ),
             // The StatementTree is a single statement.  Generate a
@@ -516,13 +515,13 @@ impl<'a> CodeGen<'a> {
                             .unzip();
                     (
                         quote! {
-                            SigmaOk(ComposedRelation::and([
+                            ComposedInstance::and([
                                 #proto_code?,
                                 #(#others_proto?,)*
-                            ]))
+                            ])
                         },
                         quote! {
-                            SigmaOk(ComposedWitness::and([
+                            ProverResult::Ok(ComposedWitness::and([
                                 #witness_code?,
                                 #(#others_witness?,)*
                             ]))
@@ -537,12 +536,12 @@ impl<'a> CodeGen<'a> {
                     .unzip();
                 (
                     quote! {
-                        SigmaOk(ComposedRelation::or([
+                        ComposedInstance::or([
                             #(#proto?,)*
-                        ]))
+                        ])
                     },
                     quote! {
-                        SigmaOk(ComposedWitness::or([
+                        ProverResult::Ok(ComposedWitness::or([
                             #(#witness?,)*
                         ]))
                     },
@@ -555,12 +554,12 @@ impl<'a> CodeGen<'a> {
                     .unzip();
                 (
                     quote! {
-                        SigmaOk(ComposedRelation::threshold(#thresh, [
+                        ComposedInstance::threshold(#thresh, [
                             #(#proto?,)*
-                        ]))
+                        ])
                     },
                     quote! {
-                        SigmaOk(ComposedWitness::threshold([
+                        ProverResult::Ok(ComposedWitness::threshold([
                             #(#witness?,)*
                         ]))
                     },
@@ -666,7 +665,7 @@ impl<'a> CodeGen<'a> {
             quote! {
                 fn protocol(
                     #instance_var: &Instance,
-                ) -> SigmaResult<ComposedRelation<Point>> {
+                ) -> Result<ComposedInstance<Point>, InvalidInstance> {
                     #protocol_code
                 }
             }
@@ -678,7 +677,7 @@ impl<'a> CodeGen<'a> {
                 fn protocol_witness(
                     instance: &Instance,
                     witness: &Witness,
-                ) -> SigmaResult<ComposedWitness<Point>> {
+                ) -> Result<ComposedWitness<Point>, InvalidWitness> {
                     #witness_code
                 }
             }
@@ -694,7 +693,6 @@ impl<'a> CodeGen<'a> {
             let rng_var = format_ident!("{}rng", self.unique_prefix);
             let proto_var = format_ident!("{}proto", self.unique_prefix);
             let proto_witness_var = format_ident!("{}proto_witness", self.unique_prefix);
-            let nizk_var = format_ident!("{}nizk", self.unique_prefix);
 
             quote! {
                 pub fn prove(
@@ -702,12 +700,12 @@ impl<'a> CodeGen<'a> {
                     #witness_var: &Witness,
                     #session_id_var: &[u8],
                     #rng_var: &mut (impl CryptoRng + RngCore),
-                ) -> SigmaResult<Vec<u8>> {
+                ) -> Result<Vec<u8>, InvalidWitness> {
+                    // The proof nonces are drawn internally by sigma-proofs
+                    let _ = #rng_var;
                     let #proto_var = protocol(#instance_var)?;
                     let #proto_witness_var = protocol_witness(#instance_var, #witness_var)?;
-                    let #nizk_var = #proto_var.into_nizk(#session_id_var);
-
-                    #nizk_var.prove_compact(&#proto_witness_var, #rng_var)
+                    prove_compact(#session_id_var, &#proto_var, &#proto_witness_var)
                 }
             }
         } else {
@@ -720,18 +718,15 @@ impl<'a> CodeGen<'a> {
             let proof_var = format_ident!("{}proof", self.unique_prefix);
             let session_id_var = format_ident!("{}session_id", self.unique_prefix);
             let proto_var = format_ident!("{}proto", self.unique_prefix);
-            let nizk_var = format_ident!("{}nizk", self.unique_prefix);
 
             quote! {
                 pub fn verify(
                     #instance_var: &Instance,
                     #proof_var: &[u8],
                     #session_id_var: &[u8],
-                ) -> SigmaResult<()> {
+                ) -> Result<(), VerificationError> {
                     let #proto_var = protocol(#instance_var)?;
-                    let #nizk_var = #proto_var.into_nizk(#session_id_var);
-
-                    #nizk_var.verify_compact(#proof_var)
+                    verify_compact(#session_id_var, &#proto_var, #proof_var)
                 }
             }
         } else {
@@ -756,11 +751,12 @@ impl<'a> CodeGen<'a> {
                 use sigma_compiler::subtle::CtOption;
                 use sigma_compiler::vecutils::*;
                 use sigma_proofs::{
-                    composition::{ComposedRelation, ComposedWitness},
-                    errors::Error as SigmaError,
-                    errors::Ok as SigmaOk,
-                    errors::Result as SigmaResult,
-                    LinearRelation, Nizk,
+                    composition::{ComposedInstance, ComposedWitness},
+                    errors::{
+                        InstanceResult, InvalidInstance, InvalidWitness, ProverResult,
+                        VerificationError,
+                    },
+                    prove_compact, verify_compact, LinearRelation,
                 };
                 use std::ops::Neg;
                 #dump_use

+ 33 - 6
sigma-compiler-core/src/sigma/types.rs

@@ -755,6 +755,23 @@ pub fn tokens_mul_maybe_vec(
 
 pub struct AExprTokenFold<'a> {
     ident_closure: &'a mut dyn FnMut(&Ident, AExprType) -> Result<TokenStream>,
+    /// Whether the emitted expression is built from sigma-proofs relation
+    /// variables rather than from runtime Scalars and Points.  It decides
+    /// how `sum` is summed; see [`AExprTokenFold::sum_tokens`].
+    over_relation_vars: bool,
+}
+
+impl AExprTokenFold<'_> {
+    /// The tokens summing `arge`, whose elements are relation variables
+    /// exactly when this fold emits into a relation and `over_vars` says
+    /// the expression is not built purely from public runtime values.
+    fn sum_tokens(&self, over_vars: bool, arge: TokenStream) -> TokenStream {
+        if self.over_relation_vars && over_vars {
+            quote! { sigma_compiler::vecutils::sum_vars(&(#arge)) }
+        } else {
+            quote! { sigma_compiler::vecutils::sum_vec(&(#arge)) }
+        }
+    }
 }
 
 impl<'a> AExprFold<TokenStream> for AExprTokenFold<'a> {
@@ -837,13 +854,18 @@ impl<'a> AExprFold<TokenStream> for AExprTokenFold<'a> {
     }
 
     /// Called when summing a vector of `Scalar`s
+    ///
+    /// Over relation variables the sum has to stay a sum of those
+    /// variables: adding two of them normalizes to sigma-proofs' canonical
+    /// weighted sum, which the remaining elements can no longer be added
+    /// into.  Over runtime values the sum is just their sum.
     fn sum_scalars(
         &mut self,
         arg: (AExprType, TokenStream),
         _restype: AExprType,
     ) -> Result<TokenStream> {
-        let arge = arg.1;
-        Ok(quote! { sigma_compiler::vecutils::sum_vec(&(#arge)) })
+        let over_vars = !matches!(arg.0, AExprType::Scalar { is_pub: true, .. });
+        Ok(self.sum_tokens(over_vars, arg.1))
     }
 
     /// Called when summing a vector of `Point`s
@@ -852,8 +874,7 @@ impl<'a> AExprFold<TokenStream> for AExprTokenFold<'a> {
         arg: (AExprType, TokenStream),
         _restype: AExprType,
     ) -> Result<TokenStream> {
-        let arge = arg.1;
-        Ok(quote! { sigma_compiler::vecutils::sum_vec(&(#arge)) })
+        Ok(self.sum_tokens(true, arg.1))
     }
 
     /// Called when subtracting two `Scalar`s
@@ -986,18 +1007,24 @@ impl<'a> AExprFold<TokenStream> for AExprTokenFold<'a> {
 pub fn expr_type_tokens(vars: &VarDict, expr: &Expr) -> Result<(AExprType, TokenStream)> {
     let mut fold = AExprTokenFold {
         ident_closure: &mut |id, _var_type| Ok(quote! { #id }),
+        over_relation_vars: false,
     };
     fold.fold(vars, expr)
 }
 
 /// Like [`expr_type_tokens`], but call a custom closure on encountering
-/// each [`struct@Ident`].
+/// each [`struct@Ident`].  Set `over_relation_vars` when the closure
+/// yields sigma-proofs relation variables instead of runtime values.
 pub fn expr_type_tokens_id_closure(
     vars: &VarDict,
     expr: &Expr,
     ident_closure: &mut dyn FnMut(&Ident, AExprType) -> Result<TokenStream>,
+    over_relation_vars: bool,
 ) -> Result<(AExprType, TokenStream)> {
-    let mut fold = AExprTokenFold { ident_closure };
+    let mut fold = AExprTokenFold {
+        ident_closure,
+        over_relation_vars,
+    };
     fold.fold(vars, expr)
 }
 

+ 1 - 1
sigma-compiler-core/src/substitution.rs

@@ -156,7 +156,7 @@ pub fn transform(
                                 // for illegal inputs (but is constant time for
                                 // valid inputs)
                                 if #id != #right_tokens {
-                                    return Err(SigmaError::VerificationFailure);
+                                    return Err(InvalidWitness);
                                 }
                             });
                         }

+ 9 - 6
src/rangeutils.rs

@@ -2,7 +2,7 @@
 //! processing of range statements.
 
 use group::ff::PrimeField;
-use sigma_proofs::errors::Error;
+use sigma_proofs::errors::InvalidInstance;
 use subtle::Choice;
 
 /// Convert a [`Scalar`] to an [`u128`], assuming it fits in an [`i128`]
@@ -67,13 +67,14 @@ pub fn bit_decomp<S: PrimeField>(mut s: S, nbits: u32) -> Vec<Choice> {
 /// constant time.
 ///
 /// [`Scalar`]: https://docs.rs/group/0.13.0/group/trait.Group.html#associatedtype.Scalar
-pub fn bitrep_scalars_vartime<S: PrimeField>(upper: S) -> Result<Vec<S>, Error> {
+pub fn bitrep_scalars_vartime<S: PrimeField>(upper: S) -> Result<Vec<S>, InvalidInstance> {
     // Get the `u128` value of `upper`, and its number of bits `nbits`
-    let (upper_val, mut nbits) = bit_decomp_vartime(upper).ok_or(Error::VerificationFailure)?;
+    let (upper_val, mut nbits) = bit_decomp_vartime(upper)
+        .ok_or_else(|| InvalidInstance::new("range upper bound exceeds i128::MAX"))?;
 
     // Ensure `nbits` is at least 2.
     if nbits < 2 {
-        return Err(Error::VerificationFailure);
+        return Err(InvalidInstance::new("range upper bound must be at least 2"));
     }
 
     // If upper is exactly a power of 2, use one fewer bit
@@ -207,7 +208,7 @@ mod tests {
     // Obliviously test whether x is in 0..upper (that is, 0 <= x <
     // upper) using bit decomposition.  `upper` is considered public,
     // but `x` is private.  `upper` must be at least 2.
-    fn bitrep_tester(upper: Scalar, x: Scalar, expected: bool) -> Result<(), Error> {
+    fn bitrep_tester(upper: Scalar, x: Scalar, expected: bool) -> Result<(), InvalidInstance> {
         let rep_scalars = bitrep_scalars_vartime(upper)?;
         let bitrep = compute_bitrep(x, &rep_scalars);
 
@@ -219,7 +220,9 @@ mod tests {
         }
 
         if (x == x_out) != expected {
-            return Err(Error::VerificationFailure);
+            return Err(InvalidInstance::new(
+                "bit representation disagrees with the range",
+            ));
         }
 
         Ok(())

+ 12 - 0
src/vecutils.rs

@@ -111,3 +111,15 @@ where
 {
     summable.iter().cloned().sum()
 }
+
+/// Add the elements of a vector of sigma-proofs relation variables.
+///
+/// [`sum_vec`] cannot serve these: adding two relation variables
+/// normalizes straight to the canonical weighted sum of the algebra, and
+/// the remaining elements are not of that type, so there is no output type
+/// satisfying both of its bounds.  Summing into
+/// [`Sum<T>`](sigma_proofs::linear_relation::Sum) keeps the term type, and
+/// the arithmetic that consumes it normalizes once at the end.
+pub fn sum_vars<T: Clone>(summable: &[T]) -> sigma_proofs::linear_relation::Sum<T> {
+    summable.iter().cloned().sum()
+}

+ 2 - 2
tests/basic.rs

@@ -6,7 +6,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn basic_test() -> sigma_proofs::errors::Result<()> {
+fn basic_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, z, rand r, rand s),
         (C, D, const cind A, const cind B),
@@ -30,5 +30,5 @@ fn basic_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, z, r, s };
 
     let proof = proof::prove(&instance, &witness, b"basic_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"basic_test")
+    Ok(proof::verify(&instance, &proof, b"basic_test")?)
 }

+ 2 - 2
tests/basic_sum.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn basic_sum_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn basic_sum_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, y, rand vec r, rand s),
         (vec C, D, const cind A, const cind B),
@@ -29,7 +29,7 @@ fn basic_sum_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, y, r, s };
 
     let proof = proof::prove(&instance, &witness, b"basic_sum_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"basic_sum_test")
+    Ok(proof::verify(&instance, &proof, b"basic_sum_test")?)
 }
 
 #[test]

+ 4 - 3
tests/basic_vec.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn basic_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn basic_vec_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, rand vec r),
         (vec C, const cind A, const cind B),
@@ -24,12 +24,13 @@ fn basic_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, r };
 
     let proof = proof::prove(&instance, &witness, b"basic_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"basic_vec_test")
+    Ok(proof::verify(&instance, &proof, b"basic_vec_test")?)
 }
 
 #[test]
 fn basic_vec_test() {
-    basic_vec_test_vecsize(0).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused.
+    assert!(basic_vec_test_vecsize(0).is_err());
     basic_vec_test_vecsize(1).unwrap();
     basic_vec_test_vecsize(2).unwrap();
     basic_vec_test_vecsize(20).unwrap();

+ 1 - 1
tests/disj.rs

@@ -6,7 +6,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn disj_test() -> sigma_proofs::errors::Result<()> {
+fn disj_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, rand r),
         (C, const cind A, const cind B),

+ 4 - 3
tests/disj_vec.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn disj_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn disj_vec_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, vec y, pub vec a, rand vec r, rand vec s),
         (vec C, vec D, const cind A, const cind B),
@@ -39,12 +39,13 @@ fn disj_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, y, r, s };
 
     let proof = proof::prove(&instance, &witness, b"disj_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"disj_vec_test")
+    Ok(proof::verify(&instance, &proof, b"disj_vec_test")?)
 }
 
 #[test]
 fn disj_vec_test() {
-    disj_vec_test_vecsize(0).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused.
+    assert!(disj_vec_test_vecsize(0).is_err());
     disj_vec_test_vecsize(1).unwrap();
     disj_vec_test_vecsize(2).unwrap();
     disj_vec_test_vecsize(20).unwrap();

+ 4 - 4
tests/dl.rs

@@ -4,7 +4,7 @@ use group::Group;
 use sigma_compiler::*;
 
 #[test]
-fn dl_zero_test() -> sigma_proofs::errors::Result<()> {
+fn dl_zero_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x),
         (C, const B),
@@ -21,11 +21,11 @@ fn dl_zero_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"dl_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"dl_test")
+    Ok(proof::verify(&instance, &proof, b"dl_test")?)
 }
 
 #[test]
-fn dl_one_test() -> sigma_proofs::errors::Result<()> {
+fn dl_one_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x),
         (C, const B),
@@ -42,5 +42,5 @@ fn dl_one_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"dl_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"dl_test")
+    Ok(proof::verify(&instance, &proof, b"dl_test")?)
 }

+ 4 - 3
tests/dot_product.rs

@@ -3,7 +3,7 @@ use curve25519_dalek::ristretto::RistrettoPoint as G;
 use group::Group;
 use sigma_compiler::*;
 
-fn dot_product_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn dot_product_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, pub vec a),
         (C, D, E, F, vec A, B),
@@ -37,12 +37,13 @@ fn dot_product_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()>
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"dot_product_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"dot_product_test")
+    Ok(proof::verify(&instance, &proof, b"dot_product_test")?)
 }
 
 #[test]
 fn dot_product_test() {
-    dot_product_test_vecsize(0).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused.
+    assert!(dot_product_test_vecsize(0).is_err());
     dot_product_test_vecsize(1).unwrap();
     dot_product_test_vecsize(2).unwrap();
     dot_product_test_vecsize(20).unwrap();

+ 9 - 3
tests/emptystatement.rs

@@ -2,8 +2,15 @@
 use curve25519_dalek::ristretto::RistrettoPoint as G;
 use sigma_compiler::*;
 
+/// A statement with no variables and no equations binds nothing, so it is
+/// satisfied by the empty witness. sigma-proofs refuses to compile one: as a
+/// branch of a disjunction it would make the whole proof forgeable.
+///
+/// The generated prover unwraps `compile`, so the refusal arrives as a panic
+/// rather than an `Err`.
 #[test]
-fn emptystatement_test() -> sigma_proofs::errors::Result<()> {
+#[should_panic(expected = "the statement has no content")]
+fn emptystatement_test() {
     sigma_compiler! { proof,
         (),
         (),
@@ -14,6 +21,5 @@ fn emptystatement_test() -> sigma_proofs::errors::Result<()> {
     let instance = proof::Instance {};
     let witness = proof::Witness {};
 
-    let proof = proof::prove(&instance, &witness, b"emptystatement_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"emptystatement_test")
+    assert!(proof::prove(&instance, &witness, b"emptystatement_test", &mut rng).is_err());
 }

+ 4 - 4
tests/left_expr.rs

@@ -6,7 +6,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn left_expr_test() -> sigma_proofs::errors::Result<()> {
+fn left_expr_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, y, pub a, rand r, rand s),
         (C, D, const cind A, const cind B),
@@ -30,11 +30,11 @@ fn left_expr_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, y, r, s };
 
     let proof = proof::prove(&instance, &witness, b"left_expr_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"left_expr_test")
+    Ok(proof::verify(&instance, &proof, b"left_expr_test")?)
 }
 
 #[test]
-fn left_expr_vec_test() -> sigma_proofs::errors::Result<()> {
+fn left_expr_vec_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, vec y, z, pub vec a, pub b, rand vec r, rand vec s, rand t),
         (vec C, vec D, E, const cind A, const cind B),
@@ -78,5 +78,5 @@ fn left_expr_vec_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, y, z, r, s, t };
 
     let proof = proof::prove(&instance, &witness, b"left_expr_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"left_expr_vec_test")
+    Ok(proof::verify(&instance, &proof, b"left_expr_vec_test")?)
 }

+ 2 - 2
tests/notequals.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn do_test(x_u128: u128) -> sigma_proofs::errors::Result<()> {
+fn do_test(x_u128: u128) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, rand r),
         (C, const cind A, const cind B),
@@ -25,7 +25,7 @@ fn do_test(x_u128: u128) -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, r };
 
     let proof = proof::prove(&instance, &witness, b"notequals_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"notequals_test")
+    Ok(proof::verify(&instance, &proof, b"notequals_test")?)
 }
 
 #[test]

+ 2 - 2
tests/pubscalars.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn pubscalars_test_val(b_val: u128) -> sigma_proofs::errors::Result<()> {
+fn pubscalars_test_val(b_val: u128) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, z, rand r, rand s, pub a, pub b),
         (C, D, const cind A, const cind B),
@@ -32,7 +32,7 @@ fn pubscalars_test_val(b_val: u128) -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, z, r, s };
 
     let proof = proof::prove(&instance, &witness, b"pubscalars_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubscalars_test")
+    Ok(proof::verify(&instance, &proof, b"pubscalars_test")?)
 }
 
 #[test]

+ 2 - 2
tests/pubscalars_or.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn pubscalars_or_test_val(b_val: u128) -> sigma_proofs::errors::Result<()> {
+fn pubscalars_or_test_val(b_val: u128) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, rand r, pub a, pub b),
         (C, const cind A, const cind B),
@@ -30,7 +30,7 @@ fn pubscalars_or_test_val(b_val: u128) -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, r };
 
     let proof = proof::prove(&instance, &witness, b"pubscalars_or_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubscalars_or_test")
+    Ok(proof::verify(&instance, &proof, b"pubscalars_or_test")?)
 }
 
 #[test]

+ 2 - 2
tests/pubscalars_or_and.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn pubscalars_or_and_test_val(x_val: u128, b_val: u128) -> sigma_proofs::errors::Result<()> {
+fn pubscalars_or_and_test_val(x_val: u128, b_val: u128) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, rand r, pub a, pub b),
         (C, const cind A, const cind B),
@@ -36,7 +36,7 @@ fn pubscalars_or_and_test_val(x_val: u128, b_val: u128) -> sigma_proofs::errors:
     let witness = proof::Witness { x, r };
 
     let proof = proof::prove(&instance, &witness, b"pubscalars_or_and_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubscalars_or_and_test")
+    Ok(proof::verify(&instance, &proof, b"pubscalars_or_and_test")?)
 }
 
 #[test]

+ 9 - 19
tests/pubscalars_or_and_vec.rs

@@ -9,7 +9,7 @@ fn pubscalars_or_vec_test_vecsize_val(
     vecsize: usize,
     b_val: u128,
     x_val: Option<u128>,
-) -> sigma_proofs::errors::Result<()> {
+) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, vec y, pub vec a, pub vec b, rand vec r, rand vec s),
         (vec C, vec D, const cind A, const cind B),
@@ -56,27 +56,17 @@ fn pubscalars_or_vec_test_vecsize_val(
     let witness = proof::Witness { x, y, r, s };
 
     let proof = proof::prove(&instance, &witness, b"pubscalars_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubscalars_vec_test")
+    Ok(proof::verify(&instance, &proof, b"pubscalars_vec_test")?)
 }
 
 fn pubscalars_or_vec_emptyvec() {
-    pubscalars_or_vec_test_vecsize_val(0, 0, Some(0)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 1, Some(0)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 2, Some(0)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 3, Some(0)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 4, Some(0)).unwrap();
-
-    pubscalars_or_vec_test_vecsize_val(0, 0, Some(1)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 1, Some(1)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 2, Some(1)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 3, Some(1)).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 4, Some(1)).unwrap();
-
-    pubscalars_or_vec_test_vecsize_val(0, 0, None).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 1, None).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 2, None).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 3, None).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 4, None).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused, for
+    // every combination of the public scalar and the optional AND branch.
+    for x_val in [Some(0), Some(1), None] {
+        for b_val in 0..=4 {
+            assert!(pubscalars_or_vec_test_vecsize_val(0, b_val, x_val).is_err());
+        }
+    }
 }
 
 fn pubscalars_or_vec_vecsize(vecsize: usize) {

+ 7 - 7
tests/pubscalars_or_vec.rs

@@ -8,7 +8,7 @@ use sigma_compiler::*;
 fn pubscalars_or_vec_test_vecsize_val(
     vecsize: usize,
     b_val: u128,
-) -> sigma_proofs::errors::Result<()> {
+) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, pub vec a, pub vec b, rand vec r),
         (vec C, const cind A, const cind B),
@@ -37,16 +37,16 @@ fn pubscalars_or_vec_test_vecsize_val(
     let witness = proof::Witness { x, r };
 
     let proof = proof::prove(&instance, &witness, b"pubscalars_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubscalars_vec_test")
+    Ok(proof::verify(&instance, &proof, b"pubscalars_vec_test")?)
 }
 
 #[test]
 fn pubscalars_or_vec_test() {
-    pubscalars_or_vec_test_vecsize_val(0, 0).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 1).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 2).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 3).unwrap();
-    pubscalars_or_vec_test_vecsize_val(0, 4).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused,
+    // whichever branch the public scalar selects.
+    for b_val in 0..=4 {
+        assert!(pubscalars_or_vec_test_vecsize_val(0, b_val).is_err());
+    }
     for vecsize in [1, 2, 20] {
         pubscalars_or_vec_test_vecsize_val(vecsize, 0).unwrap();
         pubscalars_or_vec_test_vecsize_val(vecsize, 1).unwrap_err();

+ 4 - 3
tests/pubscalars_vec.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn pubscalars_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn pubscalars_vec_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, vec y, pub vec a, pub vec b, rand vec r, rand vec s),
         (vec C, vec D, const cind A, const cind B),
@@ -36,12 +36,13 @@ fn pubscalars_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<(
     let witness = proof::Witness { x, y, r, s };
 
     let proof = proof::prove(&instance, &witness, b"pubscalars_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubscalars_vec_test")
+    Ok(proof::verify(&instance, &proof, b"pubscalars_vec_test")?)
 }
 
 #[test]
 fn pubscalars_vec_test() {
-    pubscalars_vec_test_vecsize(0).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused.
+    assert!(pubscalars_vec_test_vecsize(0).is_err());
     pubscalars_vec_test_vecsize(1).unwrap();
     pubscalars_vec_test_vecsize(2).unwrap();
     pubscalars_vec_test_vecsize(20).unwrap();

+ 2 - 2
tests/pubstatements.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sigma_compiler::*;
 
 #[test]
-fn pubstatements_test() -> sigma_proofs::errors::Result<()> {
+fn pubstatements_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, pub a),
         (C, D, const cind B),
@@ -25,5 +25,5 @@ fn pubstatements_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"pubstatements_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubstatements_test")
+    Ok(proof::verify(&instance, &proof, b"pubstatements_test")?)
 }

+ 2 - 2
tests/pubstatements_vec.rs

@@ -4,7 +4,7 @@ use group::ff::PrimeField;
 use group::Group;
 use sigma_compiler::*;
 
-fn pubstatements_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn pubstatements_vec_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, pub vec a),
         (vec C, vec D, const cind B),
@@ -24,7 +24,7 @@ fn pubstatements_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Resul
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"pubstatements_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"pubstatements_vec_test")
+    Ok(proof::verify(&instance, &proof, b"pubstatements_vec_test")?)
 }
 
 #[test]

+ 2 - 2
tests/range.rs

@@ -6,7 +6,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn range_test() -> sigma_proofs::errors::Result<()> {
+fn range_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, y, pub a, rand r),
         (C, D, const cind A, const cind B),
@@ -31,5 +31,5 @@ fn range_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, y, r };
 
     let proof = proof::prove(&instance, &witness, b"range_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"range_test")
+    Ok(proof::verify(&instance, &proof, b"range_test")?)
 }

+ 2 - 2
tests/range_dump.rs

@@ -7,7 +7,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn range_dump_test() -> sigma_proofs::errors::Result<()> {
+fn range_dump_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, y, pub a, rand r),
         (C, D, const cind A, const cind B),
@@ -39,5 +39,5 @@ fn range_dump_test() -> sigma_proofs::errors::Result<()> {
     let buf = sigma_compiler::dumper::dump_buffer();
     print!("{buf}");
 
-    res
+    Ok(res?)
 }

+ 1 - 1
tests/simple_or.rs

@@ -5,7 +5,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn simple_or_test() -> sigma_proofs::errors::Result<()> {
+fn simple_or_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, y),
         (C, const cind A, const cind B),

+ 2 - 2
tests/substitution_or.rs

@@ -6,7 +6,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn substitution_or_test() -> sigma_proofs::errors::Result<()> {
+fn substitution_or_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, z, rand r, rand s),
         (C, D, const cind A, const cind B),
@@ -33,5 +33,5 @@ fn substitution_or_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x, z, r, s };
 
     let proof = proof::prove(&instance, &witness, b"substitution_or_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"substitution_or_test")
+    Ok(proof::verify(&instance, &proof, b"substitution_or_test")?)
 }

+ 4 - 3
tests/substitution_vec.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn substitution_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn substitution_vec_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x, vec y, rand vec r, rand vec s),
         (vec C, vec D, const cind A, const cind B),
@@ -29,12 +29,13 @@ fn substitution_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result
     let witness = proof::Witness { x, y, r, s };
 
     let proof = proof::prove(&instance, &witness, b"substitution_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"substitution_vec_test")
+    Ok(proof::verify(&instance, &proof, b"substitution_vec_test")?)
 }
 
 #[test]
 fn substitution_vec_test() {
-    substitution_vec_test_vecsize(0).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused.
+    assert!(substitution_vec_test_vecsize(0).is_err());
     substitution_vec_test_vecsize(1).unwrap();
     substitution_vec_test_vecsize(2).unwrap();
     substitution_vec_test_vecsize(20).unwrap();

+ 2 - 2
tests/subtract.rs

@@ -4,7 +4,7 @@ use group::Group;
 use sigma_compiler::*;
 
 #[test]
-fn subtract_test() -> sigma_proofs::errors::Result<()> {
+fn subtract_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x),
         (C, const cind B),
@@ -21,5 +21,5 @@ fn subtract_test() -> sigma_proofs::errors::Result<()> {
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"subtract_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"subtract_test")
+    Ok(proof::verify(&instance, &proof, b"subtract_test")?)
 }

+ 4 - 3
tests/subtract_vec.rs

@@ -5,7 +5,7 @@ use group::Group;
 use sha2::Sha512;
 use sigma_compiler::*;
 
-fn subtract_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()> {
+fn subtract_vec_test_vecsize(vecsize: usize) -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (vec x),
         (vec C, vec D, vec E, const cind A, const cind B),
@@ -33,12 +33,13 @@ fn subtract_vec_test_vecsize(vecsize: usize) -> sigma_proofs::errors::Result<()>
     let witness = proof::Witness { x };
 
     let proof = proof::prove(&instance, &witness, b"subtract_vec_test", &mut rng)?;
-    proof::verify(&instance, &proof, b"subtract_vec_test")
+    Ok(proof::verify(&instance, &proof, b"subtract_vec_test")?)
 }
 
 #[test]
 fn subtract_vec_test() {
-    subtract_vec_test_vecsize(0).unwrap();
+    // No equations at size 0: a relation that binds nothing is refused.
+    assert!(subtract_vec_test_vecsize(0).is_err());
     subtract_vec_test_vecsize(1).unwrap();
     subtract_vec_test_vecsize(2).unwrap();
     subtract_vec_test_vecsize(20).unwrap();

+ 11 - 13
tests/threshold.rs

@@ -5,7 +5,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn threshold_test() -> sigma_proofs::errors::Result<()> {
+fn threshold_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { thresh3,
         (x1, x2, x3, x4, x5, rand r),
         (C, const cind G0, const cind G1, const cind G2, const cind G3,
@@ -53,18 +53,16 @@ fn threshold_test() -> sigma_proofs::errors::Result<()> {
             r,
         };
 
-        match thresh3::prove(&instance, &witness, b"thresh_test", &mut rng) {
-            Ok(_) if num_true < 3 => {
-                panic!("THRESH passed when it should have failed (true_pattern = {true_pattern})")
-            }
-            Err(_) if num_true >= 3 => {
-                panic!("THRESH failed when it should have passed (true_pattern = {true_pattern})")
-            }
-            Ok(proof) => {
-                thresh3::verify(&instance, &proof, b"thresh_test")?;
-            }
-            Err(_) => {}
-        }
+        // The prover checks witness shape, not validity: an unsatisfiable
+        // threshold still yields a NARG string, and verification is what
+        // rejects it.
+        let proof = thresh3::prove(&instance, &witness, b"thresh_test", &mut rng)?;
+        let verified = thresh3::verify(&instance, &proof, b"thresh_test").is_ok();
+        assert_eq!(
+            verified,
+            num_true >= 3,
+            "THRESH verification disagreed with satisfiability (true_pattern = {true_pattern})"
+        );
     }
     Ok(())
 }

+ 11 - 13
tests/threshold_pubscalars.rs

@@ -5,7 +5,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn threshold_pubscalars_test() -> sigma_proofs::errors::Result<()> {
+fn threshold_pubscalars_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { thresh3,
         (pub x1, pub x2, pub x3, pub x4, pub x5, rand r),
         (C, const cind G0, const cind G1, const cind G2, const cind G3,
@@ -51,18 +51,16 @@ fn threshold_pubscalars_test() -> sigma_proofs::errors::Result<()> {
         };
         let witness = thresh3::Witness { r };
 
-        match thresh3::prove(&instance, &witness, b"thresh_pubscalars_test", &mut rng) {
-            Ok(_) if num_true < 3 => {
-                panic!("THRESH passed when it should have failed (true_pattern = {true_pattern})")
-            }
-            Err(_) if num_true >= 3 => {
-                panic!("THRESH failed when it should have passed (true_pattern = {true_pattern})")
-            }
-            Ok(proof) => {
-                thresh3::verify(&instance, &proof, b"thresh_pubscalars_test")?;
-            }
-            Err(_) => {}
-        }
+        // The prover checks witness shape, not validity: an unsatisfiable
+        // threshold still yields a NARG string, and verification is what
+        // rejects it.
+        let proof = thresh3::prove(&instance, &witness, b"thresh_pubscalars_test", &mut rng)?;
+        let verified = thresh3::verify(&instance, &proof, b"thresh_pubscalars_test").is_ok();
+        assert_eq!(
+            verified,
+            num_true >= 3,
+            "THRESH verification disagreed with satisfiability (true_pattern = {true_pattern})"
+        );
     }
     Ok(())
 }

+ 1 - 1
tests/two_true.rs

@@ -5,7 +5,7 @@ use sha2::Sha512;
 use sigma_compiler::*;
 
 #[test]
-fn two_true_test() -> sigma_proofs::errors::Result<()> {
+fn two_true_test() -> Result<(), Box<dyn std::error::Error>> {
     sigma_compiler! { proof,
         (x, y),
         (C, D, const cind A, const cind B),