Skip to main content

ark_r1cs_std/
uint8.rs

1use ark_ff::{Field, PrimeField, ToConstraintField};
2use ark_relations::gr1cs::{Namespace, SynthesisError};
3use ark_std::vec::Vec;
4
5use crate::{
6    convert::ToConstraintFieldGadget,
7    fields::fp::{AllocatedFp, FpVar},
8    prelude::*,
9};
10
11/// A `UInt8` is a variable that represents an 8-bit unsigned integer in
12/// R1CS.
13pub type UInt8<F> = super::uint::UInt<8, u8, F>;
14
15impl<F: Field> UInt8<F> {
16    /// Allocates a slice of `u8`'s as public inputs by first packing them into
17    /// elements of `F`, (thus reducing the number of input allocations),
18    /// allocating these elements as public inputs, and then converting
19    /// these field variables `FpVar<F>` variables back into bytes.
20    ///
21    /// From a user perspective, this trade-off adds constraints, but improves
22    /// verifier time and verification key size.
23    ///
24    /// ```
25    /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> {
26    /// // We'll use the BLS12-381 scalar field for our constraints.
27    /// use ark_test_curves::bls12_381::Fr;
28    /// use ark_relations::gr1cs::*;
29    /// use ark_r1cs_std::prelude::*;
30    ///
31    /// let cs = ConstraintSystem::<Fr>::new_ref();
32    /// let two = UInt8::new_witness(cs.clone(), || Ok(2))?;
33    /// let var = vec![two.clone(); 32];
34    ///
35    /// let c = UInt8::new_input_vec(cs.clone(), &[2; 32])?;
36    /// var.enforce_equal(&c)?;
37    /// assert!(cs.is_satisfied().unwrap());
38    /// # Ok(())
39    /// # }
40    /// ```
41    pub fn new_input_vec(
42        cs: impl Into<Namespace<F>>,
43        values: &[u8],
44    ) -> Result<Vec<Self>, SynthesisError>
45    where
46        F: PrimeField,
47    {
48        let ns = cs.into();
49        let cs = ns.cs();
50        let values_len = values.len();
51        let field_elements: Vec<F> = ToConstraintField::<F>::to_field_elements(values).unwrap();
52
53        let max_size = 8 * ((F::MODULUS_BIT_SIZE - 1) / 8) as usize;
54        let mut allocated_bits = Vec::new();
55        for field_element in field_elements.into_iter() {
56            let fe = AllocatedFp::new_input(cs.clone(), || Ok(field_element))?;
57            let fe_bits = fe.to_bits_le()?;
58
59            // Remove the most significant bit, because we know it should be zero
60            // because `values.to_field_elements()` only
61            // packs field elements up to the penultimate bit.
62            // That is, the most significant bit (`ConstraintF::NUM_BITS`-th bit) is
63            // unset, so we can just pop it off.
64            allocated_bits.extend_from_slice(&fe_bits[0..max_size]);
65        }
66
67        // Chunk up slices of 8 bit into bytes.
68        Ok(allocated_bits[0..(8 * values_len)]
69            .chunks(8)
70            .map(Self::from_bits_le)
71            .collect())
72    }
73}
74
75/// Parses the `Vec<UInt8<ConstraintF>>` in fixed-sized
76/// `ConstraintF::MODULUS_BIT_SIZE - 1` chunks and converts each chunk, which is
77/// assumed to be little-endian, to its `FpVar<ConstraintF>` representation.
78/// This is the gadget counterpart to the `[u8]` implementation of
79/// [`ToConstraintField``].
80impl<ConstraintF: PrimeField> ToConstraintFieldGadget<ConstraintF> for [UInt8<ConstraintF>] {
81    #[tracing::instrument(target = "gr1cs")]
82    fn to_constraint_field(&self) -> Result<Vec<FpVar<ConstraintF>>, SynthesisError> {
83        let max_size = ((ConstraintF::MODULUS_BIT_SIZE - 1) / 8) as usize;
84        self.chunks(max_size)
85            .map(|chunk| Boolean::le_bits_to_fp(chunk.to_bits_le()?.as_slice()))
86            .collect::<Result<Vec<_>, SynthesisError>>()
87    }
88}
89
90impl<ConstraintF: PrimeField> ToConstraintFieldGadget<ConstraintF> for Vec<UInt8<ConstraintF>> {
91    #[tracing::instrument(target = "gr1cs")]
92    fn to_constraint_field(&self) -> Result<Vec<FpVar<ConstraintF>>, SynthesisError> {
93        self.as_slice().to_constraint_field()
94    }
95}
96
97#[cfg(test)]
98mod test {
99    use crate::{
100        convert::ToConstraintFieldGadget,
101        fields::fp::FpVar,
102        prelude::{
103            AllocationMode::{Constant, Input, Witness},
104            *,
105        },
106    };
107    use ark_ff::{PrimeField, ToConstraintField};
108    use ark_relations::gr1cs::{ConstraintSystem, SynthesisError};
109    use ark_std::rand::{distributions::Uniform, Rng};
110    use ark_test_curves::bls12_381::Fr;
111
112    #[test]
113    fn test_uint8_from_bits_to_bits() -> Result<(), SynthesisError> {
114        let cs = ConstraintSystem::<Fr>::new_ref();
115        let byte_val = 0b01110001;
116        let byte =
117            UInt8::new_witness(ark_relations::ns!(cs, "alloc value"), || Ok(byte_val)).unwrap();
118        let bits = byte.to_bits_le()?;
119        for (i, bit) in bits.iter().enumerate() {
120            assert_eq!(bit.value()?, (byte_val >> i) & 1 == 1)
121        }
122        Ok(())
123    }
124
125    #[test]
126    fn test_uint8_new_input_vec() -> Result<(), SynthesisError> {
127        let cs = ConstraintSystem::<Fr>::new_ref();
128        let byte_vals = (64u8..128u8).collect::<Vec<_>>();
129        let bytes =
130            UInt8::new_input_vec(ark_relations::ns!(cs, "alloc value"), &byte_vals).unwrap();
131        for (native, variable) in byte_vals.into_iter().zip(bytes) {
132            let bits = variable.to_bits_le()?;
133            for (i, bit) in bits.iter().enumerate() {
134                assert_eq!(
135                    bit.value()?,
136                    (native >> i) & 1 == 1,
137                    "native value {}: bit {:?}",
138                    native,
139                    i
140                )
141            }
142        }
143        Ok(())
144    }
145
146    #[test]
147    fn test_uint8_from_bits() -> Result<(), SynthesisError> {
148        let mut rng = ark_std::test_rng();
149
150        for _ in 0..1000 {
151            let v = (0..8)
152                .map(|_| Boolean::<Fr>::Constant(rng.gen()))
153                .collect::<Vec<_>>();
154
155            let val = UInt8::from_bits_le(&v);
156
157            let value = val.value()?;
158            for (i, bit) in val.bits.iter().enumerate() {
159                match bit {
160                    Boolean::Constant(b) => assert_eq!(*b, ((value >> i) & 1 == 1)),
161                    _ => unreachable!(),
162                }
163            }
164
165            let expected_to_be_same = val.to_bits_le()?;
166
167            for x in v.iter().zip(expected_to_be_same.iter()) {
168                match x {
169                    (&Boolean::Constant(true), &Boolean::Constant(true)) => {},
170                    (&Boolean::Constant(false), &Boolean::Constant(false)) => {},
171                    _ => unreachable!(),
172                }
173            }
174        }
175        Ok(())
176    }
177
178    #[test]
179    fn test_uint8_xor() -> Result<(), SynthesisError> {
180        let mut rng = ark_std::test_rng();
181
182        for _ in 0..1000 {
183            let cs = ConstraintSystem::<Fr>::new_ref();
184
185            let a: u8 = rng.gen();
186            let b: u8 = rng.gen();
187            let c: u8 = rng.gen();
188
189            let mut expected = a ^ b ^ c;
190
191            let a_bit = UInt8::new_witness(ark_relations::ns!(cs, "a_bit"), || Ok(a)).unwrap();
192            let b_bit = UInt8::constant(b);
193            let c_bit = UInt8::new_witness(ark_relations::ns!(cs, "c_bit"), || Ok(c)).unwrap();
194
195            let mut r = a_bit ^ b_bit;
196            r ^= &c_bit;
197
198            assert!(cs.is_satisfied().unwrap());
199
200            assert_eq!(r.value, Some(expected));
201
202            for b in r.bits.iter() {
203                match b {
204                    Boolean::Var(b) => assert!(b.value()? == (expected & 1 == 1)),
205                    Boolean::Constant(b) => assert!(*b == (expected & 1 == 1)),
206                }
207
208                expected >>= 1;
209            }
210        }
211        Ok(())
212    }
213
214    #[test]
215    fn test_uint8_to_constraint_field() -> Result<(), SynthesisError> {
216        let mut rng = ark_std::test_rng();
217        let max_size = ((<Fr as PrimeField>::MODULUS_BIT_SIZE - 1) / 8) as usize;
218
219        let modes = [Input, Witness, Constant];
220        for mode in &modes {
221            for _ in 0..1000 {
222                let cs = ConstraintSystem::<Fr>::new_ref();
223
224                let bytes: Vec<u8> = (&mut rng)
225                    .sample_iter(&Uniform::new_inclusive(0, u8::max_value()))
226                    .take(max_size * 3 + 5)
227                    .collect();
228
229                let bytes_var = bytes
230                    .iter()
231                    .map(|byte| UInt8::new_variable(cs.clone(), || Ok(*byte), *mode))
232                    .collect::<Result<Vec<_>, SynthesisError>>()?;
233
234                let f_vec: Vec<Fr> = bytes.to_field_elements().unwrap();
235                let f_var_vec: Vec<FpVar<Fr>> = bytes_var.to_constraint_field()?;
236
237                assert!(cs.is_satisfied().unwrap());
238                assert_eq!(f_vec, f_var_vec.value()?);
239            }
240        }
241
242        Ok(())
243    }
244
245    #[test]
246    fn test_uint8_random_access() {
247        let mut rng = ark_std::test_rng();
248
249        for _ in 0..100 {
250            let cs = ConstraintSystem::<Fr>::new_ref();
251
252            // value array
253            let values: Vec<u8> = (0..128).map(|_| rng.gen()).collect();
254            let values_const: Vec<UInt8<Fr>> = values.iter().map(|x| UInt8::constant(*x)).collect();
255
256            // index array
257            let position: Vec<bool> = (0..7).map(|_| rng.gen()).collect();
258            let position_var: Vec<Boolean<Fr>> = position
259                .iter()
260                .map(|b| {
261                    Boolean::new_witness(ark_relations::ns!(cs, "index_arr_element"), || Ok(*b))
262                        .unwrap()
263                })
264                .collect();
265
266            // index
267            let mut index = 0;
268            for x in position {
269                index *= 2;
270                index += if x { 1 } else { 0 };
271            }
272
273            assert_eq!(
274                UInt8::conditionally_select_power_of_two_vector(&position_var, &values_const)
275                    .unwrap()
276                    .value()
277                    .unwrap(),
278                values[index]
279            )
280        }
281    }
282}