Merge pull request #133 from zcash/patch-sinsemilla

Introduce `LookupRangeCheckConfig`s for each Sinsemilla advice column
This commit is contained in:
str4d 2021-06-29 10:43:30 +01:00 committed by GitHub
commit 9f1bd64fe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 135 deletions

View File

@ -3,8 +3,10 @@ use crate::circuit::gadget::{
ecc::{self, EccInstructions}, ecc::{self, EccInstructions},
utilities::Var, utilities::Var,
}; };
use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error}; use ff::PrimeField;
use std::fmt::Debug; use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::arithmetic::{CurveAffine, FieldExt};
use std::{convert::TryInto, fmt::Debug};
pub mod chip; pub mod chip;
mod message; mod message;
@ -27,7 +29,7 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
/// ///
/// For example, in the case `K = 10`, `NUM_BITS = 255`, we can fit /// For example, in the case `K = 10`, `NUM_BITS = 255`, we can fit
/// up to `N = 25` words in a single base field element. /// up to `N = 25` words in a single base field element.
type MessagePiece; type MessagePiece: Clone + Debug;
/// The x-coordinate of a point output of [`Self::hash_to_point`]. /// The x-coordinate of a point output of [`Self::hash_to_point`].
type X; type X;
@ -37,37 +39,6 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
/// HashDomains used in this instruction. /// HashDomains used in this instruction.
type HashDomains: HashDomains<C>; type HashDomains: HashDomains<C>;
/// Witness a message in the given bitstring.
/// Returns a vector of [`Self::MessagePiece`]s encoding the given message.
///
/// # Panics
///
/// Panics if the message length is not a multiple of `K`.
///
/// Panics if the message length exceeds `K * MAX_WORDS`.
fn witness_message(
&self,
layouter: impl Layouter<C::Base>,
message: Vec<Option<bool>>,
) -> Result<Self::Message, Error>;
/// Witnesses a message piece given a field element and the intended number of `K`-bit
/// words it contains.
///
/// Returns a [`Self::MessagePiece`] encoding the given message.
///
/// # Panics
///
/// Panics if the message length is not a multiple of `K`.
///
/// Panics if the message length exceeds the maximum number of words
/// that can fit in a field element.
fn witness_message_piece_bitstring(
&self,
layouter: impl Layouter<C::Base>,
message: &[Option<bool>],
) -> Result<Self::MessagePiece, Error>;
/// Witness a message piece given a field element. Returns a [`Self::MessagePiece`] /// Witness a message piece given a field element. Returns a [`Self::MessagePiece`]
/// encoding the given message. /// encoding the given message.
/// ///
@ -75,7 +46,7 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
/// ///
/// Panics if `num_words` exceed the maximum number of `K`-bit words that /// Panics if `num_words` exceed the maximum number of `K`-bit words that
/// can fit into a single base field element. /// can fit into a single base field element.
fn witness_message_piece_field( fn witness_message_piece(
&self, &self,
layouter: impl Layouter<C::Base>, layouter: impl Layouter<C::Base>,
value: Option<C::Base>, value: Option<C::Base>,
@ -130,24 +101,112 @@ where
{ {
fn from_bitstring( fn from_bitstring(
chip: SinsemillaChip, chip: SinsemillaChip,
layouter: impl Layouter<C::Base>, mut layouter: impl Layouter<C::Base>,
bitstring: Vec<Option<bool>>, bitstring: Vec<Option<bool>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let inner = chip.witness_message(layouter, bitstring)?; // Message must be composed of `K`-bit words.
Ok(Self { chip, inner }) assert_eq!(bitstring.len() % K, 0);
// Message must have at most `MAX_WORDS` words.
assert!(bitstring.len() / K <= MAX_WORDS);
// Message piece must be at most `ceil(C::NUM_BITS / K)` bits
let piece_num_words = C::Base::NUM_BITS as usize / K;
let pieces: Result<Vec<_>, _> = bitstring
.chunks(piece_num_words * K)
.enumerate()
.map(
|(i, piece)| -> Result<MessagePiece<C, SinsemillaChip, K, MAX_WORDS>, Error> {
MessagePiece::from_bitstring(
chip.clone(),
layouter.namespace(|| format!("message piece {}", i)),
piece,
)
},
)
.collect();
pieces.map(|pieces| Self::from_pieces(chip, pieces))
} }
/// Constructs a message from a vector of [`MessagePiece`]s. /// Constructs a message from a vector of [`MessagePiece`]s.
/// ///
/// [`MessagePiece`]: SinsemillaInstructions::MessagePiece /// [`MessagePiece`]: SinsemillaInstructions::MessagePiece
fn from_pieces(chip: SinsemillaChip, pieces: Vec<SinsemillaChip::MessagePiece>) -> Self { fn from_pieces(
chip: SinsemillaChip,
pieces: Vec<MessagePiece<C, SinsemillaChip, K, MAX_WORDS>>,
) -> Self {
Self { Self {
chip, chip,
inner: pieces.into(), inner: pieces
.into_iter()
.map(|piece| piece.inner)
.collect::<Vec<_>>()
.into(),
} }
} }
} }
#[derive(Clone, Debug)]
pub struct MessagePiece<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
chip: SinsemillaChip,
inner: SinsemillaChip::MessagePiece,
}
impl<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
MessagePiece<C, SinsemillaChip, K, MAX_WORDS>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
fn from_bitstring(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
bitstring: &[Option<bool>],
) -> Result<Self, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(bitstring.len() % K, 0);
let num_words = bitstring.len() / K;
// Message piece must be at most `ceil(C::Base::NUM_BITS / K)` bits
let piece_max_num_words = C::Base::NUM_BITS as usize / K;
assert!(num_words <= piece_max_num_words as usize);
// Closure to parse a bitstring (little-endian) into a base field element.
let to_base_field = |bits: &[Option<bool>]| -> Option<C::Base> {
assert!(bits.len() <= C::Base::NUM_BITS as usize);
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
let bytes: Option<Vec<u8>> = bits.map(|bits| {
// Pad bits to 256 bits
let pad_len = 256 - bits.len();
let mut bits = bits;
bits.extend_from_slice(&vec![false; pad_len]);
bits.chunks_exact(8)
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
.collect()
});
bytes.map(|bytes| C::Base::from_bytes(&bytes.try_into().unwrap()).unwrap())
};
let piece_value = to_base_field(bitstring);
Self::from_field_elem(chip, layouter, piece_value, num_words)
}
fn from_field_elem(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
field_elem: Option<C::Base>,
num_words: usize,
) -> Result<Self, Error> {
let inner = chip.witness_message_piece(layouter, field_elem, num_words)?;
Ok(Self { chip, inner })
}
}
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can /// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can
/// be used. /// be used.
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -242,7 +301,7 @@ mod tests {
use super::{ use super::{
chip::SinsemillaHashDomains, chip::SinsemillaHashDomains,
chip::{SinsemillaChip, SinsemillaConfig}, chip::{SinsemillaChip, SinsemillaConfig},
HashDomain, Message, SinsemillaInstructions, HashDomain, Message, MessagePiece,
}; };
use crate::{ use crate::{
@ -278,12 +337,30 @@ mod tests {
meta.advice_column(), meta.advice_column(),
]; ];
let constants = meta.fixed_column(); // TODO: Replace with public inputs API
let constants_1 = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
let constants_2 = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
let perm = meta.permutation( let perm = meta.permutation(
&advices &advices
.iter() .iter()
.map(|advice| (*advice).into()) .map(|advice| (*advice).into())
.chain(Some(constants.into())) .chain(constants_1.iter().map(|fixed| (*fixed).into()))
.chain(constants_2.iter().map(|fixed| (*fixed).into()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
@ -300,14 +377,14 @@ mod tests {
meta, meta,
advices[..5].try_into().unwrap(), advices[..5].try_into().unwrap(),
lookup, lookup,
constants, constants_1,
perm.clone(), perm.clone(),
); );
let config2 = SinsemillaChip::configure( let config2 = SinsemillaChip::configure(
meta, meta,
advices[5..].try_into().unwrap(), advices[5..].try_into().unwrap(),
lookup, lookup,
constants, constants_2,
perm, perm,
); );
(ecc_config, config1, config2) (ecc_config, config1, config2)
@ -337,13 +414,17 @@ mod tests {
// Layer 31, l = MERKLE_DEPTH_ORCHARD - 1 - layer = 0 // Layer 31, l = MERKLE_DEPTH_ORCHARD - 1 - layer = 0
let l_bitstring = vec![Some(false); K]; let l_bitstring = vec![Some(false); K];
let l = chip1 let l = MessagePiece::from_bitstring(
.witness_message_piece_bitstring(layouter.namespace(|| "l"), &l_bitstring)?; chip1.clone(),
layouter.namespace(|| "l"),
&l_bitstring,
)?;
// Left leaf // Left leaf
let left_bitstring: Vec<Option<bool>> = let left_bitstring: Vec<Option<bool>> =
(0..250).map(|_| Some(rand::random::<bool>())).collect(); (0..250).map(|_| Some(rand::random::<bool>())).collect();
let left = chip1.witness_message_piece_bitstring( let left = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "left"), layouter.namespace(|| "left"),
&left_bitstring, &left_bitstring,
)?; )?;
@ -351,7 +432,8 @@ mod tests {
// Right leaf // Right leaf
let right_bitstring: Vec<Option<bool>> = let right_bitstring: Vec<Option<bool>> =
(0..250).map(|_| Some(rand::random::<bool>())).collect(); (0..250).map(|_| Some(rand::random::<bool>())).collect();
let right = chip1.witness_message_piece_bitstring( let right = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "right"), layouter.namespace(|| "right"),
&right_bitstring, &right_bitstring,
)?; )?;

View File

@ -5,14 +5,13 @@ use super::{
use crate::{ use crate::{
circuit::gadget::{ circuit::gadget::{
ecc::chip::EccPoint, ecc::chip::EccPoint,
utilities::{CellValue, Var}, utilities::{lookup_range_check::LookupRangeCheckConfig, CellValue, Var},
}, },
primitives::sinsemilla::{ primitives::sinsemilla::{
self, Q_COMMIT_IVK_M_GENERATOR, Q_MERKLE_CRH, Q_NOTE_COMMITMENT_M_GENERATOR, self, Q_COMMIT_IVK_M_GENERATOR, Q_MERKLE_CRH, Q_NOTE_COMMITMENT_M_GENERATOR,
}, },
}; };
use ff::PrimeField;
use halo2::{ use halo2::{
arithmetic::{CurveAffine, FieldExt}, arithmetic::{CurveAffine, FieldExt},
circuit::{Chip, Layouter}, circuit::{Chip, Layouter},
@ -24,8 +23,6 @@ use halo2::{
}; };
use pasta_curves::pallas; use pasta_curves::pallas;
use std::convert::TryInto;
mod generator_table; mod generator_table;
pub use generator_table::get_s_by_idx; pub use generator_table::get_s_by_idx;
use generator_table::GeneratorTableConfig; use generator_table::GeneratorTableConfig;
@ -60,13 +57,19 @@ pub struct SinsemillaConfig {
lambda_2: Column<Advice>, lambda_2: Column<Advice>,
/// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$ /// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$
/// generators of the Sinsemilla hash. /// generators of the Sinsemilla hash.
generator_table: GeneratorTableConfig, pub(super) generator_table: GeneratorTableConfig,
/// Fixed column shared by the whole circuit. This is used to load the /// Fixed column shared by the whole circuit. This is used to load the
/// x-coordinate of the domain $Q$, which is then constrained to equal the /// x-coordinate of the domain $Q$, which is then constrained to equal the
/// initial $x_a$. /// initial $x_a$.
constants: Column<Fixed>, constants: Column<Fixed>,
/// Permutation over all advice columns and the `constants` fixed column. /// Permutation over all advice columns and the `constants` fixed column.
perm: Permutation, pub(super) perm: Permutation,
/// Configure each advice column to be able to perform lookup range checks.
pub(super) lookup_config_0: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
pub(super) lookup_config_1: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
pub(super) lookup_config_2: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
pub(super) lookup_config_3: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
pub(super) lookup_config_4: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
} }
#[derive(Eq, PartialEq, Clone, Debug)] #[derive(Eq, PartialEq, Clone, Debug)]
@ -106,7 +109,7 @@ impl SinsemillaChip {
meta: &mut ConstraintSystem<pallas::Base>, meta: &mut ConstraintSystem<pallas::Base>,
advices: [Column<Advice>; 5], advices: [Column<Advice>; 5],
lookup: (Column<Fixed>, Column<Fixed>, Column<Fixed>), lookup: (Column<Fixed>, Column<Fixed>, Column<Fixed>),
constants: Column<Fixed>, constants: [Column<Fixed>; 6], // TODO: replace with public inputs API
perm: Permutation, perm: Permutation,
) -> <Self as Chip<pallas::Base>>::Config { ) -> <Self as Chip<pallas::Base>>::Config {
let config = SinsemillaConfig { let config = SinsemillaConfig {
@ -123,7 +126,42 @@ impl SinsemillaChip {
table_x: lookup.1, table_x: lookup.1,
table_y: lookup.2, table_y: lookup.2,
}, },
constants, constants: constants[5],
lookup_config_0: LookupRangeCheckConfig::configure(
meta,
advices[0],
constants[0],
lookup.0,
perm.clone(),
),
lookup_config_1: LookupRangeCheckConfig::configure(
meta,
advices[1],
constants[1],
lookup.0,
perm.clone(),
),
lookup_config_2: LookupRangeCheckConfig::configure(
meta,
advices[2],
constants[2],
lookup.0,
perm.clone(),
),
lookup_config_3: LookupRangeCheckConfig::configure(
meta,
advices[3],
constants[3],
lookup.0,
perm.clone(),
),
lookup_config_4: LookupRangeCheckConfig::configure(
meta,
advices[4],
constants[4],
lookup.0,
perm.clone(),
),
perm, perm,
}; };
@ -225,71 +263,7 @@ impl SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }
type HashDomains = SinsemillaHashDomains; type HashDomains = SinsemillaHashDomains;
#[allow(non_snake_case)] fn witness_message_piece(
fn witness_message(
&self,
mut layouter: impl Layouter<pallas::Base>,
message: Vec<Option<bool>>,
) -> Result<Self::Message, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(message.len() % sinsemilla::K, 0);
// Message must have at most `sinsemilla::C` words.
assert!(message.len() / sinsemilla::K <= sinsemilla::C);
// Message piece must be at most `ceil(pallas::Base::NUM_BITS / sinsemilla::K)` bits
let piece_num_words = pallas::Base::NUM_BITS as usize / sinsemilla::K;
let pieces: Result<Vec<_>, _> = message
.chunks(piece_num_words * sinsemilla::K)
.enumerate()
.map(|(i, piece)| -> Result<Self::MessagePiece, Error> {
self.witness_message_piece_bitstring(
layouter.namespace(|| format!("message piece {}", i)),
piece,
)
})
.collect();
pieces.map(|pieces| pieces.into())
}
#[allow(non_snake_case)]
fn witness_message_piece_bitstring(
&self,
layouter: impl Layouter<pallas::Base>,
message_piece: &[Option<bool>],
) -> Result<Self::MessagePiece, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(message_piece.len() % sinsemilla::K, 0);
let num_words = message_piece.len() / sinsemilla::K;
// Message piece must be at most `ceil(C::Base::NUM_BITS / sinsemilla::K)` bits
let piece_max_num_words = pallas::Base::NUM_BITS as usize / sinsemilla::K;
assert!(num_words <= piece_max_num_words as usize);
// Closure to parse a bitstring (little-endian) into a base field element.
let to_base_field = |bits: &[Option<bool>]| -> Option<pallas::Base> {
assert!(bits.len() <= pallas::Base::NUM_BITS as usize);
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
let bytes: Option<Vec<u8>> = bits.map(|bits| {
// Pad bits to 256 bits
let pad_len = 256 - bits.len();
let mut bits = bits;
bits.extend_from_slice(&vec![false; pad_len]);
bits.chunks_exact(8)
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
.collect()
});
bytes.map(|bytes| pallas::Base::from_bytes(&bytes.try_into().unwrap()).unwrap())
};
let piece_value = to_base_field(message_piece);
self.witness_message_piece_field(layouter, piece_value, num_words)
}
fn witness_message_piece_field(
&self, &self,
mut layouter: impl Layouter<pallas::Base>, mut layouter: impl Layouter<pallas::Base>,
field_elem: Option<pallas::Base>, field_elem: Option<pallas::Base>,

View File

@ -34,25 +34,17 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize, const MAX_WORDS: usize> std::
/// cannot exceed the base field's `NUM_BITS`. /// cannot exceed the base field's `NUM_BITS`.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct MessagePiece<F: FieldExt, const K: usize> { pub struct MessagePiece<F: FieldExt, const K: usize> {
cell: Cell, cell_value: CellValue<F>,
field_elem: Option<F>,
/// The number of K-bit words in this message piece. /// The number of K-bit words in this message piece.
num_words: usize, num_words: usize,
} }
#[allow(clippy::from_over_into)]
impl<F: FieldExt + PrimeFieldBits, const K: usize> Into<CellValue<F>> for MessagePiece<F, K> {
fn into(self) -> CellValue<F> {
CellValue::new(self.cell(), self.field_elem())
}
}
impl<F: FieldExt + PrimeFieldBits, const K: usize> MessagePiece<F, K> { impl<F: FieldExt + PrimeFieldBits, const K: usize> MessagePiece<F, K> {
pub fn new(cell: Cell, field_elem: Option<F>, num_words: usize) -> Self { pub fn new(cell: Cell, field_elem: Option<F>, num_words: usize) -> Self {
assert!(num_words * K < F::NUM_BITS as usize); assert!(num_words * K < F::NUM_BITS as usize);
let cell_value = CellValue::new(cell, field_elem);
Self { Self {
cell, cell_value,
field_elem,
num_words, num_words,
} }
} }
@ -62,10 +54,14 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> MessagePiece<F, K> {
} }
pub fn cell(&self) -> Cell { pub fn cell(&self) -> Cell {
self.cell self.cell_value.cell()
} }
pub fn field_elem(&self) -> Option<F> { pub fn field_elem(&self) -> Option<F> {
self.field_elem self.cell_value.value()
}
pub fn cell_value(&self) -> CellValue<F> {
self.cell_value
} }
} }