Skip to content

Commit 1f8230b

Browse files
committed
Add bits to biguint struct
1 parent c63f7b1 commit 1f8230b

File tree

3 files changed

+160
-63
lines changed

3 files changed

+160
-63
lines changed

src/backends/plonky2/primitives/ec/bits.rs

Lines changed: 153 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use plonky2::{
1414
witness::{PartitionWitness, Witness, WitnessWrite},
1515
},
1616
plonk::{circuit_builder::CircuitBuilder, circuit_data::CommonCircuitData},
17-
util::serialization::{IoResult, Read, Write},
17+
util::serialization::{Buffer, IoResult, Read, Write},
1818
};
1919

2020
use crate::backends::plonky2::basetypes::{D, F};
@@ -79,7 +79,10 @@ impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D>
7979
/// A big integer, represented in base `2^32` with 10 digits, in little endian
8080
/// form.
8181
#[derive(Clone, Debug)]
82-
pub struct BigUInt320Target(pub(super) [Target; 10]);
82+
pub struct BigUInt320Target {
83+
pub limbs: [Target; 10],
84+
pub bits: [BoolTarget; 320],
85+
}
8386

8487
pub trait CircuitBuilderBits {
8588
/// Enforces the constraint that `then_zero` must be zero if `if_zero`
@@ -90,27 +93,26 @@ pub trait CircuitBuilderBits {
9093
/// are zero, then it chooses the solution `x = 0`.
9194
fn conditional_zero(&mut self, if_zero: Target, then_zero: Target);
9295

93-
/// Returns the binary representation of the target, in little-endian order.
94-
fn biguint_bits(&mut self, x: &BigUInt320Target) -> [BoolTarget; 320];
95-
9696
/// Decomposes the target x as `y + 2^32 z`, where `0 < y,z < 2**32`, and
9797
/// `y=0` if `z=2**32-1`. Note that calling [`CircuitBuilder::split_le`]
9898
/// with `num_bits = 64` will not check the latter condition.
9999
fn split_32_bit(&mut self, x: Target) -> [Target; 2];
100100

101+
/// Like `split_low_high` except it doesn't discard the bit decompositions.
102+
fn split_low_high_with_bits(
103+
&mut self,
104+
x: Target,
105+
n_log: usize,
106+
num_bits: usize,
107+
) -> ((Target, Vec<BoolTarget>), (Target, Vec<BoolTarget>));
108+
101109
/// Interprets `arr` as an integer in base `[GoldilocksField::ORDER]`,
102110
/// with the digits in little endian order. The length of `arr` must be at
103111
/// most 5.
104112
fn field_elements_to_biguint(&mut self, arr: &[Target]) -> BigUInt320Target;
105113

106-
fn normalize_bigint(
107-
&mut self,
108-
x: &BigUInt320Target,
109-
max_digit_bits: usize,
110-
max_num_carries: usize,
111-
) -> BigUInt320Target;
112-
113114
fn constant_biguint320(&mut self, n: &BigUint) -> BigUInt320Target;
115+
fn biguint320_target_from_limbs(&mut self, x: &[Target]) -> BigUInt320Target;
114116
fn add_virtual_biguint320_target(&mut self) -> BigUInt320Target;
115117
fn connect_biguint320(&mut self, x: &BigUInt320Target, y: &BigUInt320Target);
116118
}
@@ -128,20 +130,18 @@ impl CircuitBuilderBits for CircuitBuilder<GoldilocksField, 2> {
128130
self.connect(prod, then_zero);
129131
}
130132

131-
fn biguint_bits(&mut self, x: &BigUInt320Target) -> [BoolTarget; 320] {
132-
let bits = x.0.map(|t| self.low_bits(t, 32, 32));
133-
array::from_fn(|i| bits[i / 32][i % 32])
134-
}
135-
136133
fn field_elements_to_biguint(&mut self, arr: &[Target]) -> BigUInt320Target {
137134
assert!(arr.len() <= 5);
138135
let zero = self.zero();
139136
let neg_one = self.neg_one();
140137
let two_32 = self.constant(GoldilocksField::from_canonical_u64(1 << 32));
141138
// Apply Horner's method to Σarr[i]*p^i.
142139
// First map each target to its limbs.
143-
let arr_limbs: Vec<_> = arr.iter().map(|x| self.split_32_bit(*x).to_vec()).collect();
144-
let res_limbs = arr_limbs
140+
let arr_limbs: Vec<_> = arr
141+
.iter()
142+
.map(|x| (self.split_32_bit(*x).to_vec(), vec![]))
143+
.collect();
144+
let (res_limbs, res_bits) = arr_limbs
145145
.into_iter()
146146
.rev()
147147
.enumerate()
@@ -153,25 +153,25 @@ impl CircuitBuilderBits for CircuitBuilder<GoldilocksField, 2> {
153153
.map(|j| {
154154
if j == 0 {
155155
// x_0
156-
res[0]
156+
res.0[0]
157157
} else if j == 1 {
158158
// x_1 - x_0 + 2^32
159-
let diff = self.sub(res[1], res[0]);
159+
let diff = self.sub(res.0[1], res.0[0]);
160160
self.add(diff, two_32)
161161
} else if j < 2 * i {
162162
// x_j + x_{j-2} - x_{j-1} + 2^32 - 1
163-
let diff = self.sub(res[j], res[j - 1]);
164-
let sum = self.add(diff, res[j - 2]);
163+
let diff = self.sub(res.0[j], res.0[j - 1]);
164+
let sum = self.add(diff, res.0[j - 2]);
165165
let sum = self.add(sum, two_32);
166166
self.add(sum, neg_one)
167167
} else if j == 2 * i {
168168
// x_{2*j - 2} - x_{2*j - 1} + 2^32
169-
let diff = self.sub(res[2 * i - 2], res[2 * i - 1]);
169+
let diff = self.sub(res.0[2 * i - 2], res.0[2 * i - 1]);
170170
let sum = self.add(diff, two_32);
171171
self.add(sum, neg_one)
172172
} else {
173173
// x_{2*i - 1} - 1
174-
self.add(res[2 * i - 1], neg_one)
174+
self.add(res.0[2 * i - 1], neg_one)
175175
}
176176
})
177177
.collect::<Vec<_>>();
@@ -180,8 +180,8 @@ impl CircuitBuilderBits for CircuitBuilder<GoldilocksField, 2> {
180180
.into_iter()
181181
.enumerate()
182182
.map(|(i, x)| match i {
183-
0 => self.add(a[0], x),
184-
1 => self.add(a[1], x),
183+
0 => self.add(a.0[0], x),
184+
1 => self.add(a.0[1], x),
185185
_ => x,
186186
})
187187
.collect::<Vec<_>>();
@@ -192,30 +192,24 @@ impl CircuitBuilderBits for CircuitBuilder<GoldilocksField, 2> {
192192
)
193193
})
194194
.map(|(_, v)| v)
195-
.unwrap_or(vec![]);
195+
.unwrap_or((vec![], vec![]));
196196
// Collect limbs, padding with 0s if necessary.
197-
BigUInt320Target(array::from_fn(|i| {
197+
let limbs: [Target; 10] = array::from_fn(|i| {
198198
if i < res_limbs.len() {
199199
res_limbs[i]
200200
} else {
201201
zero
202202
}
203-
}))
204-
}
205-
206-
fn normalize_bigint(
207-
&mut self,
208-
x: &BigUInt320Target,
209-
max_digit_bits: usize,
210-
max_num_carries: usize,
211-
) -> BigUInt320Target {
212-
let mut x = x.clone();
213-
for i in 0..max_num_carries {
214-
let (low, high) = self.split_low_high(x.0[i], 32, max_digit_bits);
215-
x.0[i] = low;
216-
x.0[i + 1] = self.add(x.0[i + 1], high);
217-
}
218-
x
203+
});
204+
// Collect bits, padding with 0s if necessary.
205+
let bits: [BoolTarget; 320] = array::from_fn(|i| {
206+
if i < res_bits.len() {
207+
res_bits[i]
208+
} else {
209+
self._false()
210+
}
211+
});
212+
BigUInt320Target { limbs, bits }
219213
}
220214

221215
fn split_32_bit(&mut self, x: Target) -> [Target; 2] {
@@ -226,44 +220,146 @@ impl CircuitBuilderBits for CircuitBuilder<GoldilocksField, 2> {
226220
[low, high]
227221
}
228222

223+
fn split_low_high_with_bits(
224+
&mut self,
225+
x: Target,
226+
n_log: usize,
227+
num_bits: usize,
228+
) -> ((Target, Vec<BoolTarget>), (Target, Vec<BoolTarget>)) {
229+
let low = self.add_virtual_target();
230+
let high = self.add_virtual_target();
231+
232+
self.add_simple_generator(LowHighGenerator {
233+
integer: x,
234+
n_log,
235+
low,
236+
high,
237+
});
238+
239+
let low_bits = self.split_le(low, n_log);
240+
let high_bits = self.split_le(high, num_bits - n_log);
241+
242+
let pow2 = self.constant(F::from_canonical_u64(1 << n_log));
243+
let comp_x = self.mul_add(high, pow2, low);
244+
self.connect(x, comp_x);
245+
246+
((low, low_bits), (high, high_bits))
247+
}
248+
229249
fn constant_biguint320(&mut self, n: &BigUint) -> BigUInt320Target {
230250
assert!(n.bits() <= 320);
231251
let digits = n.to_u32_digits();
232-
let targets = array::from_fn(|i| {
252+
let limbs: [Target; 10] = array::from_fn(|i| {
233253
let d = digits.get(i).copied().unwrap_or(0);
234254
self.constant(GoldilocksField::from_canonical_u32(d))
235255
});
236-
BigUInt320Target(targets)
256+
self.biguint320_target_from_limbs(&limbs)
237257
}
238258

239-
fn add_virtual_biguint320_target(&mut self) -> BigUInt320Target {
240-
let targets = self.add_virtual_target_arr();
241-
for t in targets {
242-
self.range_check(t, 32);
259+
fn biguint320_target_from_limbs(&mut self, x: &[Target]) -> BigUInt320Target {
260+
assert!(x.len() == 10);
261+
let limbs = array::from_fn(|i| x[i]);
262+
let bit_vec = biguint_limbs_to_bits(self, x);
263+
BigUInt320Target {
264+
limbs,
265+
bits: array::from_fn(|i| bit_vec[i]),
243266
}
244-
BigUInt320Target(targets)
267+
}
268+
269+
fn add_virtual_biguint320_target(&mut self) -> BigUInt320Target {
270+
let limbs: [Target; 10] = self.add_virtual_target_arr();
271+
self.biguint320_target_from_limbs(&limbs)
245272
}
246273

247274
fn connect_biguint320(&mut self, x: &BigUInt320Target, y: &BigUInt320Target) {
248275
for i in 0..10 {
249-
self.connect(x.0[i], y.0[i]);
276+
self.connect(x.limbs[i], y.limbs[i]);
250277
}
251278
}
252279
}
253280

254281
/// Normalises the limbs of a biguint assuming no overflow in the
255-
/// field.
282+
/// field. Returns the limbs together with their bit decomposition.
256283
fn normalize_biguint_limbs(
257284
builder: &mut CircuitBuilder<F, D>,
258285
x: &[Target],
259286
max_digit_bits: usize,
260287
max_num_carries: usize,
261-
) -> Vec<Target> {
288+
) -> (Vec<Target>, Vec<BoolTarget>) {
262289
let mut x = x.to_vec();
290+
let mut bits = Vec::with_capacity(32 * (max_num_carries + 1));
263291
for i in 0..max_num_carries {
264-
let (low, high) = builder.split_low_high(x[i], 32, max_digit_bits);
292+
let ((low, mut low_bits), (high, _)) =
293+
builder.split_low_high_with_bits(x[i], 32, max_digit_bits);
265294
x[i] = low;
266295
x[i + 1] = builder.add(x[i + 1], high);
296+
bits.append(&mut low_bits);
297+
}
298+
let mut final_bits = builder.split_le(x[max_num_carries], 32);
299+
bits.append(&mut final_bits);
300+
(x, bits)
301+
}
302+
303+
/// Converts biguint limbs to bits, checking that each limb is 32-bits
304+
/// long.
305+
fn biguint_limbs_to_bits(builder: &mut CircuitBuilder<F, D>, limbs: &[Target]) -> Vec<BoolTarget> {
306+
limbs
307+
.iter()
308+
.flat_map(|t| builder.split_le(*t, 32))
309+
.collect()
310+
}
311+
312+
/*
313+
Copied from https://github.com/0xPolygonZero/plonky2/blob/82791c4809d6275682c34b926390ecdbdc2a5297/plonky2/src/gadgets/range_check.rs#L62
314+
*/
315+
316+
#[derive(Debug, Default)]
317+
pub struct LowHighGenerator {
318+
integer: Target,
319+
n_log: usize,
320+
low: Target,
321+
high: Target,
322+
}
323+
324+
impl<F: RichField + Extendable<D>, const D: usize> SimpleGenerator<F, D> for LowHighGenerator {
325+
fn id(&self) -> String {
326+
"LowHighGenerator".to_string()
327+
}
328+
329+
fn dependencies(&self) -> Vec<Target> {
330+
vec![self.integer]
331+
}
332+
333+
fn run_once(
334+
&self,
335+
witness: &PartitionWitness<F>,
336+
out_buffer: &mut GeneratedValues<F>,
337+
) -> anyhow::Result<()> {
338+
let integer_value = witness.get_target(self.integer).to_canonical_u64();
339+
let low = integer_value & ((1 << self.n_log) - 1);
340+
let high = integer_value >> self.n_log;
341+
342+
out_buffer.set_target(self.low, F::from_canonical_u64(low))?;
343+
out_buffer.set_target(self.high, F::from_canonical_u64(high))
344+
}
345+
346+
fn serialize(&self, dst: &mut Vec<u8>, _common_data: &CommonCircuitData<F, D>) -> IoResult<()> {
347+
dst.write_target(self.integer)?;
348+
dst.write_usize(self.n_log)?;
349+
dst.write_target(self.low)?;
350+
dst.write_target(self.high)
351+
}
352+
353+
fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData<F, D>) -> IoResult<Self> {
354+
let integer = src.read_target()?;
355+
let n_log = src.read_usize()?;
356+
let low = src.read_target()?;
357+
let high = src.read_target()?;
358+
Ok(Self {
359+
integer,
360+
n_log,
361+
low,
362+
high,
363+
})
267364
}
268-
x
269365
}

src/backends/plonky2/primitives/ec/curve.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ pub trait WitnessWriteCurve: WitnessWrite<GoldilocksField> {
630630
let digits = value.to_u32_digits();
631631
for i in 0..10 {
632632
let d = digits.get(i).copied().unwrap_or(0);
633-
self.set_target(target.0[i], GoldilocksField::from_canonical_u32(d))?;
633+
self.set_target(target.limbs[i], GoldilocksField::from_canonical_u32(d))?;
634634
}
635635
Ok(())
636636
}
@@ -775,8 +775,8 @@ mod test {
775775
let p_tgt = builder.constant_point(p);
776776
let g_scalar_bigint = builder.constant_biguint320(&n2);
777777
let p_scalar_bigint = builder.constant_biguint320(&n3);
778-
let g_scalar_bits = builder.biguint_bits(&g_scalar_bigint);
779-
let p_scalar_bits = builder.biguint_bits(&p_scalar_bigint);
778+
let g_scalar_bits = g_scalar_bigint.bits;
779+
let p_scalar_bits = p_scalar_bigint.bits;
780780
let e = builder.constant_point((&n2) * g + (&n3) * p);
781781
let f = builder.linear_combination_points(&g_scalar_bits, &p_scalar_bits, &g_tgt, &p_tgt);
782782
builder.connect_point(&e, &f);

src/backends/plonky2/primitives/ec/schnorr.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ impl SignatureTarget {
117117
public_key: &PointTarget,
118118
) -> BoolTarget {
119119
let g = builder.constant_point(Point::generator());
120-
let sig1_bits = builder.biguint_bits(&self.s);
121-
let sig2_bits = builder.biguint_bits(&self.e);
120+
let sig1_bits = self.s.bits;
121+
let sig2_bits = self.e.bits;
122122
let r = builder.linear_combination_points(&sig1_bits, &sig2_bits, &g, public_key);
123123
let u_arr = r.u.components;
124124
let inputs = u_arr.into_iter().chain(msg.elements).collect::<Vec<_>>();
125125
let e_hash = hash_array_circuit(builder, &inputs);
126126
let e = builder.field_elements_to_biguint(&e_hash);
127-
builder.is_equal_slice(&self.e.0, &e.0)
127+
builder.is_equal_slice(&self.e.limbs, &e.limbs)
128128
}
129129
}
130130

@@ -316,6 +316,7 @@ mod test {
316316
let int_const = builder.constant_biguint320(&hash_int);
317317
let int_circuit = builder.field_elements_to_biguint(&hash_const);
318318
builder.connect_biguint320(&int_const, &int_circuit);
319+
println!("{}", builder.num_gates());
319320
let pw = PartialWitness::new();
320321
let data = builder.build::<PoseidonGoldilocksConfig>();
321322
let proof = data.prove(pw)?;

0 commit comments

Comments
 (0)