From 15d5f370cbe36c6bec1dbee10abe705cbcb0299c Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:57:40 -0500 Subject: [PATCH] Improve parameter error messages --- src/parallel/parameters.rs | 33 ++++++++--- src/parallel/proof.rs | 114 +++++++++++++++++++++++++++---------- src/parallel/statement.rs | 43 ++++++++++---- src/parallel/transcript.rs | 4 +- src/parallel/witness.rs | 16 ++++-- src/parameters.rs | 33 ++++++++--- src/proof.rs | 106 +++++++++++++++++++++++++--------- src/statement.rs | 27 ++++++--- src/transcript.rs | 4 +- src/witness.rs | 11 ++-- 10 files changed, 290 insertions(+), 101 deletions(-) diff --git a/src/parallel/parameters.rs b/src/parallel/parameters.rs index dc23d25..92f1ca5 100644 --- a/src/parallel/parameters.rs +++ b/src/parallel/parameters.rs @@ -38,8 +38,11 @@ pub struct TriptychParameters { #[derive(Debug, Snafu)] pub enum ParameterError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, } impl TriptychParameters { @@ -98,13 +101,18 @@ impl TriptychParameters { U: &RistrettoPoint, ) -> Result { // These bounds are required by the protocol - if n < 2 || m < 2 { - return Err(ParameterError::InvalidParameter); + if n < 2 { + return Err(ParameterError::InvalidParameter { reason: "`n < 2`" }); + } + if m < 2 { + return Err(ParameterError::InvalidParameter { reason: "`m < 2`" }); } // Check that the parameters don't overflow `u32` if n.checked_pow(m).is_none() { - return Err(ParameterError::InvalidParameter); + return Err(ParameterError::InvalidParameter { + reason: "`n**m` overflowed `u32`", + }); } // Use `BLAKE3` to generate `CommitmentH` @@ -121,7 +129,9 @@ impl TriptychParameters { hasher.update(&m.to_le_bytes()); let mut hasher_xof = hasher.finalize_xof(); let mut CommitmentG_bytes = [0u8; 64]; - let CommitmentG = (0..n.checked_mul(m).ok_or(ParameterError::InvalidParameter)?) + let CommitmentG = (0..n.checked_mul(m).ok_or(ParameterError::InvalidParameter { + reason: "`n*m` overflowed `u32`", + })?) .map(|_| { hasher_xof.fill(&mut CommitmentG_bytes); RistrettoPoint::from_uniform_bytes(&CommitmentG_bytes) @@ -166,8 +176,15 @@ impl TriptychParameters { timing: OperationTiming, ) -> Result { // Check that the matrix dimensions are valid - if matrix.len() != (self.m as usize) || matrix.iter().any(|m| m.len() != (self.n as usize)) { - return Err(ParameterError::InvalidParameter); + if matrix.len() != (self.m as usize) { + return Err(ParameterError::InvalidParameter { + reason: "matrix did not have `m` rows", + }); + } + if matrix.iter().any(|m| m.len() != (self.n as usize)) { + return Err(ParameterError::InvalidParameter { + reason: "matrix did not have `n` columns", + }); } // Flatten before evaluating the commitment diff --git a/src/parallel/proof.rs b/src/parallel/proof.rs index 78e451a..2f9db2d 100644 --- a/src/parallel/proof.rs +++ b/src/parallel/proof.rs @@ -53,8 +53,11 @@ pub struct TriptychProof { #[derive(Debug, Snafu)] pub enum ProofError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, /// A transcript challenge was invalid. #[snafu(display("A transcript challenge was invalid"))] InvalidChallenge, @@ -174,7 +177,9 @@ impl TriptychProof { ) -> Result { // Check that the witness and statement have identical parameters if witness.get_params() != statement.get_params() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "witness and statement parameters did not match", + }); } // Extract values for convenience @@ -205,13 +210,17 @@ impl TriptychProof { } if M_l != r * params.get_G() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "`M[l] != r * G`", + }); } if M1_l - offset != r1 * params.get_G1() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "`M1[l] - offset != r1 * G1`", + }); } if &(r * J) != params.get_U() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { reason: "`r * J != U`" }); } // Set up the transcript @@ -231,16 +240,23 @@ impl TriptychProof { } let A = params .commit_matrix(&a, &r_A, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `A`", + })?; // Compute the `B` matrix commitment let r_B = Scalar::random(transcript.as_mut_rng()); let l_decomposed = match timing { OperationTiming::Constant => { - GrayIterator::decompose(params.get_n(), params.get_m(), l).ok_or(ProofError::InvalidParameter)? + GrayIterator::decompose(params.get_n(), params.get_m(), l).ok_or(ProofError::InvalidParameter { + reason: "`l` decomposition failed", + })? }, - OperationTiming::Variable => GrayIterator::decompose_vartime(params.get_n(), params.get_m(), l) - .ok_or(ProofError::InvalidParameter)?, + OperationTiming::Variable => GrayIterator::decompose_vartime(params.get_n(), params.get_m(), l).ok_or( + ProofError::InvalidParameter { + reason: "`l` decomposition failed", + }, + )?, }; let sigma = (0..params.get_m()) .map(|j| { @@ -251,7 +267,9 @@ impl TriptychProof { .collect::>>(); let B = params .commit_matrix(&sigma, &r_B, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `B`", + })?; // Compute the `C` matrix commitment let two = Scalar::from(2u32); @@ -265,7 +283,9 @@ impl TriptychProof { .collect::>>(); let C = params .commit_matrix(&a_sigma, &r_C, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `C`", + })?; // Compute the `D` matrix commitment let r_D = Scalar::random(transcript.as_mut_rng()); @@ -278,7 +298,9 @@ impl TriptychProof { .collect::>>(); let D = params .commit_matrix(&a_square, &r_D, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `D`", + })?; // Random masks let rho = Zeroizing::new( @@ -296,7 +318,9 @@ impl TriptychProof { let mut p = Vec::>::with_capacity(params.get_N() as usize); let mut k_decomposed = vec![0; params.get_m() as usize]; for (gray_index, _, gray_new) in - GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter)? + GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter { + reason: "coefficient decomposition failed", + })? { k_decomposed[gray_index] = gray_new; @@ -305,7 +329,9 @@ impl TriptychProof { coefficients.resize( (params.get_m() as usize) .checked_add(1) - .ok_or(ProofError::InvalidParameter)?, + .ok_or(ProofError::InvalidParameter { + reason: "polynomial degree overflowed", + })?, Scalar::ZERO, ); coefficients[0] = a[0][k_decomposed[0] as usize]; @@ -564,10 +590,14 @@ impl TriptychProof { ) -> Result<(), ProofError> { // Check that we have the same number of statements, proofs, and transcripts if statements.len() != proofs.len() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "number of statements and proofs does not match", + }); } if statements.len() != transcripts.len() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "number of statements and transcripts does not match", + }); } // An empty batch is considered trivially valid @@ -578,12 +608,16 @@ impl TriptychProof { // Each statement must use the same input set (checked using the hash for efficiency) if !statements.iter().map(|s| s.get_input_set().get_hash()).all_equal() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "statement input sets do not match", + }); } // Each statement must use the same parameters (checked using the hash for efficiency) if !statements.iter().map(|s| s.get_params().get_hash()).all_equal() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "statement parameters do not match", + }); } // Extract common values for convenience @@ -594,26 +628,42 @@ impl TriptychProof { // Check that all proof semantics are valid for the statement for proof in proofs { if proof.X.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `X` vector length was not `m`", + }); } if proof.X1.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `X1` vector length was not `m`", + }); } if proof.Y.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `Y` vector length was not `m`", + }); } if proof.f.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `f` matrix did not have `m` rows", + }); } for f_row in &proof.f { - if f_row.len() != params.get_n().checked_sub(1).ok_or(ProofError::InvalidParameter)? as usize { - return Err(ProofError::InvalidParameter); + if f_row.len() != + params.get_n().checked_sub(1).ok_or(ProofError::InvalidParameter { + reason: "proof `f` matrix column count overflowed", + })? as usize + { + return Err(ProofError::InvalidParameter { + reason: "proof `f` matrix did not have `n - 1` columns", + }); } } } // Determine the size of the final check vector, which must not overflow `usize` - let batch_size = u32::try_from(proofs.len()).map_err(|_| ProofError::InvalidParameter)?; + let batch_size = u32::try_from(proofs.len()).map_err(|_| ProofError::InvalidParameter { + reason: "batch size overflowed `u32`", + })?; // This is unlikely to overflow; even if it does, the only effect is unnecessary reallocation #[allow(clippy::arithmetic_side_effects)] @@ -631,7 +681,9 @@ impl TriptychProof { + 3 * params.get_m() // X, X1, Y ), ) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "multiscalar multiplication size overflowed `usize`", + })?; // Set up the point vector for the final check let points = proofs @@ -708,7 +760,9 @@ impl TriptychProof { // Check that `f` does not contain zero, which breaks batch inversion for f_row in &f { if f_row.contains(&Scalar::ZERO) { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `f` matrix contained 0", + }); } } @@ -779,7 +833,9 @@ impl TriptychProof { // Set up the initial `f` product and Gray iterator let mut f_product = f.iter().map(|f_row| f_row[0]).product::(); let gray_iterator = - GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter)?; + GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter { + reason: "coefficient decomposition failed", + })?; // Invert each element of `f` for efficiency let mut f_inverse_flat = f.iter().flatten().copied().collect::>(); diff --git a/src/parallel/statement.rs b/src/parallel/statement.rs index 93b2be9..e0e380f 100644 --- a/src/parallel/statement.rs +++ b/src/parallel/statement.rs @@ -32,7 +32,9 @@ impl TriptychInputSet { pub fn new(M: &[RistrettoPoint], M1: &[RistrettoPoint]) -> Result { // The verification key vectors must be the same length if M.len() != M1.len() { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "`M` and `M1` did not have matching lengths", + }); } Self::new_internal(M, M1, M.len()) @@ -55,7 +57,9 @@ impl TriptychInputSet { ) -> Result { // The vectors must be the same length if M.len() != M1.len() { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "`M` and `M1` did not have matching lengths", + }); } // Get the unpadded size @@ -63,12 +67,18 @@ impl TriptychInputSet { // We cannot have the vectors be too long if unpadded_size > params.get_N() as usize { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "unpadded size exceeded `N`", + }); } // Get the last elements, which also ensures the vectors are nonempty - let last = M.last().ok_or(StatementError::InvalidParameter)?; - let last1 = M1.last().ok_or(StatementError::InvalidParameter)?; + let last = M.last().ok_or(StatementError::InvalidParameter { + reason: "`M` was empty", + })?; + let last1 = M1.last().ok_or(StatementError::InvalidParameter { + reason: "`M1` was empty", + })?; // Pad the vectors with the corresponding last elements let mut M_padded = M.to_vec(); @@ -83,7 +93,9 @@ impl TriptychInputSet { #[allow(non_snake_case)] fn new_internal(M: &[RistrettoPoint], M1: &[RistrettoPoint], unpadded_size: usize) -> Result { // Ensure the verification key vector lengths don't overflow - let unpadded_size = u32::try_from(unpadded_size).map_err(|_| StatementError::InvalidParameter)?; + let unpadded_size = u32::try_from(unpadded_size).map_err(|_| StatementError::InvalidParameter { + reason: "unpadded size overflowed `u32`", + })?; // Use Merlin for the transcript hash let mut transcript = Transcript::new(Self::DOMAIN.as_bytes()); @@ -140,8 +152,11 @@ pub struct TriptychStatement { #[derive(Debug, Snafu)] pub enum StatementError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, } impl TriptychStatement { @@ -168,10 +183,14 @@ impl TriptychStatement { ) -> Result { // Check that the input vectors are valid against the parameters if input_set.get_keys().len() != params.get_N() as usize { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "input vector size was not `N`", + }); } if input_set.get_keys().contains(&RistrettoPoint::identity()) { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "input vector contained the identity point", + }); } if input_set .get_auxiliary_keys() @@ -180,7 +199,9 @@ impl TriptychStatement { .collect::>() .contains(&RistrettoPoint::identity()) { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "input vector contained the identity point", + }); } // Use Merlin for the transcript hash diff --git a/src/parallel/transcript.rs b/src/parallel/transcript.rs index 01f9a8c..aa70b7e 100644 --- a/src/parallel/transcript.rs +++ b/src/parallel/transcript.rs @@ -88,7 +88,9 @@ impl<'a, R: CryptoRngCore> ProofTranscript<'a, R> { let xi = Scalar::from_bytes_mod_order_wide(&xi_bytes); // Get powers of the challenge and confirm they are nonzero - let mut xi_powers = Vec::with_capacity(m.checked_add(1).ok_or(ProofError::InvalidParameter)?); + let mut xi_powers = Vec::with_capacity(m.checked_add(1).ok_or(ProofError::InvalidParameter { + reason: "challenge power count overflowed `usize`", + })?); let mut xi_power = Scalar::ONE; for _ in 0..=m { if xi_power == Scalar::ZERO { diff --git a/src/parallel/witness.rs b/src/parallel/witness.rs index 1af3845..5e07859 100644 --- a/src/parallel/witness.rs +++ b/src/parallel/witness.rs @@ -26,8 +26,11 @@ pub struct TriptychWitness { #[derive(Debug, Snafu)] pub enum WitnessError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, } impl TriptychWitness { @@ -39,11 +42,14 @@ impl TriptychWitness { /// If you'd like a [`TriptychWitness`] generated securely for you, use [`TriptychWitness::random`] instead. #[allow(non_snake_case)] pub fn new(params: &TriptychParameters, l: u32, r: &Scalar, r1: &Scalar) -> Result { - if r == &Scalar::ZERO || r1 == &Scalar::ZERO { - return Err(WitnessError::InvalidParameter); + if r == &Scalar::ZERO { + return Err(WitnessError::InvalidParameter { reason: "`r == 0`" }); + } + if r1 == &Scalar::ZERO { + return Err(WitnessError::InvalidParameter { reason: "`r1 == 0`" }); } if l >= params.get_N() { - return Err(WitnessError::InvalidParameter); + return Err(WitnessError::InvalidParameter { reason: "`l >= N`" }); } Ok(Self { diff --git a/src/parameters.rs b/src/parameters.rs index 6331ede..dde582e 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -37,8 +37,11 @@ pub struct TriptychParameters { #[derive(Debug, Snafu)] pub enum ParameterError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, } impl TriptychParameters { @@ -83,13 +86,18 @@ impl TriptychParameters { #[allow(non_snake_case)] pub fn new_with_generators(n: u32, m: u32, G: &RistrettoPoint, U: &RistrettoPoint) -> Result { // These bounds are required by the protocol - if n < 2 || m < 2 { - return Err(ParameterError::InvalidParameter); + if n < 2 { + return Err(ParameterError::InvalidParameter { reason: "`n < 2`" }); + } + if m < 2 { + return Err(ParameterError::InvalidParameter { reason: "`m < 2`" }); } // Check that the parameters don't overflow `u32` if n.checked_pow(m).is_none() { - return Err(ParameterError::InvalidParameter); + return Err(ParameterError::InvalidParameter { + reason: "`n**m` overflowed `u32`", + }); } // Use `BLAKE3` to generate `CommitmentH` @@ -106,7 +114,9 @@ impl TriptychParameters { hasher.update(&m.to_le_bytes()); let mut hasher_xof = hasher.finalize_xof(); let mut CommitmentG_bytes = [0u8; 64]; - let CommitmentG = (0..n.checked_mul(m).ok_or(ParameterError::InvalidParameter)?) + let CommitmentG = (0..n.checked_mul(m).ok_or(ParameterError::InvalidParameter { + reason: "`n*m` overflowed `u32`", + })?) .map(|_| { hasher_xof.fill(&mut CommitmentG_bytes); RistrettoPoint::from_uniform_bytes(&CommitmentG_bytes) @@ -149,8 +159,15 @@ impl TriptychParameters { timing: OperationTiming, ) -> Result { // Check that the matrix dimensions are valid - if matrix.len() != (self.m as usize) || matrix.iter().any(|m| m.len() != (self.n as usize)) { - return Err(ParameterError::InvalidParameter); + if matrix.len() != (self.m as usize) { + return Err(ParameterError::InvalidParameter { + reason: "matrix did not have `m` rows", + }); + } + if matrix.iter().any(|m| m.len() != (self.n as usize)) { + return Err(ParameterError::InvalidParameter { + reason: "matrix did not have `n` columns", + }); } // Flatten before evaluating the commitment diff --git a/src/proof.rs b/src/proof.rs index adaad8a..e3b4e90 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -53,8 +53,11 @@ pub struct TriptychProof { #[derive(Debug, Snafu)] pub enum ProofError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, /// A transcript challenge was invalid. #[snafu(display("A transcript challenge was invalid"))] InvalidChallenge, @@ -174,7 +177,9 @@ impl TriptychProof { ) -> Result { // Check that the witness and statement have identical parameters if witness.get_params() != statement.get_params() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "witness and statement parameters did not match", + }); } // Extract values for convenience @@ -199,10 +204,12 @@ impl TriptychProof { } if M_l != r * params.get_G() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "`M[l] != r * G`", + }); } if &(r * J) != params.get_U() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { reason: "`r * J != U`" }); } // Set up the transcript @@ -222,16 +229,23 @@ impl TriptychProof { } let A = params .commit_matrix(&a, &r_A, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `A`", + })?; // Compute the `B` matrix commitment let r_B = Scalar::random(transcript.as_mut_rng()); let l_decomposed = match timing { OperationTiming::Constant => { - GrayIterator::decompose(params.get_n(), params.get_m(), l).ok_or(ProofError::InvalidParameter)? + GrayIterator::decompose(params.get_n(), params.get_m(), l).ok_or(ProofError::InvalidParameter { + reason: "`l` decomposition failed", + })? }, - OperationTiming::Variable => GrayIterator::decompose_vartime(params.get_n(), params.get_m(), l) - .ok_or(ProofError::InvalidParameter)?, + OperationTiming::Variable => GrayIterator::decompose_vartime(params.get_n(), params.get_m(), l).ok_or( + ProofError::InvalidParameter { + reason: "`l` decomposition failed", + }, + )?, }; let sigma = (0..params.get_m()) .map(|j| { @@ -242,7 +256,9 @@ impl TriptychProof { .collect::>>(); let B = params .commit_matrix(&sigma, &r_B, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `B`", + })?; // Compute the `C` matrix commitment let two = Scalar::from(2u32); @@ -256,7 +272,9 @@ impl TriptychProof { .collect::>>(); let C = params .commit_matrix(&a_sigma, &r_C, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `C`", + })?; // Compute the `D` matrix commitment let r_D = Scalar::random(transcript.as_mut_rng()); @@ -269,7 +287,9 @@ impl TriptychProof { .collect::>>(); let D = params .commit_matrix(&a_square, &r_D, timing) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "unable to compute `D`", + })?; // Random masks let rho = Zeroizing::new( @@ -282,7 +302,9 @@ impl TriptychProof { let mut p = Vec::>::with_capacity(params.get_N() as usize); let mut k_decomposed = vec![0; params.get_m() as usize]; for (gray_index, _, gray_new) in - GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter)? + GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter { + reason: "coefficient decomposition failed", + })? { k_decomposed[gray_index] = gray_new; @@ -291,7 +313,9 @@ impl TriptychProof { coefficients.resize( (params.get_m() as usize) .checked_add(1) - .ok_or(ProofError::InvalidParameter)?, + .ok_or(ProofError::InvalidParameter { + reason: "polynomial degree overflowed", + })?, Scalar::ZERO, ); coefficients[0] = a[0][k_decomposed[0] as usize]; @@ -526,10 +550,14 @@ impl TriptychProof { ) -> Result<(), ProofError> { // Check that we have the same number of statements, proofs, and transcripts if statements.len() != proofs.len() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "number of statements and proof does not match", + }); } if statements.len() != transcripts.len() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "number of statements and transcripts does not match", + }); } // An empty batch is considered trivially valid @@ -540,12 +568,16 @@ impl TriptychProof { // Each statement must use the same input set (checked using the hash for efficiency) if !statements.iter().map(|s| s.get_input_set().get_hash()).all_equal() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "statement input sets do not match", + }); } // Each statement must use the same parameters (checked using the hash for efficiency) if !statements.iter().map(|s| s.get_params().get_hash()).all_equal() { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "statement parameters do not match", + }); } // Extract common values for convenience @@ -555,23 +587,37 @@ impl TriptychProof { // Check that all proof semantics are valid for the statement for proof in proofs { if proof.X.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `X` vector length was not `m`", + }); } if proof.Y.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `Y` vector length was not `m`", + }); } if proof.f.len() != params.get_m() as usize { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `f` matrix did not have `m` rows", + }); } for f_row in &proof.f { - if f_row.len() != params.get_n().checked_sub(1).ok_or(ProofError::InvalidParameter)? as usize { - return Err(ProofError::InvalidParameter); + if f_row.len() != + params.get_n().checked_sub(1).ok_or(ProofError::InvalidParameter { + reason: "proof `f` matrix column count overflowed", + })? as usize + { + return Err(ProofError::InvalidParameter { + reason: "proof `f` matrix did not have `n - 1` columns", + }); } } } // Determine the size of the final check vector, which must not overflow `usize` - let batch_size = u32::try_from(proofs.len()).map_err(|_| ProofError::InvalidParameter)?; + let batch_size = u32::try_from(proofs.len()).map_err(|_| ProofError::InvalidParameter { + reason: "batch size overflowed `u32`", + })?; // This is unlikely to overflow; even if it does, the only effect is unnecessary reallocation #[allow(clippy::arithmetic_side_effects)] @@ -587,7 +633,9 @@ impl TriptychProof { + 2 * params.get_m() // X, Y ), ) - .map_err(|_| ProofError::InvalidParameter)?; + .map_err(|_| ProofError::InvalidParameter { + reason: "multiscalar multiplication size overflowed `usize`", + })?; // Set up the point vector for the final check let points = proofs @@ -656,7 +704,9 @@ impl TriptychProof { // Check that `f` does not contain zero, which breaks batch inversion for f_row in &f { if f_row.contains(&Scalar::ZERO) { - return Err(ProofError::InvalidParameter); + return Err(ProofError::InvalidParameter { + reason: "proof `f` matrix contained 0", + }); } } @@ -717,7 +767,9 @@ impl TriptychProof { // Set up the initial `f` product and Gray iterator let mut f_product = f.iter().map(|f_row| f_row[0]).product::(); let gray_iterator = - GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter)?; + GrayIterator::new(params.get_n(), params.get_m()).ok_or(ProofError::InvalidParameter { + reason: "coefficient decomposition failed", + })?; // Invert each element of `f` for efficiency let mut f_inverse_flat = f.iter().flatten().copied().collect::>(); diff --git a/src/statement.rs b/src/statement.rs index 8f0f67e..7533f70 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -45,11 +45,15 @@ impl TriptychInputSet { // We cannot have the vector be too long if unpadded_size > params.get_N() as usize { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "unpadded size exceeded `N`", + }); } // Get the last element, which also ensures the vector is nonempty - let last = M.last().ok_or(StatementError::InvalidParameter)?; + let last = M + .last() + .ok_or(StatementError::InvalidParameter { reason: "`M` is empty" })?; // Pad the vector with the last element let mut M_padded = M.to_vec(); @@ -62,7 +66,9 @@ impl TriptychInputSet { #[allow(non_snake_case)] fn new_internal(M: &[RistrettoPoint], unpadded_size: usize) -> Result { // Ensure the verification key vector length doesn't overflow - let unpadded_size = u32::try_from(unpadded_size).map_err(|_| StatementError::InvalidParameter)?; + let unpadded_size = u32::try_from(unpadded_size).map_err(|_| StatementError::InvalidParameter { + reason: "unpadded size overflowed `u32`", + })?; // Use Merlin for the transcript hash let mut transcript = Transcript::new(Self::DOMAIN.as_bytes()); @@ -108,8 +114,11 @@ pub struct TriptychStatement { #[derive(Debug, Snafu)] pub enum StatementError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, } impl TriptychStatement { @@ -135,10 +144,14 @@ impl TriptychStatement { ) -> Result { // Check that the input vector is valid against the parameters if input_set.get_keys().len() != params.get_N() as usize { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "input vector length was not `N`", + }); } if input_set.get_keys().contains(&RistrettoPoint::identity()) { - return Err(StatementError::InvalidParameter); + return Err(StatementError::InvalidParameter { + reason: "input vector contained the identity point", + }); } // Use Merlin for the transcript hash diff --git a/src/transcript.rs b/src/transcript.rs index 3abdb3a..3b7ebc2 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -81,7 +81,9 @@ impl<'a, R: CryptoRngCore> ProofTranscript<'a, R> { let xi = Scalar::from_bytes_mod_order_wide(&xi_bytes); // Get powers of the challenge and confirm they are nonzero - let mut xi_powers = Vec::with_capacity(m.checked_add(1).ok_or(ProofError::InvalidParameter)?); + let mut xi_powers = Vec::with_capacity(m.checked_add(1).ok_or(ProofError::InvalidParameter { + reason: "challenge power count overflowed `usize`", + })?); let mut xi_power = Scalar::ONE; for _ in 0..=m { if xi_power == Scalar::ZERO { diff --git a/src/witness.rs b/src/witness.rs index f0b0c70..740bfbe 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -25,8 +25,11 @@ pub struct TriptychWitness { #[derive(Debug, Snafu)] pub enum WitnessError { /// An invalid parameter was provided. - #[snafu(display("An invalid parameter was provided"))] - InvalidParameter, + #[snafu(display("An invalid parameter was provided: {reason}"))] + InvalidParameter { + /// The reason for the parameter error. + reason: &'static str, + }, } impl TriptychWitness { @@ -39,10 +42,10 @@ impl TriptychWitness { #[allow(non_snake_case)] pub fn new(params: &TriptychParameters, l: u32, r: &Scalar) -> Result { if r == &Scalar::ZERO { - return Err(WitnessError::InvalidParameter); + return Err(WitnessError::InvalidParameter { reason: "`r == 0`" }); } if l >= params.get_N() { - return Err(WitnessError::InvalidParameter); + return Err(WitnessError::InvalidParameter { reason: "`l >= N`" }); } Ok(Self {