Document r1cs-std

This commit is contained in:
Pratyush Mishra
2020-09-10 01:16:01 -07:00
parent c21d0b2796
commit 370fbcdd3b
61 changed files with 691 additions and 211 deletions

View File

@@ -254,6 +254,10 @@ impl<F: Field> Boolean<F> {
/// Returns the constrant `false`.
pub const FALSE: Self = Boolean::Constant(false);
/// Constructs a `LinearCombination` from `Self`'s variables.
///
/// * `Boolean::Is(v) => lc!() + v.variable()`
/// * `Boolean::Not(v) => lc!() + Variable::One - v.variable()`
pub fn lc(&self) -> LinearCombination<F> {
match self {
Boolean::Constant(false) => lc!(),
@@ -263,7 +267,9 @@ impl<F: Field> Boolean<F> {
}
}
/// Construct a boolean vector from a vector of u8
/// Constructs a `Boolean` vector from a slice of constant `u8`.
///
/// This *does not* create any new variables.
pub fn constant_vec_from_bytes(values: &[u8]) -> Vec<Self> {
let mut input_bits = vec![];
for input_byte in values {
@@ -274,12 +280,12 @@ impl<F: Field> Boolean<F> {
input_bits
}
/// Construct a boolean from a known constant
/// Constructs a constant `Boolean` with value `b`.
pub fn constant(b: bool) -> Self {
Boolean::Constant(b)
}
/// Return a negated interpretation of this boolean.
/// Negates `self`.
pub fn not(&self) -> Self {
match *self {
Boolean::Constant(c) => Boolean::Constant(!c),
@@ -290,11 +296,14 @@ impl<F: Field> Boolean<F> {
}
impl<F: Field> Boolean<F> {
/// Perform XOR over two boolean operands
/// Outputs `self ^ other`.
///
/// If at least one of `self` and `other` are constants, then this method
/// *does not* create any constraints or variables.
#[tracing::instrument(target = "r1cs")]
pub fn xor<'a>(&'a self, b: &'a Self) -> Result<Self, SynthesisError> {
pub fn xor<'a>(&'a self, other: &'a Self) -> Result<Self, SynthesisError> {
use Boolean::*;
match (self, b) {
match (self, other) {
(&Constant(false), x) | (x, &Constant(false)) => Ok(x.clone()),
(&Constant(true), x) | (x, &Constant(true)) => Ok(x.not()),
// a XOR (NOT b) = NOT(a XOR b)
@@ -306,11 +315,14 @@ impl<F: Field> Boolean<F> {
}
}
/// Perform OR over two boolean operands
/// Outputs `self | other`.
///
/// If at least one of `self` and `other` are constants, then this method
/// *does not* create any constraints or variables.
#[tracing::instrument(target = "r1cs")]
pub fn or<'a>(&'a self, b: &'a Self) -> Result<Self, SynthesisError> {
pub fn or<'a>(&'a self, other: &'a Self) -> Result<Self, SynthesisError> {
use Boolean::*;
match (self, b) {
match (self, other) {
(&Constant(false), x) | (x, &Constant(false)) => Ok(x.clone()),
(&Constant(true), _) | (_, &Constant(true)) => Ok(Constant(true)),
// a OR b = NOT ((NOT a) AND b)
@@ -321,11 +333,14 @@ impl<F: Field> Boolean<F> {
}
}
/// Perform AND over two boolean operands
/// Outputs `self & other`.
///
/// If at least one of `self` and `other` are constants, then this method
/// *does not* create any constraints or variables.
#[tracing::instrument(target = "r1cs")]
pub fn and<'a>(&'a self, b: &'a Self) -> Result<Self, SynthesisError> {
pub fn and<'a>(&'a self, other: &'a Self) -> Result<Self, SynthesisError> {
use Boolean::*;
match (self, b) {
match (self, other) {
// false AND x is always false
(&Constant(false), _) | (_, &Constant(false)) => Ok(Constant(false)),
// true AND x is always x
@@ -339,6 +354,7 @@ impl<F: Field> Boolean<F> {
}
}
/// Outputs `bits[0] & bits[1] & ... & bits.last().unwrap()`.
#[tracing::instrument(target = "r1cs")]
pub fn kary_and(bits: &[Self]) -> Result<Self, SynthesisError> {
assert!(!bits.is_empty());
@@ -354,6 +370,7 @@ impl<F: Field> Boolean<F> {
Ok(cur.expect("should not be 0"))
}
/// Outputs `bits[0] | bits[1] | ... | bits.last().unwrap()`.
#[tracing::instrument(target = "r1cs")]
pub fn kary_or(bits: &[Self]) -> Result<Self, SynthesisError> {
assert!(!bits.is_empty());
@@ -369,12 +386,15 @@ impl<F: Field> Boolean<F> {
Ok(cur.expect("should not be 0"))
}
/// Outputs `(bits[0] & bits[1] & ... & bits.last().unwrap()).not()`.
#[tracing::instrument(target = "r1cs")]
pub fn kary_nand(bits: &[Self]) -> Result<Self, SynthesisError> {
Ok(Self::kary_and(bits)?.not())
}
/// Assert that at least one input is false.
/// Enforces that `Self::kary_nand(bits).is_eq(&Boolean::TRUE)`.
///
/// Informally, this means that at least one element in `bits` must be `false`.
#[tracing::instrument(target = "r1cs")]
fn enforce_kary_nand(bits: &[Self]) -> Result<(), SynthesisError> {
use Boolean::*;
@@ -392,7 +412,7 @@ impl<F: Field> Boolean<F> {
/// Enforces that `bits`, when interpreted as a integer, is less than `F::characteristic()`,
/// That is, interpret bits as a little-endian integer, and enforce that this integer
/// is "in the field F".
/// is "in the field Z_p", where `p = F::characteristic()` .
#[tracing::instrument(target = "r1cs")]
pub fn enforce_in_field_le(bits: &[Self]) -> Result<(), SynthesisError> {
// `bits` < F::characteristic() <==> `bits` <= F::characteristic() -1
@@ -466,6 +486,9 @@ impl<F: Field> Boolean<F> {
Ok(current_run)
}
/// 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`.
#[tracing::instrument(target = "r1cs", skip(first, second))]
pub fn select<T: CondSelectGadget<F>>(
&self,

View File

@@ -5,15 +5,20 @@ use crate::{
use algebra::Field;
use r1cs_core::SynthesisError;
/// This module contains `Boolean`, a R1CS equivalent of the `bool` type.
pub mod boolean;
/// This module contains `UInt8`, a R1CS equivalent of the `u8` type.
pub mod uint8;
/// This module contains a macro for generating `UIntN` types, which are R1CS equivalents of
/// `N`-bit unsigned integers.
#[macro_use]
pub mod uint;
make_uint!(UInt16, 16, u16, uint16);
make_uint!(UInt32, 32, u32, uint32);
make_uint!(UInt64, 64, u64, uint64);
make_uint!(UInt16, 16, u16, uint16, "16");
make_uint!(UInt32, 32, u32, uint32, "32");
make_uint!(UInt64, 64, u64, uint64, "64");
/// Specifies constraints for conversion to a little-endian bit representation of `self`.
pub trait ToBitsGadget<F: Field> {
/// Outputs the canonical little-endian bit-wise representation of `self`.
///
@@ -70,6 +75,7 @@ where
}
}
/// Specifies constraints for conversion to a little-endian byte representation of `self`.
pub trait ToBytesGadget<F: Field> {
/// Outputs a canonical, little-endian, byte decomposition of `self`.
///

View File

@@ -1,5 +1,10 @@
macro_rules! make_uint {
($name:ident, $size:expr, $native:ident, $mod_name:ident) => {
($name:ident, $size:expr, $native:ident, $mod_name:ident, $native_doc_name:expr) => {
#[doc = "This module contains a `UInt"]
#[doc = $native_doc_name]
#[doc = "`, a R1CS equivalent of the `u"]
#[doc = $native_doc_name]
#[doc = "`type."]
pub mod $mod_name {
use algebra::{Field, FpParameters, PrimeField};
use core::borrow::Borrow;
@@ -15,8 +20,14 @@ macro_rules! make_uint {
Assignment, Vec,
};
/// Represents an interpretation of `Boolean` objects as an
/// unsigned integer.
#[doc = "This struct represent an unsigned"]
#[doc = $native_doc_name]
#[doc = "-bit integer as a sequence of "]
#[doc = $native_doc_name]
#[doc = " `Boolean`s\n"]
#[doc = "This is the R1CS equivalent of the native `u"]
#[doc = $native_doc_name]
#[doc = "` unsigned integer type."]
#[derive(Clone, Debug)]
pub struct $name<F: Field> {
// Least significant bit first
@@ -46,7 +57,11 @@ macro_rules! make_uint {
}
impl<F: Field> $name<F> {
/// Construct a constant `$name` from a `$native`
#[doc = "Construct a constant `UInt"]
#[doc = $native_doc_name]
#[doc = "` from the native `u"]
#[doc = $native_doc_name]
#[doc = "` type."]
pub fn constant(value: $native) -> Self {
let mut bits = Vec::with_capacity($size);
@@ -67,13 +82,18 @@ macro_rules! make_uint {
}
}
/// Turns this `$name` into its little-endian byte order representation.
/// Turns `self` into the underlying little-endian bits.
pub fn to_bits_le(&self) -> Vec<Boolean<F>> {
self.bits.clone()
}
/// Converts a little-endian byte order representation of bits into a
/// `$name`.
/// Construct `Self` from a slice of `Boolean`s.
///
/// # Panics
///
/// This method panics if `bits.len() != u
#[doc($native_doc_name)]
#[doc("`.")]
pub fn from_bits_le(bits: &[Boolean<F>]) -> Self {
assert_eq!(bits.len(), $size);
@@ -105,6 +125,7 @@ macro_rules! make_uint {
Self { value, bits }
}
/// Rotates `self` to the right by `by` steps, wrapping around.
#[tracing::instrument(target = "r1cs", skip(self))]
pub fn rotr(&self, by: usize) -> Self {
let by = by % $size;
@@ -126,8 +147,11 @@ macro_rules! make_uint {
}
}
/// XOR this `$name` with another `$name`
#[tracing::instrument(target = "r1cs", skip(self))]
/// Outputs `self ^ other`.
///
/// If at least one of `self` and `other` are constants, then this method
/// *does not* create any constraints or variables.
#[tracing::instrument(target = "r1cs", skip(self, other))]
pub fn xor(&self, other: &Self) -> Result<Self, SynthesisError> {
let new_value = match (self.value, other.value) {
(Some(a), Some(b)) => Some(a ^ b),
@@ -147,8 +171,10 @@ macro_rules! make_uint {
})
}
/// Perform modular addition of several `$name` objects.
#[tracing::instrument(target = "r1cs")]
/// Perform modular addition of `operands`.
///
/// The user must ensure that overflow does not occur.
#[tracing::instrument(target = "r1cs", skip(operands))]
pub fn addmany(operands: &[Self]) -> Result<Self, SynthesisError>
where
F: PrimeField,

View File

@@ -64,6 +64,7 @@ impl<F: Field> UInt8<F> {
}
}
/// Allocates a slice of `u8`'s as private witnesses.
pub fn new_witness_vec(
cs: impl Into<Namespace<F>>,
values: &[impl Into<Option<u8>> + Copy],
@@ -78,10 +79,13 @@ impl<F: Field> UInt8<F> {
Ok(output_vec)
}
/// Allocates a vector of `u8`'s by first converting (chunks of) them to
/// `ConstraintF` elements, (thus reducing the number of input allocations),
/// and then converts this list of `ConstraintF` gadgets back into
/// 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<F>` variables back into
/// bytes.
///
/// From a user perspective, this trade-off adds constraints, but improves
/// verifier time and verification key size.
pub fn new_input_vec(
cs: impl Into<Namespace<F>>,
values: &[u8],
@@ -134,7 +138,10 @@ impl<F: Field> UInt8<F> {
Self { value, bits }
}
/// XOR this `UInt8` with another `UInt8`
/// Outputs `self ^ other`.
///
/// If at least one of `self` and `other` are constants, then this method
/// *does not* create any constraints or variables.
#[tracing::instrument(target = "r1cs")]
pub fn xor(&self, other: &Self) -> Result<Self, SynthesisError> {
let new_value = match (self.value, other.value) {