snarkVM
snarkVM copied to clipboard
[Proposal] `to_field_element` bit packing instead of byte packing
💥 Proposal
Currently, converting a [u8] into Vec<F> uses byte-aligned indices to convert to field elements.
#[inline]
fn to_field_elements(&self) -> Result<Vec<F>, ConstraintFieldError> {
let max_size = <F as PrimeField>::Parameters::CAPACITY / 8;
let max_size = max_size as usize;
let fes = self
.chunks(max_size)
.map(|chunk| {
let mut chunk = chunk.to_vec();
chunk.resize(max_size + 1, 0u8);
F::read_le(chunk.as_slice())
})
.collect::<Result<Vec<_>, _>>()?;
Ok(fes)
}
So if a field modulus is 383 bits, the current logic would occupy up to 376 bits and leave 6 bits unused. (We cannot use the 383rd-bit given the value could lie outside the modulus)
Instead, one could bit-align it up to the penultimate bit, to better use the space available (hacky pseudocode):
#[inline]
fn to_field_elements(&self) -> Result<Vec<F>, ConstraintFieldError> {
let max_size = <F as PrimeField>::Parameters::CAPACITY;
let fes = BitIteratorLE::new(self)
.chunks(max_size)
.map(|chunk| {
F::from_repr(BigInteger::from_bititerator(chunk))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(fes)
}
It seems that the from_bititerator can be done using BigInteger's from_bits_le.
https://github.com/AleoHQ/snarkVM/blob/cb0552a8077a3dd3fcf496bbb3447ed99401e121/utilities/src/bits.rs#L50
The implementation above looks correct. Note that we also need to implement the gadget version (ToConstraintFieldGadget) for [UInt8].