diff --git a/CHANGELOG.md b/CHANGELOG.md index 45926e51..e4d40d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added more tests for low coverage parts of the code [#861] - Added MSRV, set to rust version `1.85` [#860] ### Changed @@ -625,6 +626,7 @@ is necessary since `rkyv/validation` was required as a bound. - Proof system module. +[#862]: https://github.com/dusk-network/plonk/issues/862 [#861]: https://github.com/dusk-network/plonk/issues/861 [#860]: https://github.com/dusk-network/plonk/issues/860 [#859]: https://github.com/dusk-network/plonk/issues/859 diff --git a/src/commitment_scheme/kzg10/commitment.rs b/src/commitment_scheme/kzg10/commitment.rs index 7b1aae0d..e86bcd82 100644 --- a/src/commitment_scheme/kzg10/commitment.rs +++ b/src/commitment_scheme/kzg10/commitment.rs @@ -82,4 +82,23 @@ mod commitment_tests { .expect("Error on the deserialization"); assert_eq!(commitment, obtained_comm); } + + #[test] + fn commitment_default_is_identity() { + let c = Commitment::default(); + assert_eq!(c, Commitment(G1Affine::identity())); + + let bytes = c.to_bytes(); + let obtained = Commitment::from_slice(&bytes) + .expect("identity commitment should deserialize"); + assert_eq!(c, obtained); + } + + #[test] + fn commitment_from_projective_matches_affine() { + let projective = G1Projective::generator(); + let from_proj: Commitment = projective.into(); + let from_aff: Commitment = G1Affine::generator().into(); + assert_eq!(from_proj, from_aff); + } } diff --git a/src/commitment_scheme/kzg10/proof.rs b/src/commitment_scheme/kzg10/proof.rs index 58fffb63..47d60099 100644 --- a/src/commitment_scheme/kzg10/proof.rs +++ b/src/commitment_scheme/kzg10/proof.rs @@ -108,3 +108,50 @@ pub(crate) mod alloc { } } } + +#[cfg(all(test, feature = "alloc"))] +mod tests { + use super::*; + use dusk_bls12_381::{G1Affine, G1Projective}; + + #[test] + fn aggregate_proof_flatten_is_linear_combination() { + // Build an aggregate proof with 3 parts. + let witness_commitment: Commitment = G1Affine::generator().into(); + let mut agg = alloc::AggregateProof::with_witness(witness_commitment); + + let c0: Commitment = + (G1Projective::generator() * BlsScalar::from(2u64)).into(); + let c1: Commitment = + (G1Projective::generator() * BlsScalar::from(3u64)).into(); + let c2: Commitment = + (G1Projective::generator() * BlsScalar::from(5u64)).into(); + + let e0 = BlsScalar::from(11u64); + let e1 = BlsScalar::from(13u64); + let e2 = BlsScalar::from(17u64); + + agg.add_part((e0, c0)); + agg.add_part((e1, c1)); + agg.add_part((e2, c2)); + + let v = BlsScalar::from(7u64); + + let proof = agg.flatten(&v); + + // commitment_to_witness is left unchanged. + assert_eq!(proof.commitment_to_witness, witness_commitment); + + let powers = crate::util::powers_of(&v, 2); + assert_eq!(powers.len(), 3); + + let expected_eval = e0 * powers[0] + e1 * powers[1] + e2 * powers[2]; + assert_eq!(proof.evaluated_point, expected_eval); + + let expected_commitment_proj: G1Projective = + c0.0 * &powers[0] + c1.0 * &powers[1] + c2.0 * &powers[2]; + let expected_commitment: Commitment = expected_commitment_proj.into(); + + assert_eq!(proof.commitment_to_polynomial, expected_commitment); + } +} diff --git a/src/composer/constraint_system/witness.rs b/src/composer/constraint_system/witness.rs index e24b51c7..7b9dceaa 100644 --- a/src/composer/constraint_system/witness.rs +++ b/src/composer/constraint_system/witness.rs @@ -55,3 +55,38 @@ impl Witness { #[cfg(feature = "zeroize")] impl zeroize::DefaultIsZeroes for Witness {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn witness_constants_and_default_are_consistent() { + assert_eq!(Witness::ZERO.index(), 0); + assert_eq!(Witness::ONE.index(), 1); + + // `Default` is implemented as `Witness::ZERO`. + assert_eq!(Witness::default(), Witness::ZERO); + } + + #[test] + fn wire_data_variants_are_distinct_and_debuggable() { + let a = WireData::Left(0); + let b = WireData::Right(0); + let c = WireData::Output(0); + let d = WireData::Fourth(0); + + assert_ne!(a, b); + assert_ne!(b, c); + assert_ne!(c, d); + + // Debug fmt should not panic. + let _ = format!("{a:?}{b:?}{c:?}{d:?}"); + + // Pattern match to make sure we can extract indices. + match a { + WireData::Left(i) => assert_eq!(i, 0), + _ => panic!("expected Left"), + } + } +} diff --git a/src/composer/gate.rs b/src/composer/gate.rs index a47069e2..e6d56fdd 100644 --- a/src/composer/gate.rs +++ b/src/composer/gate.rs @@ -44,3 +44,68 @@ pub struct Gate { /// Fourth wire witness. pub(crate) d: Witness, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gate_is_copy_clone_and_eq() { + let gate = Gate { + q_m: BlsScalar::from(1u64), + q_l: BlsScalar::from(2u64), + q_r: BlsScalar::from(3u64), + q_o: BlsScalar::from(4u64), + q_f: BlsScalar::from(5u64), + q_c: BlsScalar::from(6u64), + q_arith: BlsScalar::one(), + q_range: BlsScalar::zero(), + q_logic: BlsScalar::zero(), + q_fixed_group_add: BlsScalar::zero(), + q_variable_group_add: BlsScalar::zero(), + a: Witness::ZERO, + b: Witness::ONE, + c: Witness::new(2), + d: Witness::new(3), + }; + + // Copy + let gate_copy = gate; + assert_eq!(gate, gate_copy); + + // Clone + let gate_clone = gate_copy.clone(); + assert_eq!(gate_copy, gate_clone); + + // Debug fmt should not panic + let _ = format!("{gate_clone:?}"); + } + + #[test] + fn gate_partial_eq_compares_fields() { + let mut a = Gate { + q_m: BlsScalar::from(1u64), + q_l: BlsScalar::from(2u64), + q_r: BlsScalar::from(3u64), + q_o: BlsScalar::from(4u64), + q_f: BlsScalar::from(5u64), + q_c: BlsScalar::from(6u64), + q_arith: BlsScalar::one(), + q_range: BlsScalar::zero(), + q_logic: BlsScalar::zero(), + q_fixed_group_add: BlsScalar::zero(), + q_variable_group_add: BlsScalar::zero(), + a: Witness::ZERO, + b: Witness::ONE, + c: Witness::new(2), + d: Witness::new(3), + }; + + let mut b = a; + assert_eq!(a, b); + + // Flip one field and ensure inequality. + b.q_c = BlsScalar::from(7u64); + assert_ne!(a, b); + } +} diff --git a/src/error.rs b/src/error.rs index e3377d8b..c7188275 100644 --- a/src/error.rs +++ b/src/error.rs @@ -188,3 +188,70 @@ impl From for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + use dusk_bls12_381::BlsScalar; + use dusk_bytes::{DeserializableSlice, Serializable}; + + #[test] + fn display_arms_are_exercised() { + let bogus = [0u8; BlsScalar::SIZE - 1]; + let dusk_err = BlsScalar::from_slice(&bogus) + .expect_err("decoding from a short slice must fail"); + let bytes_error: Error = dusk_err.into(); + assert!(matches!(bytes_error, Error::BytesError(_))); + + // Format each variant at least once so the `Display` impl gets covered. + let all_errors: [Error; 20] = [ + Error::InvalidEvalDomainSize { + log_size_of_group: 32, + adacity: 28, + }, + Error::ProofVerificationError, + Error::CircuitInputsNotFound, + Error::UninitializedPIGenerator, + Error::InvalidPublicInputBytes, + Error::CircuitAlreadyPreprocessed, + Error::InvalidCircuitSize(1, 2), + Error::MismatchedPolyLen, + Error::DegreeIsZero, + Error::TruncatedDegreeTooLarge, + Error::TruncatedDegreeIsZero, + Error::PolynomialDegreeTooLarge, + Error::PolynomialDegreeIsZero, + Error::PairingCheckFailure, + Error::NotEnoughBytes, + Error::PointMalformed, + Error::BlsScalarMalformed, + Error::JubJubScalarMalformed, + Error::UnsupportedWNAF2k, + Error::InvalidCompressedCircuit, + ]; + + for e in all_errors { + let s = e.to_string(); + assert!(!s.is_empty()); + } + + // Variants with payloads. + assert!( + Error::PublicInputNotFound { index: 7 } + .to_string() + .contains("index") + ); + assert!( + Error::InconsistentPublicInputsLen { + expected: 1, + provided: 2 + } + .to_string() + .contains("provided") + ); + + assert!(!bytes_error.to_string().is_empty()); + let _as_std_error: &dyn std::error::Error = + &Error::ProofVerificationError; + } +} diff --git a/src/fft/evaluations.rs b/src/fft/evaluations.rs index 3e5bde22..0653fb81 100644 --- a/src/fft/evaluations.rs +++ b/src/fft/evaluations.rs @@ -166,3 +166,132 @@ impl<'a> DivAssign<&'a Evaluations> for Evaluations { .for_each(|(a, b)| *a *= b.invert().unwrap()); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::fft::domain::EvaluationDomain; + use crate::fft::polynomial::Polynomial; + + #[test] + fn evaluations_var_bytes_roundtrip() { + let poly = Polynomial::from_coefficients_vec(vec![ + BlsScalar::from(1u64), + BlsScalar::from(2u64), + BlsScalar::from(3u64), + BlsScalar::from(4u64), + ]); + + let domain = EvaluationDomain::new(poly.len()) + .expect("domain construction should succeed"); + let evals = domain.fft(&poly); + + let evaluations = Evaluations::from_vec_and_domain(evals, domain); + let bytes = evaluations.to_var_bytes(); + + let decoded = Evaluations::from_slice(&bytes) + .expect("decoding evaluations should succeed"); + assert_eq!(evaluations, decoded); + } + + #[test] + fn evaluations_interpolate_roundtrip() { + let poly = Polynomial::from_coefficients_vec(vec![ + BlsScalar::from(7u64), + BlsScalar::from(0u64), + BlsScalar::from(5u64), + ]); + + let domain = EvaluationDomain::new(poly.len()) + .expect("domain construction should succeed"); + let evals = domain.fft(&poly); + let evaluations = Evaluations::from_vec_and_domain(evals, domain); + + let recovered = evaluations.clone().interpolate(); + assert_eq!(recovered, poly); + } + + #[test] + fn evaluations_arithmetic_is_element_wise() { + let domain = EvaluationDomain::new(4) + .expect("domain construction should succeed"); + + let a = Evaluations::from_vec_and_domain( + vec![ + BlsScalar::from(1u64), + BlsScalar::from(2u64), + BlsScalar::from(3u64), + BlsScalar::from(4u64), + ], + domain, + ); + let b = Evaluations::from_vec_and_domain( + vec![ + BlsScalar::from(5u64), + BlsScalar::from(6u64), + BlsScalar::from(7u64), + BlsScalar::from(8u64), + ], + domain, + ); + + // Indexing + assert_eq!(a[2], BlsScalar::from(3u64)); + + let add = &a + &b; + assert_eq!( + add.evals, + vec![ + BlsScalar::from(6u64), + BlsScalar::from(8u64), + BlsScalar::from(10u64), + BlsScalar::from(12u64) + ] + ); + + let sub = &b - &a; + assert_eq!( + sub.evals, + vec![ + BlsScalar::from(4u64), + BlsScalar::from(4u64), + BlsScalar::from(4u64), + BlsScalar::from(4u64) + ] + ); + + let mul = &a * &b; + assert_eq!( + mul.evals, + vec![ + BlsScalar::from(5u64), + BlsScalar::from(12u64), + BlsScalar::from(21u64), + BlsScalar::from(32u64) + ] + ); + + let mut div = b.clone(); + div /= &a; + assert_eq!(div.evals[0], BlsScalar::from(5u64)); + assert_eq!(div.evals[1], BlsScalar::from(3u64)); + } + + #[test] + #[should_panic(expected = "domains are unequal")] + fn operations_panic_on_domain_mismatch() { + let domain_a = EvaluationDomain::new(4).unwrap(); + let domain_b = EvaluationDomain::new(8).unwrap(); + + let a = Evaluations::from_vec_and_domain( + vec![BlsScalar::one(); domain_a.size()], + domain_a, + ); + let b = Evaluations::from_vec_and_domain( + vec![BlsScalar::one(); domain_b.size()], + domain_b, + ); + + let _ = &a + &b; + } +} diff --git a/src/runtime.rs b/src/runtime.rs index 743a21f6..349f301a 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -64,3 +64,27 @@ impl Runtime { self.debugger.event(event); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn runtime_new_default_and_events_do_not_panic() { + let mut rt = Runtime::new(); + + rt.event(RuntimeEvent::WitnessAppended { + w: Witness::ZERO, + v: BlsScalar::from(42u64), + }); + + rt.event(RuntimeEvent::ConstraintAppended { + c: Constraint::new(), + }); + rt.event(RuntimeEvent::ProofFinished); + + // `Default` delegates to `new()`. + let mut rt2 = Runtime::default(); + rt2.event(RuntimeEvent::ProofFinished); + } +}