Skip to content

Commit

Permalink
[feat] add bits_to_num and limbs_to_num (#222)
Browse files Browse the repository at this point in the history
* feat: add bits_to_num function

* feat: add limbs_to_num function

* feat(test): add unit tests for *_to_num

* fix: capacity is already bits - 1

* fix: run clippy
  • Loading branch information
shuklaayush authored Nov 26, 2023
1 parent c56acf0 commit 400122a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 0 deletions.
20 changes: 20 additions & 0 deletions halo2-base/src/gates/flex_gate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,12 @@ pub trait GateInstructions<F: ScalarField> {
range_bits: usize,
) -> Vec<AssignedValue<F>>;

/// Constrains and returns field representation of little-endian bit vector `bits`.
///
/// Assumes values of `bits` are boolean.
/// * `bits`: slice of [QuantumCell]'s that contains bit representation in little-endian form
fn bits_to_num(&self, ctx: &mut Context<F>, bits: &[AssignedValue<F>]) -> AssignedValue<F>;

/// Constrains and computes `a`<sup>`exp`</sup> where both `a, exp` are witnesses. The exponent is computed in the native field `F`.
///
/// Constrains that `exp` has at most `max_bits` bits.
Expand Down Expand Up @@ -1269,6 +1275,20 @@ impl<F: ScalarField> GateInstructions<F> for GateChip<F> {
bit_cells
}

/// Constrains and returns field representation of little-endian bit vector `bits`.
///
/// Assumes values of `bits` are boolean.
/// * `bits`: slice of [QuantumCell]'s that contains bit representation in little-endian form
fn bits_to_num(&self, ctx: &mut Context<F>, bits: &[AssignedValue<F>]) -> AssignedValue<F> {
assert!((bits.len() as u32) <= F::CAPACITY);

self.inner_product(
ctx,
bits.iter().copied(),
self.pow_of_two[..bits.len()].iter().map(|c| Constant(*c)),
)
}

/// Constrains and computes `a^exp` where both `a, exp` are witnesses. The exponent is computed in the native field `F`.
///
/// Constrains that `exp` has at most `max_bits` bits.
Expand Down
31 changes: 31 additions & 0 deletions halo2-base/src/gates/range/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,37 @@ pub trait RangeInstructions<F: ScalarField> {
limbs_le
}

/// Reconstructs a field element from `limbs` in **little** endian with each `limb` having
/// `limb_bits` bits and constrains the reconstruction holds. Returns the number.
/// More precisely, checks that:
/// ```ignore
/// num = sum_{i=0}^{num_limbs-1} limbs[i] * 2^{i * limb_bits}
/// ```
///
/// Assumes 'limbs' contains valid limbs of at most limb_bits size.
/// NOTE: There might be multiple `limbs` that represent the same number. eg. x and x + p.
/// Better to range check `limbs` before calling this function.
fn limbs_to_num(
&self,
ctx: &mut Context<F>,
limbs: &[AssignedValue<F>],
limb_bits: usize,
) -> AssignedValue<F>
where
F: BigPrimeField,
{
self.gate().inner_product(
ctx,
limbs.iter().copied(),
self.gate()
.pow_of_two()
.iter()
.step_by(limb_bits)
.take(limbs.len())
.map(|x| Constant(*x)),
)
}

/// Bitwise right rotate a by BIT bits. BIT and NUM_BITS must be determined at compile time.
///
/// Assumes 'a' is a NUM_BITS bit integer and 0 < NUM_BITS <= 128.
Expand Down
8 changes: 8 additions & 0 deletions halo2-base/src/gates/tests/flex_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ pub fn test_num_to_bits(num: usize, bits: usize) -> Vec<Fr> {
})
}

#[test_case([0,1,1].map(Fr::from).to_vec() => Fr::from(6); "bits_to_num(): [0,1,1]")]
pub fn test_bits_to_num(bits: Vec<Fr>) -> Fr {
base_test().run_gate(|ctx, chip| {
let bits = ctx.assign_witnesses(bits);
*chip.bits_to_num(ctx, bits.as_slice()).value()
})
}

#[test_case(Fr::from(3), BigUint::from(3u32), 4 => Fr::from(27); "pow_var(): 3^3 = 27")]
pub fn test_pow_var(a: Fr, exp: BigUint, max_bits: usize) -> Fr {
assert!(exp.bits() <= max_bits as u64);
Expand Down
8 changes: 8 additions & 0 deletions halo2-base/src/gates/tests/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ pub fn test_decompose_le(num: Fr, limb_bits: usize, num_limbs: usize) -> Vec<Fr>
chip.decompose_le(ctx, num, limb_bits, num_limbs).iter().map(|x| *x.value()).collect()
})
}

#[test_case([0x4, 0x3, 0x2, 0x1].map(Fr::from).to_vec(), 4 => Fr::from(0x1234); "limbs_to_num([0x4, 0x3, 0x2, 0x1], 4)")]
pub fn test_limbs_to_num(limbs: Vec<Fr>, limb_bits: usize) -> Fr {
base_test().run(|ctx, chip| {
let limbs = ctx.assign_witnesses(limbs);
*chip.limbs_to_num(ctx, limbs.as_slice(), limb_bits).value()
})
}

0 comments on commit 400122a

Please sign in to comment.