Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.

Commit 0230b6a

Browse files
Handle base conversion nicer (#281)
* refactor: use more native biguint radix api * simplify inspect
1 parent 412c28e commit 0230b6a

File tree

6 files changed

+154
-186
lines changed

6 files changed

+154
-186
lines changed

keccak256/src/arith_helpers.rs

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
use crate::common::State;
22
use itertools::Itertools;
33
use num_bigint::BigUint;
4-
use num_traits::{One, Zero};
4+
use num_traits::Zero;
55
use pairing::arithmetic::FieldExt;
66
use pairing::bn256::Fr as Fp;
77
use std::ops::{Index, IndexMut};
88

9-
pub const B2: u64 = 2;
10-
pub const B13: u64 = 13;
11-
pub const B9: u64 = 9;
9+
pub const B2: u8 = 2;
10+
pub const B13: u8 = 13;
11+
pub const B9: u8 = 9;
1212

1313
/// Base 9 coef mapper scalers
1414
/// f_logic(x1, x2, x3, x4) = x1 ^ (!x2 & x3) ^ x4
1515
/// f_arith(x1, x2, x3, x4) = 2*x1 + x2 + 3*x3 + 2*x4
1616
/// where x1, x2, x3, x4 are binary.
1717
/// We have the property that `0 <= f_arith(...) < 9` and
1818
/// the map from `f_arith(...)` to `f_logic(...)` is injective.
19-
pub const A1: u64 = 2u64;
20-
pub const A2: u64 = 1u64;
21-
pub const A3: u64 = 3u64;
22-
pub const A4: u64 = 2u64;
19+
pub const A1: u8 = 2;
20+
pub const A2: u8 = 1;
21+
pub const A3: u8 = 3;
22+
pub const A4: u8 = 2;
2323

2424
pub type Lane13 = BigUint;
2525
pub type Lane9 = BigUint;
@@ -87,10 +87,6 @@ impl Clone for StateBigInt {
8787
}
8888
}
8989

90-
pub fn mod_u64(a: &BigUint, b: u64) -> u64 {
91-
(a % b).iter_u64_digits().take(1).next().unwrap_or(0)
92-
}
93-
9490
pub fn convert_b2_to_b13(a: u64) -> Lane13 {
9591
let mut lane13: BigUint = Zero::zero();
9692
for i in 0..64 {
@@ -117,7 +113,7 @@ pub fn convert_b2_to_b9(a: u64) -> Lane9 {
117113
///
118114
/// For example, if we have 5 bits set and 7 bits unset, then we have `x` as 5
119115
/// and the xor result to be 1.
120-
pub fn convert_b13_coef(x: u64) -> u64 {
116+
pub fn convert_b13_coef(x: u8) -> u8 {
121117
assert!(x < 13);
122118
x & 1
123119
}
@@ -127,55 +123,49 @@ pub fn convert_b13_coef(x: u64) -> u64 {
127123
///
128124
/// The input `x` is a chunk of a base 9 number and it represents the arithmatic
129125
/// result of `2*a + b + 3*c + 2*d`, where `a`, `b`, `c`, and `d` each is a bit.
130-
pub fn convert_b9_coef(x: u64) -> u64 {
126+
pub fn convert_b9_coef(x: u8) -> u8 {
131127
assert!(x < 9);
132-
let bit_table: [u64; 9] = [0, 0, 1, 1, 0, 0, 1, 1, 0];
128+
let bit_table: [u8; 9] = [0, 0, 1, 1, 0, 0, 1, 1, 0];
133129
bit_table[x as usize]
134130
}
135131

132+
// We assume the input comes from Theta step and has 65 chunks
136133
pub fn convert_b13_lane_to_b9(x: Lane13, rot: u32) -> Lane9 {
137-
let mut base = BigUint::from(B9).pow(rot);
138-
let mut special_chunk = Zero::zero();
139-
let mut raw = x;
140-
let mut acc: Lane9 = Zero::zero();
141-
142-
for i in 0..65 {
143-
let remainder: u64 = mod_u64(&raw, B13);
144-
if i == 0 || i == 64 {
145-
special_chunk += remainder;
146-
} else {
147-
acc += convert_b13_coef(remainder) * base.clone();
148-
}
149-
raw /= B13;
150-
base *= B9;
151-
if i == 63 - rot {
152-
base = One::one();
153-
}
154-
}
155-
acc += convert_b13_coef(special_chunk) * BigUint::from(B9).pow(rot);
156-
acc
134+
// 65 chunks
135+
let mut chunks = x.to_radix_le(B13.into());
136+
chunks.resize(65, 0);
137+
// 0 and 64 was separated in Theta, we now combined them together
138+
let special = chunks.get(0).unwrap() + chunks.get(64).unwrap();
139+
// middle 63 chunks
140+
let middle = chunks.get(1..64).unwrap();
141+
// split at offset
142+
let (left, right) = middle.split_at(63 - rot as usize);
143+
// rotated has 64 chunks
144+
// left is rotated right, and the right is wrapped over to left
145+
// with the special chunk in the middle
146+
let rotated: Vec<u8> = right
147+
.iter()
148+
.chain(vec![special].iter())
149+
.chain(left.iter())
150+
.map(|&x| convert_b13_coef(x))
151+
.collect_vec();
152+
BigUint::from_radix_le(&rotated, B9.into()).unwrap_or_default()
157153
}
158154

159155
pub fn convert_lane<F>(
160156
lane: BigUint,
161-
from_base: u64,
162-
to_base: u64,
157+
from_base: u8,
158+
to_base: u8,
163159
coef_transform: F,
164160
) -> BigUint
165161
where
166-
F: Fn(u64) -> u64,
162+
F: Fn(u8) -> u8,
167163
{
168-
let mut base: BigUint = One::one();
169-
let mut raw = lane;
170-
let mut acc: BigUint = Zero::zero();
171-
172-
for _ in 0..64 {
173-
let remainder: u64 = mod_u64(&raw, B9);
174-
acc += coef_transform(remainder) * base.clone();
175-
raw /= from_base;
176-
base *= to_base;
177-
}
178-
acc
164+
let chunks = lane.to_radix_be(from_base.into());
165+
let converted_chunks: Vec<u8> =
166+
chunks.iter().map(|&x| coef_transform(x)).collect();
167+
BigUint::from_radix_be(&converted_chunks, to_base.into())
168+
.unwrap_or_default()
179169
}
180170

181171
pub fn convert_b9_lane_to_b13(x: Lane9) -> Lane13 {
@@ -215,17 +205,11 @@ pub fn big_uint_to_field<F: FieldExt>(a: &BigUint) -> F {
215205

216206
/// This function allows us to inpect coefficients of big-numbers in different
217207
/// bases.
218-
pub fn inspect(x: BigUint, name: &str, base: u64) {
219-
let mut raw = x.clone();
220-
let mut info: Vec<(u32, u64)> = vec![];
221-
222-
for i in 0..65 {
223-
let remainder: u64 = mod_u64(&raw, base);
224-
raw /= base;
225-
if remainder != 0 {
226-
info.push((i, remainder));
227-
}
228-
}
208+
pub fn inspect(x: BigUint, name: &str, base: u8) {
209+
let mut chunks = x.to_radix_le(base.into());
210+
chunks.resize(65, 0);
211+
let info: Vec<(usize, u8)> =
212+
(0..65).zip(chunks.iter().copied()).collect_vec();
229213
println!("inspect {} {} info {:?}", name, x, info);
230214
}
231215

@@ -283,3 +267,34 @@ pub fn state_bigint_to_field<F: FieldExt, const N: usize>(
283267
arr[0..N].copy_from_slice(&vector[0..N]);
284268
arr
285269
}
270+
271+
pub fn f_from_radix_be<F: FieldExt>(buf: &[u8], base: u8) -> F {
272+
let base = F::from(base.into());
273+
buf.iter()
274+
.fold(F::zero(), |acc, &x| acc * base + F::from(x.into()))
275+
}
276+
277+
#[cfg(test)]
278+
mod tests {
279+
use super::*;
280+
use num_bigint::BigUint;
281+
#[test]
282+
fn test_convert_b13_lane_to_b9() {
283+
// the number 1 is chosen that `convert_b13_coef` has no effect
284+
let mut a = vec![0, 1, 1, 1];
285+
a.resize(65, 0);
286+
let lane = BigUint::from_radix_le(&a, B13.into()).unwrap_or_default();
287+
assert_eq!(
288+
convert_b13_lane_to_b9(lane.clone(), 0),
289+
BigUint::from_radix_le(&a, B9.into()).unwrap_or_default()
290+
);
291+
292+
// rotate by 4
293+
let mut b = vec![0, 0, 0, 0, 0, 1, 1, 1];
294+
b.resize(65, 0);
295+
assert_eq!(
296+
convert_b13_lane_to_b9(lane, 4),
297+
BigUint::from_radix_le(&b, B9.into()).unwrap_or_default()
298+
);
299+
}
300+
}

keccak256/src/gates/gate_helpers.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ pub fn f_to_biguint<F: FieldExt>(x: F) -> Option<BigUint> {
2929
Option::from(BigUint::from_bytes_le(&x.to_bytes()[..]))
3030
}
3131

32-
pub fn biguint_mod(x: &BigUint, modulus: u64) -> u64 {
33-
match (x % modulus).to_u64_digits().first() {
34-
Some(d) => *d,
35-
None => 0u64,
36-
}
32+
pub fn biguint_mod(x: &BigUint, modulus: u8) -> u8 {
33+
x.to_radix_le(modulus.into())
34+
.first()
35+
.copied()
36+
.unwrap_or_default()
3737
}

keccak256/src/gates/rho_checks.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,16 @@ struct RotatingVariables {
178178
block_count: Option<u32>,
179179
// step2 acc and step3 acc
180180
block_count_acc: [u32; 2],
181-
low_value: u64,
182-
high_value: Option<u64>,
181+
low_value: u8,
182+
high_value: Option<u8>,
183183
}
184184

185185
impl RotatingVariables {
186186
fn from(lane_base_13: BigUint, rotation: u32) -> Result<Self, Error> {
187187
let chunk_idx = 1;
188188
let step = get_step_size(chunk_idx, rotation);
189-
let input_raw = lane_base_13.clone() / B13;
190-
let input_coef = input_raw.clone() % B13.pow(step);
189+
let input_raw = lane_base_13.clone() / (B13 as u64);
190+
let input_coef = input_raw.clone() % (B13 as u64).pow(step);
191191
let input_power_of_base = BigUint::from(B13);
192192
let input_acc = lane_base_13.clone();
193193
let (block_count, output_coef) =
@@ -204,7 +204,7 @@ impl RotatingVariables {
204204
if step == 2 || step == 3 {
205205
block_count_acc[(step - 2) as usize] += block_count;
206206
}
207-
let low_value: u64 = biguint_mod(&lane_base_13, B13);
207+
let low_value = biguint_mod(&lane_base_13, B13);
208208
Ok(Self {
209209
rotation,
210210
chunk_idx,
@@ -228,16 +228,18 @@ impl RotatingVariables {
228228
let mut new = self.clone();
229229
new.chunk_idx += self.step;
230230
new.step = get_step_size(new.chunk_idx, self.rotation);
231-
new.input_raw /= B13.pow(self.step);
232-
new.input_coef = new.input_raw.clone() % B13.pow(new.step);
233-
new.input_power_of_base *= B13.pow(self.step);
231+
let input_pow_of_step = (B13 as u64).pow(self.step);
232+
new.input_raw /= input_pow_of_step;
233+
new.input_coef = new.input_raw.clone() % (B13 as u64).pow(new.step);
234+
new.input_power_of_base *= input_pow_of_step;
234235
new.input_acc -=
235236
self.input_power_of_base.clone() * self.input_coef.clone();
237+
let output_pow_of_step = (B9 as u64).pow(self.step);
236238
new.output_power_of_base =
237239
if is_at_rotation_offset(new.chunk_idx, self.rotation) {
238240
BigUint::one()
239241
} else {
240-
self.output_power_of_base.clone() * B9.pow(self.step)
242+
self.output_power_of_base.clone() * output_pow_of_step
241243
};
242244
new.output_acc +=
243245
self.output_power_of_base.clone() * self.output_coef.clone();
@@ -250,11 +252,11 @@ impl RotatingVariables {
250252
);
251253
new.block_count = None;
252254
new.high_value = Some(biguint_mod(&self.input_raw, B13));
253-
let high = new.high_value.unwrap();
255+
let high: u8 = new.high_value.unwrap();
254256
new.output_coef =
255257
BigUint::from(convert_b13_coef(high + self.low_value));
256-
let expect =
257-
new.low_value + high * BigUint::from(B13).pow(LANE_SIZE);
258+
let expect = (new.low_value as u64)
259+
+ (high as u64) * BigUint::from(B13).pow(LANE_SIZE);
258260
assert_eq!(
259261
new.input_acc,
260262
expect,
@@ -431,7 +433,8 @@ impl<F: FieldExt> ChunkRotateConversionConfig<F> {
431433
fix_cols,
432434
);
433435

434-
let power_of_b13 = F::from(B13).pow(&[chunk_idx.into(), 0, 0, 0]);
436+
let power_of_b13 =
437+
F::from(B13.into()).pow(&[chunk_idx.into(), 0, 0, 0]);
435438

436439
// | coef | 13**x | acc |
437440
// |------|-------|-----------|
@@ -451,7 +454,7 @@ impl<F: FieldExt> ChunkRotateConversionConfig<F> {
451454
q_enable * (acc_next - acc + coef * power_of_base),
452455
)]
453456
});
454-
let power_of_b9 = F::from(B9).pow(&[
457+
let power_of_b9 = F::from(B9.into()).pow(&[
455458
((rotation + chunk_idx) % LANE_SIZE).into(),
456459
0,
457460
0,
@@ -573,8 +576,9 @@ impl<F: FieldExt> SpecialChunkConfig<F> {
573576
.query_advice(base_9_acc, Rotation::next())
574577
- meta.query_advice(base_9_acc, Rotation::cur());
575578
let last_b9_coef = meta.query_advice(last_b9_coef, Rotation::cur());
576-
let pow_of_9 =
577-
Expression::Constant(F::from(B9).pow(&[rotation, 0, 0, 0]));
579+
let pow_of_9 = Expression::Constant(
580+
F::from(B9.into()).pow(&[rotation, 0, 0, 0]),
581+
);
578582
vec![(
579583
"delta_base_9_acc === (high_value + low_value) * 9**rotation",
580584
meta.query_selector(q_enable)

keccak256/src/gates/rho_helpers.rs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::{
22
arith_helpers::{convert_b13_coef, B13, B9},
33
common::LANE_SIZE,
4-
gates::gate_helpers::biguint_mod,
54
};
65

76
use std::convert::TryInto;
@@ -72,7 +71,7 @@ pub const STEP3_RANGE: u64 = 169;
7271

7372
/// Get the block count from an input chunks
7473
///
75-
/// The input is chunks of a base 13 number in descending order of signiticance.
74+
/// The input is chunks of a base 13 number in big endian.
7675
/// For example, if the input is `[1, 12, 3, 7]`, it represents a coefficient
7776
/// `1*13^3 + 12*13^2 + 3*13 + 7`. The example only happens when `step = 4`. If
7877
/// we have a `step = 3`, the first chunk must be 0. It could be the case that
@@ -84,7 +83,7 @@ pub const STEP3_RANGE: u64 = 169;
8483
/// block count to be 170.
8584
///
8685
/// This would fail the final block count check.
87-
pub fn get_block_count(b13_chunks: [u64; BASE_NUM_OF_CHUNKS as usize]) -> u32 {
86+
pub fn get_block_count(b13_chunks: [u8; BASE_NUM_OF_CHUNKS as usize]) -> u32 {
8887
// could be 0, 1, 2, 3, 4
8988
let non_zero_chunk_count = BASE_NUM_OF_CHUNKS as usize
9089
- b13_chunks.iter().take_while(|x| **x == 0).count();
@@ -93,20 +92,13 @@ pub fn get_block_count(b13_chunks: [u64; BASE_NUM_OF_CHUNKS as usize]) -> u32 {
9392
}
9493

9594
pub fn get_block_count_and_output_coef(input_coef: BigUint) -> (u32, u64) {
96-
// b13_chunks is in descending order of more significant chunk
97-
let b13_chunks: [u64; BASE_NUM_OF_CHUNKS as usize] = {
98-
let mut x = input_coef;
99-
let mut b13_chunks: Vec<u64> = vec![];
100-
for _ in 0..BASE_NUM_OF_CHUNKS {
101-
b13_chunks.push(biguint_mod(&x, B13));
102-
x /= B13;
103-
}
104-
b13_chunks.reverse();
105-
b13_chunks.try_into().unwrap()
106-
};
107-
let output_coef: u64 = b13_chunks.iter().fold(0, |acc, b13_chunk| {
108-
let b9_chunk = convert_b13_coef(*b13_chunk);
109-
acc * B9 + b9_chunk
95+
let mut b13_chunks = input_coef.to_radix_le(B13.into());
96+
b13_chunks.resize(BASE_NUM_OF_CHUNKS as usize, 0);
97+
b13_chunks.reverse();
98+
let b13_chunks: [u8; BASE_NUM_OF_CHUNKS as usize] =
99+
b13_chunks.try_into().unwrap();
100+
let output_coef: u64 = b13_chunks.iter().fold(0, |acc, &b13_chunk| {
101+
acc * B9 as u64 + convert_b13_coef(b13_chunk) as u64
110102
});
111103
let block_count = get_block_count(b13_chunks);
112104
(block_count, output_coef)

0 commit comments

Comments
 (0)