Merge pull request #568 from zcash/circuit-review

Changes from Orchard circuit review
This commit is contained in:
str4d 2022-05-05 16:03:31 +01:00 committed by GitHub
commit b2e2b9b081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 147 additions and 2 deletions

View File

@ -6,6 +6,11 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `halo2_gadgets::sinsemilla::MessagePiece::from_subpieces`
- `halo2_gadgets::utilities`:
- `FieldValue` trait.
- `RangeConstrained` newtype wrapper.
## [0.1.0-beta.3] - 2022-04-06
### Changed

View File

@ -1,7 +1,7 @@
//! Gadgets for the Sinsemilla hash function.
use crate::{
ecc::{self, EccInstructions, FixedPoints},
utilities::Var,
utilities::{FieldValue, RangeConstrained, Var},
};
use group::ff::{Field, PrimeField};
use halo2_proofs::{circuit::Layouter, plonk::Error};
@ -221,6 +221,40 @@ where
let inner = chip.witness_message_piece(layouter, field_elem, num_words)?;
Ok(Self { chip, inner })
}
/// Constructs a `MessagePiece` by concatenating a sequence of [`RangeConstrained`]
/// subpieces.
///
/// # Panics
///
/// Panics if the total number of bits across the subpieces is not a multiple of the
/// word size, or if the required bitshift for any subpiece is greater than 63 bits.
pub fn from_subpieces(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
subpieces: impl IntoIterator<Item = RangeConstrained<C::Base, Option<C::Base>>>,
) -> Result<Self, Error> {
let (field_elem, total_bits) =
subpieces
.into_iter()
.fold((Some(C::Base::zero()), 0), |(acc, bits), subpiece| {
assert!(bits < 64);
let subpiece_shifted = subpiece
.inner()
.value()
.map(|v| C::Base::from(1 << bits) * v);
(
acc.zip(subpiece_shifted).map(|(a, b)| a + b),
bits + subpiece.num_bits(),
)
});
// Message must be composed of `K`-bit words.
assert_eq!(total_bits % K, 0);
let num_words = total_bits / K;
Self::from_field_elem(chip, layouter, field_elem, num_words)
}
}
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can

View File

@ -1,17 +1,36 @@
//! Utility gadgets.
use ff::PrimeFieldBits;
use ff::{Field, PrimeFieldBits};
use halo2_proofs::{
circuit::{AssignedCell, Cell, Layouter},
plonk::{Advice, Column, Error, Expression},
};
use pasta_curves::arithmetic::FieldExt;
use std::marker::PhantomData;
use std::ops::Range;
pub mod cond_swap;
pub mod decompose_running_sum;
pub mod lookup_range_check;
/// A type that has a value at either keygen or proving time.
pub trait FieldValue<F: Field> {
/// Returns the value of this type.
fn value(&self) -> Option<&F>;
}
impl<F: Field> FieldValue<F> for Option<F> {
fn value(&self) -> Option<&F> {
self.as_ref()
}
}
impl<F: Field> FieldValue<F> for AssignedCell<F, F> {
fn value(&self) -> Option<&F> {
self.value()
}
}
/// Trait for a variable in the circuit.
pub trait Var<F: FieldExt>: Clone + std::fmt::Debug + From<AssignedCell<F, F>> {
/// The cell at which this variable was allocated.
@ -59,6 +78,62 @@ pub trait UtilitiesInstructions<F: FieldExt> {
}
}
/// A type representing a range-constrained field element.
#[derive(Clone, Copy, Debug)]
pub struct RangeConstrained<F: Field, T: FieldValue<F>> {
inner: T,
num_bits: usize,
_phantom: PhantomData<F>,
}
impl<F: Field, T: FieldValue<F>> RangeConstrained<F, T> {
/// Returns the range-constrained inner type.
pub fn inner(&self) -> &T {
&self.inner
}
/// Returns the number of bits to which this cell is constrained.
pub fn num_bits(&self) -> usize {
self.num_bits
}
}
impl<F: PrimeFieldBits> RangeConstrained<F, Option<F>> {
/// Constructs a `RangeConstrained<Option<F>>` as a bitrange of the given value.
pub fn bitrange_of(value: Option<&F>, bitrange: Range<usize>) -> Self {
let num_bits = bitrange.len();
Self {
inner: value.map(|value| bitrange_subset(value, bitrange)),
num_bits,
_phantom: PhantomData::default(),
}
}
}
impl<F: Field> RangeConstrained<F, AssignedCell<F, F>> {
/// Constructs a `RangeConstrained<AssignedCell<F, F>>` without verifying that the
/// cell is correctly range constrained.
///
/// This API only exists to ease with integrating this type into existing circuits,
/// and will likely be removed in future.
pub fn unsound_unchecked(cell: AssignedCell<F, F>, num_bits: usize) -> Self {
Self {
inner: cell,
num_bits,
_phantom: PhantomData::default(),
}
}
/// Extracts the range-constrained value from this range-constrained cell.
pub fn value(&self) -> RangeConstrained<F, Option<F>> {
RangeConstrained {
inner: self.inner.value().copied(),
num_bits: self.num_bits,
_phantom: PhantomData::default(),
}
}
}
pub(crate) fn transpose_option_array<T: Copy + std::fmt::Debug, const LEN: usize>(
option_array: Option<[T; LEN]>,
) -> [Option<T>; LEN] {

View File

@ -23,6 +23,37 @@ impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
}
}
impl<F: FieldExt + PrimeFieldBits> RangeConstrained<F, AssignedCell<F, F>> {
/// Witnesses a subset of the bits in `value` and constrains them to be the correct
/// number of bits.
///
/// # Panics
///
/// Panics if `bitrange.len() >= K`.
pub fn witness_short<const K: usize>(
lookup_config: &LookupRangeCheckConfig<F, K>,
layouter: impl Layouter<F>,
value: Option<&F>,
bitrange: Range<usize>,
) -> Result<Self, Error> {
let num_bits = bitrange.len();
assert!(num_bits < K);
// Witness the subset and constrain it to be the correct number of bits.
lookup_config
.witness_short_check(
layouter,
value.map(|value| bitrange_subset(value, bitrange)),
num_bits,
)
.map(|inner| Self {
inner,
num_bits,
_phantom: PhantomData::default(),
})
}
}
/// Configuration that provides methods for a lookup range check.
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct LookupRangeCheckConfig<F: FieldExt + PrimeFieldBits, const K: usize> {