From 13332bcc1d0594501d825306b5defa4a2aa6f99b Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Wed, 16 Sep 2020 20:51:48 -0700 Subject: [PATCH] Add doctests for `Boolean` and `UInt8`. --- r1cs-std/src/alloc.rs | 12 +- r1cs-std/src/bits/boolean.rs | 257 ++++++++++++++++++++++++++++++++--- r1cs-std/src/bits/mod.rs | 2 +- r1cs-std/src/bits/uint8.rs | 104 +++++++++++++- r1cs-std/src/lib.rs | 7 +- 5 files changed, 351 insertions(+), 31 deletions(-) diff --git a/r1cs-std/src/alloc.rs b/r1cs-std/src/alloc.rs index f07b29e..8a5a870 100644 --- a/r1cs-std/src/alloc.rs +++ b/r1cs-std/src/alloc.rs @@ -40,7 +40,7 @@ where Self: Sized, V: ?Sized, { - /// Allocates a new variable of type `Self` in the `ConstraintSystem`. + /// Allocates a new variable of type `Self` in the `ConstraintSystem` `cs`. /// The mode of allocation is decided by `mode`. fn new_variable>( cs: impl Into>, @@ -48,7 +48,9 @@ where mode: AllocationMode, ) -> Result; - /// Allocates a new constant of type `Self` in the `ConstraintSystem`. + /// Allocates a new constant of type `Self` in the `ConstraintSystem` `cs`. + /// + /// This should *not* allocate any new variables or constraints in `cs`. #[tracing::instrument(target = "r1cs", skip(cs, t))] fn new_constant( cs: impl Into>, @@ -57,7 +59,7 @@ where Self::new_variable(cs, || Ok(t), AllocationMode::Constant) } - /// Allocates a new public input of type `Self` in the `ConstraintSystem`. + /// Allocates a new public input of type `Self` in the `ConstraintSystem` `cs`. #[tracing::instrument(target = "r1cs", skip(cs, f))] fn new_input>( cs: impl Into>, @@ -66,7 +68,7 @@ where Self::new_variable(cs, f, AllocationMode::Input) } - /// Allocates a new private witness of type `Self` in the `ConstraintSystem`. + /// Allocates a new private witness of type `Self` in the `ConstraintSystem` `cs`. #[tracing::instrument(target = "r1cs", skip(cs, f))] fn new_witness>( cs: impl Into>, @@ -76,6 +78,8 @@ where } } +/// This blanket implementation just allocates variables in `Self` +/// element by element. impl> AllocVar<[I], F> for Vec { fn new_variable>( cs: impl Into>, diff --git a/r1cs-std/src/bits/boolean.rs b/r1cs-std/src/bits/boolean.rs index f72ca36..995243a 100644 --- a/r1cs-std/src/bits/boolean.rs +++ b/r1cs-std/src/bits/boolean.rs @@ -6,6 +6,10 @@ use r1cs_core::{lc, ConstraintSystemRef, LinearCombination, Namespace, Synthesis /// Represents a variable in the constraint system which is guaranteed /// to be either zero or one. +/// +/// In general, one should prefer using `Boolean` instead of `AllocatedBit`, +/// as `Boolean` offers better support for constant values, and implements +/// more traits. #[derive(Clone, Debug, Eq, PartialEq)] #[must_use] pub struct AllocatedBit { @@ -156,10 +160,7 @@ impl AllocatedBit { } impl AllocVar for AllocatedBit { - /// If `self.mode` == `AllocationMode::Constant`, this method simply outputs - /// a `Boolean::Constant`. - /// - /// Otherwise, it produces a new variable of the appropriate type + /// Produces a new variable of the appropriate kind /// (instance or witness), with a booleanity check. /// /// N.B.: we could omit the booleanity check when allocating `self` @@ -215,16 +216,16 @@ impl CondSelectGadget for AllocatedBit { } } -/// This is a boolean value which may be either a constant or -/// an interpretation of an `AllocatedBit`. +/// Represents a boolean value in the constraint system which is guaranteed +/// to be either zero or one. #[derive(Clone, Debug, Eq, PartialEq)] #[must_use] pub enum Boolean { - /// Existential view of the boolean variable + /// Existential view of the boolean variable. Is(AllocatedBit), - /// Negated view of the boolean variable + /// Negated view of the boolean variable. Not(AllocatedBit), - /// Constant (not an allocated variable) + /// Constant (not an allocated variable). Constant(bool), } @@ -248,14 +249,17 @@ impl R1CSVar for Boolean { } impl Boolean { - /// Returns the constrant `true`. + /// The constant `true`. pub const TRUE: Self = Boolean::Constant(true); - /// Returns the constrant `false`. + /// The constant `false`. pub const FALSE: Self = Boolean::Constant(false); - /// Constructs a `LinearCombination` from `Self`'s variables. + /// Constructs a `LinearCombination` from `Self`'s variables according + /// to the following map. /// + /// * `Boolean::Constant(true) => lc!() + Variable::One` + /// * `Boolean::Constant(false) => lc!()` /// * `Boolean::Is(v) => lc!() + v.variable()` /// * `Boolean::Not(v) => lc!() + Variable::One - v.variable()` pub fn lc(&self) -> LinearCombination { @@ -268,24 +272,84 @@ impl Boolean { } /// Constructs a `Boolean` vector from a slice of constant `u8`. + /// The `u8`s are decomposed in little-endian manner. + /// + /// This *does not* create any new variables or constraints. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; /// - /// This *does not* create any new variables. + /// let cs = ConstraintSystem::::new_ref(); + /// let t = Boolean::::TRUE; + /// let f = Boolean::::FALSE; + /// + /// let bits = vec![f, t]; + /// let generated_bits = Boolean::constant_vec_from_bytes(&[2]); + /// bits[..2].enforce_equal(&generated_bits[..2])?; + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` pub fn constant_vec_from_bytes(values: &[u8]) -> Vec { - let mut input_bits = vec![]; - for input_byte in values { - for bit_i in (0..8).rev() { - input_bits.push(Self::Constant(((input_byte >> bit_i) & 1u8) == 1u8)); + let mut bits = vec![]; + for byte in values { + for i in 0..8 { + bits.push(Self::Constant(((byte >> i) & 1u8) == 1u8)); } } - input_bits + bits } /// Constructs a constant `Boolean` with value `b`. + /// + /// This *does not* create any new variables or constraints. + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_std::prelude::*; + /// + /// let true_var = Boolean::::TRUE; + /// let false_var = Boolean::::FALSE; + /// + /// true_var.enforce_equal(&Boolean::constant(true))?; + /// false_var.enforce_equal(&Boolean::constant(false))?; + /// # Ok(()) + /// # } + /// ``` pub fn constant(b: bool) -> Self { Boolean::Constant(b) } /// Negates `self`. + /// + /// This *does not* create any new variables or constraints. + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// + /// a.not().enforce_equal(&b)?; + /// b.not().enforce_equal(&a)?; + /// + /// a.not().enforce_equal(&Boolean::FALSE)?; + /// b.not().enforce_equal(&Boolean::TRUE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` pub fn not(&self) -> Self { match *self { Boolean::Constant(c) => Boolean::Constant(!c), @@ -293,13 +357,34 @@ impl Boolean { Boolean::Not(ref v) => Boolean::Is(v.clone()), } } -} -impl Boolean { /// Outputs `self ^ other`. /// /// If at least one of `self` and `other` are constants, then this method /// *does not* create any constraints or variables. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// + /// a.xor(&b)?.enforce_equal(&Boolean::TRUE)?; + /// b.xor(&a)?.enforce_equal(&Boolean::TRUE)?; + /// + /// a.xor(&a)?.enforce_equal(&Boolean::FALSE)?; + /// b.xor(&b)?.enforce_equal(&Boolean::FALSE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn xor<'a>(&'a self, other: &'a Self) -> Result { use Boolean::*; @@ -319,6 +404,29 @@ impl Boolean { /// /// If at least one of `self` and `other` are constants, then this method /// *does not* create any constraints or variables. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// + /// a.or(&b)?.enforce_equal(&Boolean::TRUE)?; + /// b.or(&a)?.enforce_equal(&Boolean::TRUE)?; + /// + /// a.or(&a)?.enforce_equal(&Boolean::TRUE)?; + /// b.or(&b)?.enforce_equal(&Boolean::FALSE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn or<'a>(&'a self, other: &'a Self) -> Result { use Boolean::*; @@ -337,6 +445,29 @@ impl Boolean { /// /// If at least one of `self` and `other` are constants, then this method /// *does not* create any constraints or variables. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// + /// a.and(&a)?.enforce_equal(&Boolean::TRUE)?; + /// + /// a.and(&b)?.enforce_equal(&Boolean::FALSE)?; + /// b.and(&a)?.enforce_equal(&Boolean::FALSE)?; + /// b.and(&b)?.enforce_equal(&Boolean::FALSE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn and<'a>(&'a self, other: &'a Self) -> Result { use Boolean::*; @@ -355,6 +486,27 @@ impl Boolean { } /// Outputs `bits[0] & bits[1] & ... & bits.last().unwrap()`. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// let c = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// + /// Boolean::kary_and(&[a.clone(), b.clone(), c.clone()])?.enforce_equal(&Boolean::FALSE)?; + /// Boolean::kary_and(&[a.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn kary_and(bits: &[Self]) -> Result { assert!(!bits.is_empty()); @@ -371,6 +523,28 @@ impl Boolean { } /// Outputs `bits[0] | bits[1] | ... | bits.last().unwrap()`. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// let c = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// + /// Boolean::kary_or(&[a.clone(), b.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; + /// Boolean::kary_or(&[a.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; + /// Boolean::kary_or(&[b.clone(), c.clone()])?.enforce_equal(&Boolean::FALSE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn kary_or(bits: &[Self]) -> Result { assert!(!bits.is_empty()); @@ -387,6 +561,28 @@ impl Boolean { } /// Outputs `(bits[0] & bits[1] & ... & bits.last().unwrap()).not()`. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// let c = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// + /// Boolean::kary_nand(&[a.clone(), b.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; + /// Boolean::kary_nand(&[a.clone(), c.clone()])?.enforce_equal(&Boolean::FALSE)?; + /// Boolean::kary_nand(&[b.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn kary_nand(bits: &[Self]) -> Result { Ok(Self::kary_and(bits)?.not()) @@ -489,6 +685,27 @@ impl Boolean { /// Conditionally selects one of `first` and `second` based on the value of `self`: /// /// If `self.is_eq(&Boolean::TRUE)`, this outputs `first`; else, it outputs `second`. + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// + /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; + /// + /// let cond = Boolean::new_witness(cs.clone(), || Ok(true))?; + /// + /// cond.select(&a, &b)?.enforce_equal(&Boolean::TRUE)?; + /// cond.select(&b, &a)?.enforce_equal(&Boolean::FALSE)?; + /// + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs", skip(first, second))] pub fn select>( &self, diff --git a/r1cs-std/src/bits/mod.rs b/r1cs-std/src/bits/mod.rs index 179b36a..03b10c9 100644 --- a/r1cs-std/src/bits/mod.rs +++ b/r1cs-std/src/bits/mod.rs @@ -28,7 +28,7 @@ pub trait ToBitsGadget { /// Outputs a possibly non-unique bit-wise representation of `self`. /// /// If you're not absolutely certain that your usecase can get away with a - /// non-canonical representation, please use `self.to_bits(cs)` instead. + /// non-canonical representation, please use `self.to_bits()` instead. fn to_non_unique_bits_le(&self) -> Result>, SynthesisError> { self.to_bits_le() } diff --git a/r1cs-std/src/bits/uint8.rs b/r1cs-std/src/bits/uint8.rs index 28b22f6..7b2676e 100644 --- a/r1cs-std/src/bits/uint8.rs +++ b/r1cs-std/src/bits/uint8.rs @@ -12,7 +12,6 @@ use core::borrow::Borrow; pub struct UInt8 { /// Little-endian representation: least significant bit first pub(crate) bits: Vec>, - /// Little-endian representation: least significant bit first pub(crate) value: Option, } @@ -39,6 +38,24 @@ impl R1CSVar for UInt8 { impl UInt8 { /// Construct a constant vector of `UInt8` from a vector of `u8` + /// + /// This *does not* create any new variables or constraints. + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// let var = vec![UInt8::new_witness(cs.clone(), || Ok(2))?]; + /// + /// let constant = UInt8::constant_vec(&[2]); + /// var.enforce_equal(&constant)?; + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` pub fn constant_vec(values: &[u8]) -> Vec { let mut result = Vec::new(); for value in values { @@ -48,6 +65,25 @@ impl UInt8 { } /// Construct a constant `UInt8` from a `u8` + /// + /// This *does not* create new variables or constraints. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// let var = UInt8::new_witness(cs.clone(), || Ok(2))?; + /// + /// let constant = UInt8::constant(2); + /// var.enforce_equal(&constant)?; + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` pub fn constant(value: u8) -> Self { let mut bits = Vec::with_capacity(8); @@ -80,12 +116,30 @@ impl UInt8 { } /// Allocates a slice of `u8`'s as public inputs by first packing them into - /// `F` elements, (thus reducing the number of input allocations), - /// and then converts this list of `AllocatedFp` variables back into - /// bytes. + /// elements of `F`, (thus reducing the number of input allocations), allocating + /// these elements as public inputs, and then converting these field variables + /// `FpVar` variables back into bytes. /// /// From a user perspective, this trade-off adds constraints, but improves /// verifier time and verification key size. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// let two = UInt8::new_witness(cs.clone(), || Ok(2))?; + /// let var = vec![two.clone(); 32]; + /// + /// let c = UInt8::new_input_vec(cs.clone(), &[2; 32])?; + /// var.enforce_equal(&c)?; + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` pub fn new_input_vec( cs: impl Into>, values: &[u8], @@ -121,6 +175,30 @@ impl UInt8 { /// Converts a little-endian byte order representation of bits into a /// `UInt8`. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// let var = UInt8::new_witness(cs.clone(), || Ok(128))?; + /// + /// let f = Boolean::FALSE; + /// let t = Boolean::TRUE; + /// + /// // Construct [0, 0, 0, 0, 0, 0, 0, 1] + /// let mut bits = vec![f.clone(); 7]; + /// bits.push(t); + /// + /// let mut c = UInt8::from_bits_le(&bits); + /// var.enforce_equal(&c)?; + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn from_bits_le(bits: &[Boolean]) -> Self { assert_eq!(bits.len(), 8); @@ -142,6 +220,24 @@ impl UInt8 { /// /// If at least one of `self` and `other` are constants, then this method /// *does not* create any constraints or variables. + /// + /// ``` + /// # fn main() -> Result<(), r1cs_core::SynthesisError> { + /// // We'll use the BLS12-381 scalar field for our constraints. + /// use algebra::bls12_381::Fr; + /// use r1cs_core::*; + /// use r1cs_std::prelude::*; + /// + /// let cs = ConstraintSystem::::new_ref(); + /// let a = UInt8::new_witness(cs.clone(), || Ok(16))?; + /// let b = UInt8::new_witness(cs.clone(), || Ok(17))?; + /// let c = UInt8::new_witness(cs.clone(), || Ok(1))?; + /// + /// a.xor(&b)?.enforce_equal(&c)?; + /// assert!(cs.is_satisfied().unwrap()); + /// # Ok(()) + /// # } + /// ``` #[tracing::instrument(target = "r1cs")] pub fn xor(&self, other: &Self) -> Result { let new_value = match (self.value, other.value) { diff --git a/r1cs-std/src/lib.rs b/r1cs-std/src/lib.rs index 73bbe94..27bae96 100644 --- a/r1cs-std/src/lib.rs +++ b/r1cs-std/src/lib.rs @@ -112,7 +112,8 @@ pub mod prelude { }; } -/// This trait describes some core functionality that is common to high-level variables, such as `Boolean`s, `FieldVar`s, `GroupVar`s, etc. +/// This trait describes some core functionality that is common to high-level variables, +/// such as `Boolean`s, `FieldVar`s, `GroupVar`s, etc. pub trait R1CSVar { /// The type of the "native" value that `Self` represents in the constraint system. type Value: core::fmt::Debug + Eq + Clone; @@ -175,8 +176,10 @@ impl Assignment for Option { } } -/// Obtains the field variables +/// Specifies how to convert a variable of type `Self` to variables of +/// type `FpVar` pub trait ToConstraintFieldGadget { + /// Converts `self` to `FpVar` variables. fn to_constraint_field( &self, ) -> Result>, r1cs_core::SynthesisError>;