use std::{
borrow::Borrow,
cmp::{max, min},
};
use crate::{
gates::{
flex_gate::GateInstructions,
range::{RangeChip, RangeInstructions},
},
utils::ScalarField,
AssignedValue, Context,
QuantumCell::Constant,
};
use itertools::Itertools;
mod bytes;
mod primitives;
pub use bytes::*;
pub use primitives::*;
#[cfg(test)]
pub mod tests;
type RawAssignedValues<F> = Vec<AssignedValue<F>>;
const BITS_PER_BYTE: usize = 8;
#[derive(Clone, Debug)]
pub struct SafeType<F: ScalarField, const BYTES_PER_ELE: usize, const TOTAL_BITS: usize> {
value: RawAssignedValues<F>,
}
impl<F: ScalarField, const BYTES_PER_ELE: usize, const TOTAL_BITS: usize>
SafeType<F, BYTES_PER_ELE, TOTAL_BITS>
{
pub const BYTES_PER_ELE: usize = BYTES_PER_ELE;
pub const TOTAL_BITS: usize = TOTAL_BITS;
pub const VALUE_LENGTH: usize = TOTAL_BITS.div_ceil(BYTES_PER_ELE * BITS_PER_BYTE);
pub fn bits_per_ele() -> usize {
min(TOTAL_BITS, BYTES_PER_ELE * BITS_PER_BYTE)
}
fn new(raw_values: RawAssignedValues<F>) -> Self {
assert!(raw_values.len() == Self::VALUE_LENGTH, "Invalid raw values length");
Self { value: raw_values }
}
pub fn value(&self) -> &[AssignedValue<F>] {
&self.value
}
}
impl<F: ScalarField, const BYTES_PER_ELE: usize, const TOTAL_BITS: usize> AsRef<[AssignedValue<F>]>
for SafeType<F, BYTES_PER_ELE, TOTAL_BITS>
{
fn as_ref(&self) -> &[AssignedValue<F>] {
self.value()
}
}
impl<F: ScalarField, const TOTAL_BITS: usize> TryFrom<Vec<SafeByte<F>>>
for SafeType<F, 1, TOTAL_BITS>
{
type Error = String;
fn try_from(value: Vec<SafeByte<F>>) -> Result<Self, Self::Error> {
if value.len() * 8 != TOTAL_BITS {
return Err("Invalid length".to_owned());
}
Ok(Self::new(value.into_iter().map(|b| b.0).collect::<Vec<_>>()))
}
}
pub type SafeAddress<F> = SafeType<F, 1, 160>;
pub type SafeBytes32<F> = SafeType<F, 1, 256>;
pub struct SafeTypeChip<'a, F: ScalarField> {
range_chip: &'a RangeChip<F>,
}
impl<'a, F: ScalarField> SafeTypeChip<'a, F> {
pub fn new(range_chip: &'a RangeChip<F>) -> Self {
Self { range_chip }
}
pub fn raw_bytes_to<const BYTES_PER_ELE: usize, const TOTAL_BITS: usize>(
&self,
ctx: &mut Context<F>,
inputs: RawAssignedValues<F>,
) -> SafeType<F, BYTES_PER_ELE, TOTAL_BITS> {
let element_bits = SafeType::<F, BYTES_PER_ELE, TOTAL_BITS>::bits_per_ele();
let bits = TOTAL_BITS;
assert!(
inputs.len() * BITS_PER_BYTE == max(bits, BITS_PER_BYTE),
"number of bits doesn't match"
);
self.add_bytes_constraints(ctx, &inputs, bits);
if bits == 1 || element_bits == BITS_PER_BYTE {
return SafeType::<F, BYTES_PER_ELE, TOTAL_BITS>::new(inputs);
};
assert!(
element_bits <= F::CAPACITY as usize,
"packed SafeType element exceeds native field capacity"
);
let byte_base = (0..BYTES_PER_ELE)
.map(|i| Constant(self.range_chip.gate.pow_of_two[i * BITS_PER_BYTE]))
.collect::<Vec<_>>();
let value = inputs
.chunks(BYTES_PER_ELE)
.map(|chunk| {
self.range_chip.gate.inner_product(
ctx,
chunk.to_vec(),
byte_base[..chunk.len()].to_vec(),
)
})
.collect::<Vec<_>>();
SafeType::<F, BYTES_PER_ELE, TOTAL_BITS>::new(value)
}
pub fn unsafe_to_safe_type<const BYTES_PER_ELE: usize, const TOTAL_BITS: usize>(
inputs: RawAssignedValues<F>,
) -> SafeType<F, BYTES_PER_ELE, TOTAL_BITS> {
assert_eq!(inputs.len(), SafeType::<F, BYTES_PER_ELE, TOTAL_BITS>::VALUE_LENGTH);
SafeType::<F, BYTES_PER_ELE, TOTAL_BITS>::new(inputs)
}
pub fn assert_bool(&self, ctx: &mut Context<F>, input: AssignedValue<F>) -> SafeBool<F> {
self.range_chip.gate().assert_bit(ctx, input);
SafeBool(input)
}
pub fn load_bool(&self, ctx: &mut Context<F>, input: bool) -> SafeBool<F> {
let input = ctx.load_witness(F::from(input));
self.assert_bool(ctx, input)
}
pub fn unsafe_to_bool(input: AssignedValue<F>) -> SafeBool<F> {
SafeBool(input)
}
pub fn assert_byte(&self, ctx: &mut Context<F>, input: AssignedValue<F>) -> SafeByte<F> {
self.range_chip.range_check(ctx, input, BITS_PER_BYTE);
SafeByte(input)
}
pub fn load_byte(&self, ctx: &mut Context<F>, input: u8) -> SafeByte<F> {
let input = ctx.load_witness(F::from(input as u64));
self.assert_byte(ctx, input)
}
pub fn unsafe_to_byte(input: AssignedValue<F>) -> SafeByte<F> {
SafeByte(input)
}
pub fn unsafe_to_var_len_bytes<const MAX_LEN: usize>(
inputs: [AssignedValue<F>; MAX_LEN],
len: AssignedValue<F>,
) -> VarLenBytes<F, MAX_LEN> {
VarLenBytes::<F, MAX_LEN>::new(inputs.map(|input| Self::unsafe_to_byte(input)), len)
}
pub fn unsafe_to_var_len_bytes_vec(
inputs: RawAssignedValues<F>,
len: AssignedValue<F>,
max_len: usize,
) -> VarLenBytesVec<F> {
VarLenBytesVec::<F>::new(
inputs.iter().map(|input| Self::unsafe_to_byte(*input)).collect_vec(),
len,
max_len,
)
}
pub fn unsafe_to_fix_len_bytes<const MAX_LEN: usize>(
inputs: [AssignedValue<F>; MAX_LEN],
) -> FixLenBytes<F, MAX_LEN> {
FixLenBytes::<F, MAX_LEN>::new(inputs.map(|input| Self::unsafe_to_byte(input)))
}
pub fn unsafe_to_fix_len_bytes_vec(
inputs: RawAssignedValues<F>,
len: usize,
) -> FixLenBytesVec<F> {
FixLenBytesVec::<F>::new(
inputs.into_iter().map(|input| Self::unsafe_to_byte(input)).collect_vec(),
len,
)
}
pub fn raw_to_var_len_bytes<const MAX_LEN: usize>(
&self,
ctx: &mut Context<F>,
inputs: [AssignedValue<F>; MAX_LEN],
len: AssignedValue<F>,
) -> VarLenBytes<F, MAX_LEN> {
self.range_chip.check_less_than_safe(ctx, len, MAX_LEN as u64 + 1);
VarLenBytes::<F, MAX_LEN>::new(inputs.map(|input| self.assert_byte(ctx, input)), len)
}
pub fn raw_to_var_len_bytes_vec(
&self,
ctx: &mut Context<F>,
inputs: RawAssignedValues<F>,
len: AssignedValue<F>,
max_len: usize,
) -> VarLenBytesVec<F> {
self.range_chip.check_less_than_safe(ctx, len, max_len as u64 + 1);
VarLenBytesVec::<F>::new(
inputs.iter().map(|input| self.assert_byte(ctx, *input)).collect_vec(),
len,
max_len,
)
}
pub fn raw_to_fix_len_bytes<const LEN: usize>(
&self,
ctx: &mut Context<F>,
inputs: [AssignedValue<F>; LEN],
) -> FixLenBytes<F, LEN> {
FixLenBytes::<F, LEN>::new(inputs.map(|input| self.assert_byte(ctx, input)))
}
pub fn raw_to_fix_len_bytes_vec(
&self,
ctx: &mut Context<F>,
inputs: RawAssignedValues<F>,
len: usize,
) -> FixLenBytesVec<F> {
FixLenBytesVec::<F>::new(
inputs.into_iter().map(|input| self.assert_byte(ctx, input)).collect_vec(),
len,
)
}
fn add_bytes_constraints(
&self,
ctx: &mut Context<F>,
inputs: &RawAssignedValues<F>,
bits: usize,
) {
let mut bits_left = bits;
for input in inputs {
let num_bit = min(bits_left, BITS_PER_BYTE);
self.range_chip.range_check(ctx, *input, num_bit);
bits_left -= num_bit;
}
}
}