Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add VariableByteArray #88

Merged
merged 9 commits into from
Aug 18, 2023
67 changes: 67 additions & 0 deletions halo2-base/src/safe_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,55 @@ pub type SafeAddress<F> = SafeType<F, 1, 160>;
/// SafeType for bytes32.
pub type SafeBytes32<F> = SafeType<F, 1, 256>;

/// Represents a variable length byte array as a vector of AssignedValue<F>'s.
///
/// The number of elements within the array may vary in circuit from `0..max_var_len`, with the assigned witness `var_len`
/// representing in circuit the number of significant elements present within the byte array from `0..=var_len`.
///
/// * Range checks that each AssignedValue<F> of the vector is within byte range 0~255.
/// * Asserts that bytes.len() == max_var_len and var_len < max_var_len.
///
/// Note: The minimum value of max_var_len (and length of the byte array) is 1 i.e. for var_len = 32, max_var_len must be 33.
#[derive(Debug, Clone)]
pub struct VariableByteArray<F: ScalarField> {
jonathanpwang marked this conversation as resolved.
Show resolved Hide resolved
/// Vector of AssignedValue<F>'s witnesses representing the byte array.
bytes: Vec<AssignedValue<F>>,
/// AssignedValue<F> witness representing the variable elements within the byte array from 0..var_len.
pub var_len: AssignedValue<F>,
/// Maximum length of the byte array and the number of elements it must contain.
pub max_var_len: usize,
}

impl<F: ScalarField> VariableByteArray<F> {

fn new(bytes: Vec<AssignedValue<F>>, var_len: AssignedValue<F>, max_var_len: usize) -> Self {
assert!(bytes.len() == max_var_len, "len of bytes must equal max_var_len");
//Change this to constraint
Self { bytes, var_len, max_var_len}
}

pub fn var_len_to_usize(&self) -> usize {
self.var_len.value().get_lower_32() as usize
}

pub fn bytes(&self) -> &[AssignedValue<F>] {
&self.bytes.as_slice()
}

pub fn len(&self) -> usize {
self.bytes.len()
}
}

impl<F: ScalarField> IntoIterator for VariableByteArray<F> {
type Item = AssignedValue<F>;
type IntoIter = ::std::vec::IntoIter<AssignedValue<F>>;

fn into_iter(self) -> Self::IntoIter {
self.bytes.into_iter()
}
}

/// Chip for SafeType
pub struct SafeTypeChip<'a, F: ScalarField> {
range_chip: &'a RangeChip<F>,
Expand Down Expand Up @@ -131,6 +180,24 @@ impl<'a, F: ScalarField> SafeTypeChip<'a, F> {
SafeType::<F, BYTES_PER_ELE, TOTAL_BITS>::new(value)
}

/// Converts a vector of AssignedValue(treated as little-endian) to VariableAssignedBytes.
///
/// * ctx: Circuit [Context]<F> to assign witnesses to.
/// * inputs: Vector of [RawAssignedValues]<F> representing the byte array.
/// * var_len: [AssignedValue]<F> witness representing the variable elements within the byte array from 0..=var_len.
/// * max_var_len: [usize] representing the maximum length of the byte array and the number of elements it must contain.
pub fn raw_var_bytes_to(
&self,
ctx: &mut Context<F>,
inputs: RawAssignedValues<F>,
var_len: AssignedValue<F>,
max_var_len: usize,
) -> VariableByteArray<F> {
self.add_bytes_constraints(ctx, &inputs, BITS_PER_BYTE * max_var_len);
self.range_chip.check_less_than_safe(ctx, var_len, max_var_len as u64);
VariableByteArray::new(inputs, var_len, max_var_len)
}

fn add_bytes_constraints(
&self,
ctx: &mut Context<F>,
Expand Down
2 changes: 2 additions & 0 deletions halo2-base/src/safe_types/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub (crate) mod var_byte_array;
pub (crate) mod safe_type;
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ use crate::{
plonk::keygen_pk,
plonk::{keygen_vk, Assigned},
},
safe_types::*,
};
use rand::rngs::OsRng;
use itertools::Itertools;
use super::*;
use std::env;

/// helper function to generate a proof with real prover
Expand Down
166 changes: 166 additions & 0 deletions halo2-base/src/safe_types/tests/var_byte_array.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use crate::{
safe_types::SafeTypeChip,
gates::{RangeChip, builder::{RangeCircuitBuilder, GateThreadBuilder}},
halo2_proofs::{
halo2curves::bn256::{Fr, Bn256, G1Affine},
dev::MockProver,
plonk::{keygen_pk, keygen_vk, create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey},
poly::commitment::ParamsProver,
poly::kzg::{
commitment::KZGCommitmentScheme, commitment::ParamsKZG, multiopen::ProverSHPLONK,
multiopen::VerifierSHPLONK, strategy::SingleStrategy,
},
transcript::{
Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer,
},
},
};
use rand::rngs::OsRng;
use std::{env::set_var, vec};

// =========== Mock Prover ===========

// Circuit Satisfied for valid inputs
#[test]
fn pos_var_assigned_bytes() {
set_var("LOOKUP_BITS", "8");
let mut builder = GateThreadBuilder::mock();
let range = RangeChip::default(8);
let safe = SafeTypeChip::new(&range);
let ctx = builder.main(0);
let fake_bytes = ctx.assign_witnesses(vec![255u64, 255u64, 255u64, 255u64].into_iter().map(Fr::from).collect::<Vec<_>>());
let var_len = ctx.load_witness(Fr::from(3u64));
let max_var_len = 4;
safe.raw_var_bytes_to(ctx, fake_bytes, var_len, max_var_len);
builder.config(10, Some(9));
let circuit = RangeCircuitBuilder::mock(builder);
MockProver::run(10 as u32, &circuit, vec![]).unwrap().assert_satisfied();
}

// Checks circuit is unsatisfied for AssignedValue<F>'s are not in range 0..256
#[test]
#[should_panic(expected = "circuit was not satisfied")]
fn witness_values_not_bytes() {
let mut builder = GateThreadBuilder::mock();
let range = RangeChip::default(8);
let safe = SafeTypeChip::new(&range);
let ctx = builder.main(0);
let var_len = ctx.load_witness(Fr::from(3u64));
let max_var_len = 4;
let fake_bytes = ctx.assign_witnesses(vec![500u64, 500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::<Vec<_>>());
safe.raw_var_bytes_to(ctx, fake_bytes, var_len, max_var_len);
builder.config(10, Some(9));
let circuit = RangeCircuitBuilder::mock(builder);
MockProver::run(10 as u32, &circuit, vec![]).unwrap().assert_satisfied();
}

//Checks assertion max_var_len == bytes.len()
#[test]
#[should_panic(expected = "len of value must equal max_var_len")]
fn bytes_len_not_equal_max_var_len() {
let mut builder = GateThreadBuilder::mock();
let range = RangeChip::default(8);
let safe = SafeTypeChip::new(&range);
let ctx = builder.main(0);
let var_len = ctx.load_witness(Fr::from(3u64));
let max_var_len = 4;
let fake_bytes = ctx.assign_witnesses(vec![500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::<Vec<_>>());
safe.raw_var_bytes_to(ctx, fake_bytes, var_len, max_var_len);
}

//Checks assertion var_len < max_var_len
#[test]
#[should_panic(expected = "circuit was not satisfied")]
fn neg_var_len_less_than_max_var_len() {
let mut builder = GateThreadBuilder::mock();
let range = RangeChip::default(8);
let safe = SafeTypeChip::new(&range);
let ctx = builder.main(0);
let var_len = ctx.load_witness(Fr::from(4u64));
let max_var_len = 4;
let fake_bytes = ctx.assign_witnesses(vec![500u64, 500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::<Vec<_>>());
safe.raw_var_bytes_to(ctx, fake_bytes, var_len, max_var_len);
builder.config(10, Some(9));
let circuit = RangeCircuitBuilder::mock(builder);
MockProver::run(10 as u32, &circuit, vec![]).unwrap().assert_satisfied();
}

// =========== Prover ===========
#[test]
fn pos_prover_satisfied() {
let keygen_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3, 4);
let proof_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3, 4);
prover_satisfied(keygen_inputs, proof_inputs).unwrap();
}

#[test]
fn pos_diff_var_len_same_max_len() {
let keygen_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3, 4);
let proof_inputs = (vec![1u64, 2u64, 3u64, 4u64], 2, 4);
prover_satisfied(keygen_inputs, proof_inputs).unwrap();
}

#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: ConstraintSystemFailure")]
fn neg_different_proof_max_var_len() {
let keygen_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3, 4);
let proof_inputs = (vec![1u64, 2u64, 3u64], 2, 3);
prover_satisfied(keygen_inputs, proof_inputs).unwrap();
}

//test circuit
fn var_byte_array_circuit(k: usize, phase: bool, (bytes, var_len, max_var_len): (Vec<u64>, usize, usize)) -> RangeCircuitBuilder<Fr> {
let lookup_bits = 3;
set_var("LOOKUP_BITS", lookup_bits.to_string());
let k = 11;
let mut builder = match phase {
true => GateThreadBuilder::prover(),
false => GateThreadBuilder::keygen(),
};
let range = RangeChip::<Fr>::default(lookup_bits);
let safe = SafeTypeChip::new(&range);
let ctx = builder.main(0);
let var_len = ctx.load_witness(Fr::from(var_len as u64));
let fake_bytes = ctx.assign_witnesses(bytes.into_iter().map(Fr::from).collect::<Vec<_>>());
safe.raw_var_bytes_to(ctx, fake_bytes, var_len, max_var_len);
builder.config(k, Some(9));
let circuit = match phase {
true => RangeCircuitBuilder::prover(builder, vec![vec![]]),
false => RangeCircuitBuilder::keygen(builder),
};
circuit
}

//Prover test
fn prover_satisfied(keygen_inputs: (Vec<u64>, usize, usize), proof_inputs: (Vec<u64>, usize, usize)) -> Result<(), Box<dyn std::error::Error>> {
let k = 11;
let rng = OsRng;
let params = ParamsKZG::<Bn256>::setup(k as u32, rng);
let keygen_circuit = var_byte_array_circuit(k, false, keygen_inputs);
let vk = keygen_vk(&params, &keygen_circuit).unwrap();
let pk = keygen_pk(&params, vk, &keygen_circuit).unwrap();
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);

let proof_circuit = var_byte_array_circuit(k, true, proof_inputs);
create_proof::<
nyunyunyunyu marked this conversation as resolved.
Show resolved Hide resolved
KZGCommitmentScheme<Bn256>,
ProverSHPLONK<'_, Bn256>,
Challenge255<G1Affine>,
_,
Blake2bWrite<Vec<u8>, G1Affine, Challenge255<G1Affine>>,
_,
>(&params, &pk, &[proof_circuit], &[&[]], rng, &mut transcript)?;
let proof = transcript.finalize();

let verifier_params = params.verifier_params();
let strategy = SingleStrategy::new(&params);
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
verify_proof::<
KZGCommitmentScheme<Bn256>,
VerifierSHPLONK<'_, Bn256>,
Challenge255<G1Affine>,
Blake2bRead<&[u8], G1Affine, Challenge255<G1Affine>>,
SingleStrategy<'_, Bn256>,
>(verifier_params, pk.get_vk(), strategy, &[&[]], &mut transcript).unwrap();
Ok(())
}