use algebra::Field; use algebra::{FpParameters, PrimeField, ToConstraintField}; use r1cs_core::{ConstraintSystemRef, Namespace, SynthesisError}; use crate::{fields::fp::AllocatedFp, prelude::*, Assignment, Vec}; use core::borrow::Borrow; /// Represents an interpretation of 8 `Boolean` objects as an /// unsigned integer. #[derive(Clone, Debug)] 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, } impl R1CSVar for UInt8 { type Value = u8; fn cs(&self) -> Option> { self.bits.as_slice().cs() } fn value(&self) -> Result { let mut value = None; for (i, bit) in self.bits.iter().enumerate() { let b = u8::from(bit.value()?); value = match value { Some(value) => Some(value + (b << i)), None => Some(b << i), }; } debug_assert_eq!(self.value, value); value.get() } } impl UInt8 { /// Construct a constant vector of `UInt8` from a vector of `u8` pub fn constant_vec(values: &[u8]) -> Vec { let mut result = Vec::new(); for value in values { result.push(UInt8::constant(*value)); } result } /// Construct a constant `UInt8` from a `u8` pub fn constant(value: u8) -> Self { let mut bits = Vec::with_capacity(8); let mut tmp = value; for _ in 0..8 { // If last bit is one, push one. bits.push(Boolean::constant(tmp & 1 == 1)); tmp >>= 1; } Self { bits, value: Some(value), } } /// Allocates a slice of `u8`'s as private witnesses. pub fn new_witness_vec( cs: impl Into>, values: &[impl Into> + Copy], ) -> Result, SynthesisError> { let ns = cs.into(); let cs = ns.cs(); let mut output_vec = Vec::with_capacity(values.len()); for value in values { let byte: Option = Into::into(*value); output_vec.push(Self::new_witness(cs.clone(), || byte.get())?); } Ok(output_vec) } /// 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. /// /// 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>, values: &[u8], ) -> Result, SynthesisError> where F: PrimeField, { let ns = cs.into(); let cs = ns.cs(); let values_len = values.len(); let field_elements: Vec = ToConstraintField::::to_field_elements(values).unwrap(); let max_size = 8 * (F::Params::CAPACITY / 8) as usize; let mut allocated_bits = Vec::new(); for field_element in field_elements.into_iter() { let fe = AllocatedFp::new_input(cs.clone(), || Ok(field_element))?; let fe_bits = fe.to_bits_le()?; // Remove the most significant bit, because we know it should be zero // because `values.to_field_elements()` only // packs field elements up to the penultimate bit. // That is, the most significant bit (`ConstraintF::NUM_BITS`-th bit) is // unset, so we can just pop it off. allocated_bits.extend_from_slice(&fe_bits[0..max_size]); } // Chunk up slices of 8 bit into bytes. Ok(allocated_bits[0..(8 * values_len)] .chunks(8) .map(Self::from_bits_le) .collect()) } /// Converts a little-endian byte order representation of bits into a /// `UInt8`. #[tracing::instrument(target = "r1cs")] pub fn from_bits_le(bits: &[Boolean]) -> Self { assert_eq!(bits.len(), 8); let bits = bits.to_vec(); let mut value = Some(0u8); for (i, b) in bits.iter().enumerate() { value = match b.value().ok() { Some(b) => value.map(|v| v + (u8::from(b) << i)), None => None, } } Self { value, bits } } /// 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 { let new_value = match (self.value, other.value) { (Some(a), Some(b)) => Some(a ^ b), _ => None, }; let bits = self .bits .iter() .zip(other.bits.iter()) .map(|(a, b)| a.xor(b)) .collect::>()?; Ok(Self { bits, value: new_value, }) } } impl EqGadget for UInt8 { #[tracing::instrument(target = "r1cs")] fn is_eq(&self, other: &Self) -> Result, SynthesisError> { self.bits.as_slice().is_eq(&other.bits) } #[tracing::instrument(target = "r1cs")] fn conditional_enforce_equal( &self, other: &Self, condition: &Boolean, ) -> Result<(), SynthesisError> { self.bits.conditional_enforce_equal(&other.bits, condition) } #[tracing::instrument(target = "r1cs")] fn conditional_enforce_not_equal( &self, other: &Self, condition: &Boolean, ) -> Result<(), SynthesisError> { self.bits .conditional_enforce_not_equal(&other.bits, condition) } } impl AllocVar for UInt8 { fn new_variable>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { let ns = cs.into(); let cs = ns.cs(); let value = f().map(|f| *f.borrow()); let values = match value { Ok(val) => (0..8).map(|i| Some((val >> i) & 1 == 1)).collect(), _ => vec![None; 8], }; let bits = values .into_iter() .map(|v| Boolean::new_variable(cs.clone(), || v.get(), mode)) .collect::, _>>()?; Ok(Self { bits, value: value.ok(), }) } } #[cfg(test)] mod test { use super::UInt8; use crate::{prelude::*, Vec}; use algebra::bls12_381::Fr; use r1cs_core::{ConstraintSystem, SynthesisError}; use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; #[test] fn test_uint8_from_bits_to_bits() -> Result<(), SynthesisError> { let cs = ConstraintSystem::::new_ref(); let byte_val = 0b01110001; let byte = UInt8::new_witness(r1cs_core::ns!(cs, "alloc value"), || Ok(byte_val)).unwrap(); let bits = byte.to_bits_le()?; for (i, bit) in bits.iter().enumerate() { assert_eq!(bit.value()?, (byte_val >> i) & 1 == 1) } Ok(()) } #[test] fn test_uint8_new_input_vec() -> Result<(), SynthesisError> { let cs = ConstraintSystem::::new_ref(); let byte_vals = (64u8..128u8).collect::>(); let bytes = UInt8::new_input_vec(r1cs_core::ns!(cs, "alloc value"), &byte_vals).unwrap(); dbg!(bytes.value())?; for (native, variable) in byte_vals.into_iter().zip(bytes) { let bits = variable.to_bits_le()?; for (i, bit) in bits.iter().enumerate() { assert_eq!( bit.value()?, (native >> i) & 1 == 1, "native value {}: bit {:?}", native, i ) } } Ok(()) } #[test] fn test_uint8_from_bits() -> Result<(), SynthesisError> { let mut rng = XorShiftRng::seed_from_u64(1231275789u64); for _ in 0..1000 { let v = (0..8) .map(|_| Boolean::::Constant(rng.gen())) .collect::>(); let val = UInt8::from_bits_le(&v); for (i, bit) in val.bits.iter().enumerate() { match bit { Boolean::Constant(b) => assert!(*b == ((val.value()? >> i) & 1 == 1)), _ => unreachable!(), } } let expected_to_be_same = val.to_bits_le()?; for x in v.iter().zip(expected_to_be_same.iter()) { match x { (&Boolean::Constant(true), &Boolean::Constant(true)) => {} (&Boolean::Constant(false), &Boolean::Constant(false)) => {} _ => unreachable!(), } } } Ok(()) } #[test] fn test_uint8_xor() -> Result<(), SynthesisError> { let mut rng = XorShiftRng::seed_from_u64(1231275789u64); for _ in 0..1000 { let cs = ConstraintSystem::::new_ref(); let a: u8 = rng.gen(); let b: u8 = rng.gen(); let c: u8 = rng.gen(); let mut expected = a ^ b ^ c; let a_bit = UInt8::new_witness(r1cs_core::ns!(cs, "a_bit"), || Ok(a)).unwrap(); let b_bit = UInt8::constant(b); let c_bit = UInt8::new_witness(r1cs_core::ns!(cs, "c_bit"), || Ok(c)).unwrap(); let r = a_bit.xor(&b_bit).unwrap(); let r = r.xor(&c_bit).unwrap(); assert!(cs.is_satisfied().unwrap()); assert!(r.value == Some(expected)); for b in r.bits.iter() { match b { Boolean::Is(b) => assert!(b.value()? == (expected & 1 == 1)), Boolean::Not(b) => assert!(!b.value()? == (expected & 1 == 1)), Boolean::Constant(b) => assert!(*b == (expected & 1 == 1)), } expected >>= 1; } } Ok(()) } }