|
| 1 | +use crate::{overhead, Vec}; |
| 2 | +use ark_ff::PrimeField; |
| 3 | +use ark_nonnative_field::params::{get_params, OptimizationType}; |
| 4 | +use ark_nonnative_field::{AllocatedNonNativeFieldVar, NonNativeFieldVar}; |
| 5 | +use ark_r1cs_std::{ |
| 6 | + alloc::AllocVar, |
| 7 | + bits::{uint8::UInt8, ToBitsGadget}, |
| 8 | + boolean::Boolean, |
| 9 | + fields::fp::AllocatedFp, |
| 10 | + fields::fp::FpVar, |
| 11 | + R1CSVar, |
| 12 | +}; |
| 13 | +use ark_relations::lc; |
| 14 | +use ark_relations::r1cs::{ |
| 15 | + ConstraintSystemRef, LinearCombination, OptimizationGoal, SynthesisError, |
| 16 | +}; |
| 17 | +use ark_sponge::constraints::{AbsorbGadget, CryptographicSpongeVar}; |
| 18 | +use ark_sponge::CryptographicSponge; |
| 19 | +use core::marker::PhantomData; |
| 20 | + |
| 21 | +/// Building the Fiat-Shamir sponge's gadget from any algebraic sponge's gadget. |
| 22 | +#[derive(Clone)] |
| 23 | +pub struct FiatShamirAlgebraicSpongeRngVar< |
| 24 | + F: PrimeField, |
| 25 | + CF: PrimeField, |
| 26 | + PS: CryptographicSponge, |
| 27 | + S: CryptographicSpongeVar<CF, PS>, |
| 28 | +> { |
| 29 | + pub cs: ConstraintSystemRef<CF>, |
| 30 | + pub s: S, |
| 31 | + #[doc(hidden)] |
| 32 | + f_phantom: PhantomData<F>, |
| 33 | + cf_phantom: PhantomData<CF>, |
| 34 | + ps_phantom: PhantomData<PS>, |
| 35 | +} |
| 36 | + |
| 37 | +impl<F: PrimeField, CF: PrimeField, PS: CryptographicSponge, S: CryptographicSpongeVar<CF, PS>> |
| 38 | + FiatShamirAlgebraicSpongeRngVar<F, CF, PS, S> |
| 39 | +{ |
| 40 | + /// Compress every two elements if possible. Provides a vector of (limb, num_of_additions), |
| 41 | + /// both of which are CF. |
| 42 | + #[tracing::instrument(target = "r1cs")] |
| 43 | + pub fn compress_gadgets( |
| 44 | + src_limbs: &[(FpVar<CF>, CF)], |
| 45 | + ty: OptimizationType, |
| 46 | + ) -> Result<Vec<FpVar<CF>>, SynthesisError> { |
| 47 | + let capacity = CF::size_in_bits() - 1; |
| 48 | + let mut dest_limbs = Vec::<FpVar<CF>>::new(); |
| 49 | + |
| 50 | + if src_limbs.is_empty() { |
| 51 | + return Ok(vec![]); |
| 52 | + } |
| 53 | + |
| 54 | + let params = get_params(F::size_in_bits(), CF::size_in_bits(), ty); |
| 55 | + |
| 56 | + let adjustment_factor_lookup_table = { |
| 57 | + let mut table = Vec::<CF>::new(); |
| 58 | + |
| 59 | + let mut cur = CF::one(); |
| 60 | + for _ in 1..=capacity { |
| 61 | + table.push(cur); |
| 62 | + cur.double_in_place(); |
| 63 | + } |
| 64 | + |
| 65 | + table |
| 66 | + }; |
| 67 | + |
| 68 | + let mut i: usize = 0; |
| 69 | + let src_len = src_limbs.len(); |
| 70 | + while i < src_len { |
| 71 | + let first = &src_limbs[i]; |
| 72 | + let second = if i + 1 < src_len { |
| 73 | + Some(&src_limbs[i + 1]) |
| 74 | + } else { |
| 75 | + None |
| 76 | + }; |
| 77 | + |
| 78 | + let first_max_bits_per_limb = params.bits_per_limb + overhead!(first.1 + &CF::one()); |
| 79 | + let second_max_bits_per_limb = if second.is_some() { |
| 80 | + params.bits_per_limb + overhead!(second.unwrap().1 + &CF::one()) |
| 81 | + } else { |
| 82 | + 0 |
| 83 | + }; |
| 84 | + |
| 85 | + if second.is_some() && first_max_bits_per_limb + second_max_bits_per_limb <= capacity { |
| 86 | + let adjustment_factor = &adjustment_factor_lookup_table[second_max_bits_per_limb]; |
| 87 | + |
| 88 | + dest_limbs.push(&first.0 * *adjustment_factor + &second.unwrap().0); |
| 89 | + i += 2; |
| 90 | + } else { |
| 91 | + dest_limbs.push(first.0.clone()); |
| 92 | + i += 1; |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + Ok(dest_limbs) |
| 97 | + } |
| 98 | + |
| 99 | + /// Push gadgets to sponge. |
| 100 | + #[tracing::instrument(target = "r1cs", skip(sponge))] |
| 101 | + pub fn push_gadgets_to_sponge( |
| 102 | + sponge: &mut S, |
| 103 | + src: &[NonNativeFieldVar<F, CF>], |
| 104 | + ty: OptimizationType, |
| 105 | + ) -> Result<(), SynthesisError> { |
| 106 | + let mut src_limbs: Vec<(FpVar<CF>, CF)> = Vec::new(); |
| 107 | + |
| 108 | + for elem in src.iter() { |
| 109 | + match elem { |
| 110 | + NonNativeFieldVar::Constant(c) => { |
| 111 | + let v = AllocatedNonNativeFieldVar::<F, CF>::new_constant(sponge.cs(), c)?; |
| 112 | + |
| 113 | + for limb in v.limbs.iter() { |
| 114 | + let num_of_additions_over_normal_form = |
| 115 | + if v.num_of_additions_over_normal_form == CF::zero() { |
| 116 | + CF::one() |
| 117 | + } else { |
| 118 | + v.num_of_additions_over_normal_form |
| 119 | + }; |
| 120 | + src_limbs.push((limb.clone(), num_of_additions_over_normal_form)); |
| 121 | + } |
| 122 | + } |
| 123 | + NonNativeFieldVar::Var(v) => { |
| 124 | + for limb in v.limbs.iter() { |
| 125 | + let num_of_additions_over_normal_form = |
| 126 | + if v.num_of_additions_over_normal_form == CF::zero() { |
| 127 | + CF::one() |
| 128 | + } else { |
| 129 | + v.num_of_additions_over_normal_form |
| 130 | + }; |
| 131 | + src_limbs.push((limb.clone(), num_of_additions_over_normal_form)); |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + let dest_limbs = Self::compress_gadgets(&src_limbs, ty)?; |
| 138 | + sponge.absorb(&dest_limbs)?; |
| 139 | + Ok(()) |
| 140 | + } |
| 141 | + |
| 142 | + /// Obtain random bits from hashchain gadget. (Not guaranteed to be uniformly distributed, |
| 143 | + /// should only be used in certain situations.) |
| 144 | + #[tracing::instrument(target = "r1cs", skip(sponge))] |
| 145 | + pub fn get_booleans_from_sponge( |
| 146 | + sponge: &mut S, |
| 147 | + num_bits: usize, |
| 148 | + ) -> Result<Vec<Boolean<CF>>, SynthesisError> { |
| 149 | + let bits_per_element = CF::size_in_bits() - 1; |
| 150 | + let num_elements = (num_bits + bits_per_element - 1) / bits_per_element; |
| 151 | + |
| 152 | + let src_elements = sponge.squeeze_field_elements(num_elements)?; |
| 153 | + let mut dest_bits = Vec::<Boolean<CF>>::new(); |
| 154 | + |
| 155 | + for elem in src_elements.iter() { |
| 156 | + let elem_bits = elem.to_bits_be()?; |
| 157 | + dest_bits.extend_from_slice(&elem_bits[1..]); // discard the highest bit |
| 158 | + } |
| 159 | + |
| 160 | + Ok(dest_bits) |
| 161 | + } |
| 162 | + |
| 163 | + /// Obtain random elements from hashchain gadget. (Not guaranteed to be uniformly distributed, |
| 164 | + /// should only be used in certain situations.) |
| 165 | + #[tracing::instrument(target = "r1cs", skip(sponge))] |
| 166 | + pub fn get_gadgets_from_sponge( |
| 167 | + sponge: &mut S, |
| 168 | + num_elements: usize, |
| 169 | + outputs_short_elements: bool, |
| 170 | + ) -> Result<Vec<NonNativeFieldVar<F, CF>>, SynthesisError> { |
| 171 | + let (dest_gadgets, _) = |
| 172 | + Self::get_gadgets_and_bits_from_sponge(sponge, num_elements, outputs_short_elements)?; |
| 173 | + |
| 174 | + Ok(dest_gadgets) |
| 175 | + } |
| 176 | + |
| 177 | + /// Obtain random elements, and the corresponding bits, from hashchain gadget. (Not guaranteed |
| 178 | + /// to be uniformly distributed, should only be used in certain situations.) |
| 179 | + #[tracing::instrument(target = "r1cs", skip(sponge))] |
| 180 | + #[allow(clippy::type_complexity)] |
| 181 | + pub fn get_gadgets_and_bits_from_sponge( |
| 182 | + sponge: &mut S, |
| 183 | + num_elements: usize, |
| 184 | + outputs_short_elements: bool, |
| 185 | + ) -> Result<(Vec<NonNativeFieldVar<F, CF>>, Vec<Vec<Boolean<CF>>>), SynthesisError> { |
| 186 | + let cs = sponge.cs(); |
| 187 | + |
| 188 | + let optimization_type = match cs.optimization_goal() { |
| 189 | + OptimizationGoal::None => OptimizationType::Constraints, |
| 190 | + OptimizationGoal::Constraints => OptimizationType::Constraints, |
| 191 | + OptimizationGoal::Weight => OptimizationType::Weight, |
| 192 | + }; |
| 193 | + |
| 194 | + let params = get_params(F::size_in_bits(), CF::size_in_bits(), optimization_type); |
| 195 | + |
| 196 | + let num_bits_per_nonnative = if outputs_short_elements { |
| 197 | + 128 |
| 198 | + } else { |
| 199 | + F::size_in_bits() - 1 // also omit the highest bit |
| 200 | + }; |
| 201 | + let bits = Self::get_booleans_from_sponge(sponge, num_bits_per_nonnative * num_elements)?; |
| 202 | + |
| 203 | + let mut lookup_table = Vec::<Vec<CF>>::new(); |
| 204 | + let mut cur = F::one(); |
| 205 | + for _ in 0..num_bits_per_nonnative { |
| 206 | + let repr = AllocatedNonNativeFieldVar::<F, CF>::get_limbs_representations( |
| 207 | + &cur, |
| 208 | + optimization_type, |
| 209 | + )?; |
| 210 | + lookup_table.push(repr); |
| 211 | + cur.double_in_place(); |
| 212 | + } |
| 213 | + |
| 214 | + let mut dest_gadgets = Vec::<NonNativeFieldVar<F, CF>>::new(); |
| 215 | + let mut dest_bits = Vec::<Vec<Boolean<CF>>>::new(); |
| 216 | + bits.chunks_exact(num_bits_per_nonnative) |
| 217 | + .for_each(|per_nonnative_bits| { |
| 218 | + let mut val = vec![CF::zero(); params.num_limbs]; |
| 219 | + let mut lc = vec![LinearCombination::<CF>::zero(); params.num_limbs]; |
| 220 | + |
| 221 | + let mut per_nonnative_bits_le = per_nonnative_bits.to_vec(); |
| 222 | + per_nonnative_bits_le.reverse(); |
| 223 | + |
| 224 | + dest_bits.push(per_nonnative_bits_le.clone()); |
| 225 | + |
| 226 | + for (j, bit) in per_nonnative_bits_le.iter().enumerate() { |
| 227 | + if bit.value().unwrap_or_default() { |
| 228 | + for (k, val) in val.iter_mut().enumerate().take(params.num_limbs) { |
| 229 | + *val += &lookup_table[j][k]; |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + #[allow(clippy::needless_range_loop)] |
| 234 | + for k in 0..params.num_limbs { |
| 235 | + lc[k] = &lc[k] + bit.lc() * lookup_table[j][k]; |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + let mut limbs = Vec::new(); |
| 240 | + for k in 0..params.num_limbs { |
| 241 | + let gadget = |
| 242 | + AllocatedFp::new_witness(ark_relations::ns!(cs, "alloc"), || Ok(val[k])) |
| 243 | + .unwrap(); |
| 244 | + lc[k] = lc[k].clone() - (CF::one(), gadget.variable); |
| 245 | + cs.enforce_constraint(lc!(), lc!(), lc[k].clone()).unwrap(); |
| 246 | + limbs.push(FpVar::<CF>::from(gadget)); |
| 247 | + } |
| 248 | + |
| 249 | + dest_gadgets.push(NonNativeFieldVar::<F, CF>::Var( |
| 250 | + AllocatedNonNativeFieldVar::<F, CF> { |
| 251 | + cs: cs.clone(), |
| 252 | + limbs, |
| 253 | + num_of_additions_over_normal_form: CF::zero(), |
| 254 | + is_in_the_normal_form: true, |
| 255 | + target_phantom: Default::default(), |
| 256 | + }, |
| 257 | + )); |
| 258 | + }); |
| 259 | + |
| 260 | + Ok((dest_gadgets, dest_bits)) |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +impl<F: PrimeField, CF: PrimeField, PS: CryptographicSponge, S: CryptographicSpongeVar<CF, PS>> |
| 265 | + CryptographicSpongeVar<CF, PS> for FiatShamirAlgebraicSpongeRngVar<F, CF, PS, S> |
| 266 | +{ |
| 267 | + type Parameters = S::Parameters; |
| 268 | + |
| 269 | + fn new(cs: ConstraintSystemRef<CF>, params: &Self::Parameters) -> Self { |
| 270 | + Self { |
| 271 | + cs: cs.clone(), |
| 272 | + s: S::new(cs, params), |
| 273 | + f_phantom: PhantomData, |
| 274 | + cf_phantom: PhantomData, |
| 275 | + ps_phantom: PhantomData, |
| 276 | + } |
| 277 | + } |
| 278 | + |
| 279 | + fn cs(&self) -> ConstraintSystemRef<CF> { |
| 280 | + self.cs.clone() |
| 281 | + } |
| 282 | + |
| 283 | + fn absorb(&mut self, input: &impl AbsorbGadget<CF>) -> Result<(), SynthesisError> { |
| 284 | + self.s.absorb(input) |
| 285 | + } |
| 286 | + |
| 287 | + fn squeeze_bytes(&mut self, num_bytes: usize) -> Result<Vec<UInt8<CF>>, SynthesisError> { |
| 288 | + self.s.squeeze_bytes(num_bytes) |
| 289 | + } |
| 290 | + |
| 291 | + fn squeeze_bits(&mut self, num_bits: usize) -> Result<Vec<Boolean<CF>>, SynthesisError> { |
| 292 | + self.s.squeeze_bits(num_bits) |
| 293 | + } |
| 294 | + |
| 295 | + fn squeeze_field_elements( |
| 296 | + &mut self, |
| 297 | + num_elements: usize, |
| 298 | + ) -> Result<Vec<FpVar<CF>>, SynthesisError> { |
| 299 | + self.s.squeeze_field_elements(num_elements) |
| 300 | + } |
| 301 | +} |
0 commit comments