mirror of https://github.com/zcash/halo2.git
Merge pull request #67 from zcash/sinsemilla-chip-config
Sinsemilla chip with HashDomain
This commit is contained in:
commit
66340e2655
|
@ -1,3 +1,4 @@
|
|||
pub(crate) mod ecc;
|
||||
pub(crate) mod poseidon;
|
||||
pub(crate) mod sinsemilla;
|
||||
pub(crate) mod utilities;
|
||||
|
|
|
@ -27,6 +27,17 @@ pub struct EccPoint {
|
|||
}
|
||||
|
||||
impl EccPoint {
|
||||
/// Constructs a point from its coordinates, without checking they are on the curve.
|
||||
///
|
||||
/// This is an internal API that we only use where we know we have a valid curve point
|
||||
/// (specifically inside Sinsemilla).
|
||||
pub(in crate::circuit::gadget) fn from_coordinates_unchecked(
|
||||
x: CellValue<pallas::Base>,
|
||||
y: CellValue<pallas::Base>,
|
||||
) -> Self {
|
||||
EccPoint { x, y }
|
||||
}
|
||||
|
||||
/// Returns the value of this curve point, if known.
|
||||
pub fn point(&self) -> Option<pallas::Affine> {
|
||||
match (self.x.value(), self.y.value()) {
|
||||
|
|
|
@ -0,0 +1,425 @@
|
|||
//! Gadget and chips for the Sinsemilla hash function.
|
||||
use crate::circuit::gadget::{
|
||||
ecc::{self, EccInstructions},
|
||||
utilities::Var,
|
||||
};
|
||||
use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub mod chip;
|
||||
mod message;
|
||||
|
||||
/// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget.
|
||||
/// This trait is bounded on two constant parameters: `K`, the number of bits
|
||||
/// in each word accepted by the Sinsemilla hash, and `MAX_WORDS`, the maximum
|
||||
/// number of words that a single hash instance can process.
|
||||
pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS: usize> {
|
||||
/// A variable in the circuit.
|
||||
type CellValue: Var<C::Base>;
|
||||
|
||||
/// A message composed of [`Self::MessagePiece`]s.
|
||||
type Message: From<Vec<Self::MessagePiece>>;
|
||||
|
||||
/// A piece in a message containing a number of `K`-bit words.
|
||||
/// A [`Self::MessagePiece`] fits in a single base field element,
|
||||
/// which means it can only contain up to `N` words, where
|
||||
/// `N*K <= C::Base::NUM_BITS`.
|
||||
///
|
||||
/// For example, in the case `K = 10`, `NUM_BITS = 255`, we can fit
|
||||
/// up to `N = 25` words in a single base field element.
|
||||
type MessagePiece;
|
||||
|
||||
/// The x-coordinate of a point output of [`Self::hash_to_point`].
|
||||
type X;
|
||||
/// A point output of [`Self::hash_to_point`].
|
||||
type Point: Clone + Debug;
|
||||
|
||||
/// HashDomains used in this instruction.
|
||||
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`]
|
||||
/// encoding the given message.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `num_words` exceed the maximum number of `K`-bit words that
|
||||
/// can fit into a single base field element.
|
||||
fn witness_message_piece_field(
|
||||
&self,
|
||||
layouter: impl Layouter<C::Base>,
|
||||
value: Option<C::Base>,
|
||||
num_words: usize,
|
||||
) -> Result<Self::MessagePiece, Error>;
|
||||
|
||||
/// Hashes a message to an ECC curve point.
|
||||
/// This returns both the resulting point, as well as the message
|
||||
/// decomposition in the form of intermediate values in a cumulative
|
||||
/// sum.
|
||||
///
|
||||
/// A cumulative sum `z` is used to decompose a Sinsemilla message. It
|
||||
/// produces intermediate values for each word in the message, such
|
||||
/// that `z_next` = (`z_cur` - `word_next`) / `2^K`.
|
||||
///
|
||||
/// These intermediate values are useful for range checks on subsets
|
||||
/// of the Sinsemilla message. Sinsemilla messages in the Orchard
|
||||
/// protocol are composed of field elements, and we need to check
|
||||
/// the canonicity of the field element encodings in certain cases.
|
||||
///
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn hash_to_point(
|
||||
&self,
|
||||
layouter: impl Layouter<C::Base>,
|
||||
Q: C,
|
||||
message: Self::Message,
|
||||
) -> Result<(Self::Point, Vec<Vec<Self::CellValue>>), Error>;
|
||||
|
||||
/// Extracts the x-coordinate of the output of a Sinsemilla hash.
|
||||
fn extract(point: &Self::Point) -> Self::X;
|
||||
}
|
||||
|
||||
/// A message to be hashed.
|
||||
///
|
||||
/// Composed of [`MessagePiece`]s with bitlength some multiple of `K`.
|
||||
///
|
||||
/// [`MessagePiece`]: SinsemillaInstructions::MessagePiece
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Message<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
|
||||
where
|
||||
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
|
||||
{
|
||||
chip: SinsemillaChip,
|
||||
inner: SinsemillaChip::Message,
|
||||
}
|
||||
|
||||
impl<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
|
||||
Message<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: Vec<Option<bool>>,
|
||||
) -> Result<Self, Error> {
|
||||
let inner = chip.witness_message(layouter, bitstring)?;
|
||||
Ok(Self { chip, inner })
|
||||
}
|
||||
|
||||
/// Constructs a message from a vector of [`MessagePiece`]s.
|
||||
///
|
||||
/// [`MessagePiece`]: SinsemillaInstructions::MessagePiece
|
||||
fn from_pieces(chip: SinsemillaChip, pieces: Vec<SinsemillaChip::MessagePiece>) -> Self {
|
||||
Self {
|
||||
chip,
|
||||
inner: pieces.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can
|
||||
/// be used.
|
||||
#[allow(non_snake_case)]
|
||||
pub struct HashDomain<
|
||||
C: CurveAffine,
|
||||
SinsemillaChip,
|
||||
EccChip,
|
||||
const K: usize,
|
||||
const MAX_WORDS: usize,
|
||||
> where
|
||||
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
|
||||
EccChip: EccInstructions<
|
||||
C,
|
||||
Point = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::Point,
|
||||
> + Clone
|
||||
+ Debug
|
||||
+ Eq,
|
||||
{
|
||||
sinsemilla_chip: SinsemillaChip,
|
||||
ecc_chip: EccChip,
|
||||
Q: C,
|
||||
}
|
||||
|
||||
impl<C: CurveAffine, SinsemillaChip, EccChip, const K: usize, const MAX_WORDS: usize>
|
||||
HashDomain<C, SinsemillaChip, EccChip, K, MAX_WORDS>
|
||||
where
|
||||
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
|
||||
EccChip: EccInstructions<
|
||||
C,
|
||||
Point = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::Point,
|
||||
> + Clone
|
||||
+ Debug
|
||||
+ Eq,
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
/// Constructs a new `HashDomain` for the given domain.
|
||||
pub fn new(
|
||||
sinsemilla_chip: SinsemillaChip,
|
||||
ecc_chip: EccChip,
|
||||
domain: &SinsemillaChip::HashDomains,
|
||||
) -> Self {
|
||||
HashDomain {
|
||||
sinsemilla_chip,
|
||||
ecc_chip,
|
||||
Q: domain.Q(),
|
||||
}
|
||||
}
|
||||
|
||||
/// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash].
|
||||
///
|
||||
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
|
||||
pub fn hash_to_point(
|
||||
&self,
|
||||
layouter: impl Layouter<C::Base>,
|
||||
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
|
||||
) -> Result<ecc::Point<C, EccChip>, Error> {
|
||||
assert_eq!(self.sinsemilla_chip, message.chip);
|
||||
self.sinsemilla_chip
|
||||
.hash_to_point(layouter, self.Q, message.inner)
|
||||
.map(|(point, _)| ecc::Point::from_inner(self.ecc_chip.clone(), point))
|
||||
}
|
||||
|
||||
/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
|
||||
///
|
||||
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
|
||||
pub fn hash(
|
||||
&self,
|
||||
layouter: impl Layouter<C::Base>,
|
||||
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
|
||||
) -> Result<ecc::X<C, EccChip>, Error> {
|
||||
assert_eq!(self.sinsemilla_chip, message.chip);
|
||||
let p = self.hash_to_point(layouter, message);
|
||||
p.map(|p| p.extract_p())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait allowing circuit's Sinsemilla HashDomains to be enumerated.
|
||||
#[allow(non_snake_case)]
|
||||
pub trait HashDomains<C: CurveAffine>: Clone + Debug {
|
||||
fn Q(&self) -> C;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use halo2::{
|
||||
circuit::{layouter::SingleChipLayouter, Layouter},
|
||||
dev::MockProver,
|
||||
pasta::pallas,
|
||||
plonk::{Assignment, Circuit, ConstraintSystem, Error},
|
||||
};
|
||||
|
||||
use super::{
|
||||
chip::SinsemillaHashDomains,
|
||||
chip::{SinsemillaChip, SinsemillaConfig},
|
||||
HashDomain, Message, SinsemillaInstructions,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
circuit::gadget::ecc::{
|
||||
chip::{EccChip, EccConfig},
|
||||
Point,
|
||||
},
|
||||
constants::MERKLE_CRH_PERSONALIZATION,
|
||||
primitives::sinsemilla::{self, K},
|
||||
};
|
||||
|
||||
use group::Curve;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
struct MyCircuit {}
|
||||
|
||||
impl Circuit<pallas::Base> for MyCircuit {
|
||||
type Config = (EccConfig, SinsemillaConfig, SinsemillaConfig);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
|
||||
let advices = [
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
meta.advice_column(),
|
||||
];
|
||||
|
||||
let constants = meta.fixed_column();
|
||||
let perm = meta.permutation(
|
||||
&advices
|
||||
.iter()
|
||||
.map(|advice| (*advice).into())
|
||||
.chain(Some(constants.into()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let ecc_config = EccChip::configure(meta, advices, perm.clone());
|
||||
|
||||
// Fixed columns for the Sinsemilla generator lookup table
|
||||
let lookup = (
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
);
|
||||
|
||||
let config1 = SinsemillaChip::configure(
|
||||
meta,
|
||||
advices[..5].try_into().unwrap(),
|
||||
lookup,
|
||||
constants,
|
||||
perm.clone(),
|
||||
);
|
||||
let config2 = SinsemillaChip::configure(
|
||||
meta,
|
||||
advices[5..].try_into().unwrap(),
|
||||
lookup,
|
||||
constants,
|
||||
perm,
|
||||
);
|
||||
(ecc_config, config1, config2)
|
||||
}
|
||||
|
||||
fn synthesize(
|
||||
&self,
|
||||
cs: &mut impl Assignment<pallas::Base>,
|
||||
config: Self::Config,
|
||||
) -> Result<(), Error> {
|
||||
let mut layouter = SingleChipLayouter::new(cs)?;
|
||||
let ecc_chip = EccChip::construct(config.0);
|
||||
|
||||
// The two `SinsemillaChip`s share the same lookup table.
|
||||
SinsemillaChip::load(config.1.clone(), &mut layouter)?;
|
||||
|
||||
// This MerkleCRH example is purely for illustrative purposes.
|
||||
// It is not an implementation of the Orchard protocol spec.
|
||||
{
|
||||
let chip1 = SinsemillaChip::construct(config.1);
|
||||
|
||||
let merkle_crh = HashDomain::new(
|
||||
chip1.clone(),
|
||||
ecc_chip.clone(),
|
||||
&SinsemillaHashDomains::MerkleCrh,
|
||||
);
|
||||
|
||||
// Layer 31, l = MERKLE_DEPTH_ORCHARD - 1 - layer = 0
|
||||
let l_bitstring = vec![Some(false); K];
|
||||
let l = chip1
|
||||
.witness_message_piece_bitstring(layouter.namespace(|| "l"), &l_bitstring)?;
|
||||
|
||||
// Left leaf
|
||||
let left_bitstring: Vec<Option<bool>> =
|
||||
(0..250).map(|_| Some(rand::random::<bool>())).collect();
|
||||
let left = chip1.witness_message_piece_bitstring(
|
||||
layouter.namespace(|| "left"),
|
||||
&left_bitstring,
|
||||
)?;
|
||||
|
||||
// Right leaf
|
||||
let right_bitstring: Vec<Option<bool>> =
|
||||
(0..250).map(|_| Some(rand::random::<bool>())).collect();
|
||||
let right = chip1.witness_message_piece_bitstring(
|
||||
layouter.namespace(|| "right"),
|
||||
&right_bitstring,
|
||||
)?;
|
||||
|
||||
let l_bitstring: Option<Vec<bool>> = l_bitstring.into_iter().collect();
|
||||
let left_bitstring: Option<Vec<bool>> = left_bitstring.into_iter().collect();
|
||||
let right_bitstring: Option<Vec<bool>> = right_bitstring.into_iter().collect();
|
||||
|
||||
// Witness expected parent
|
||||
let expected_parent = {
|
||||
let expected_parent = if let (Some(l), Some(left), Some(right)) =
|
||||
(l_bitstring, left_bitstring, right_bitstring)
|
||||
{
|
||||
let merkle_crh = sinsemilla::HashDomain::new(MERKLE_CRH_PERSONALIZATION);
|
||||
let point = merkle_crh
|
||||
.hash_to_point(
|
||||
l.into_iter()
|
||||
.chain(left.into_iter())
|
||||
.chain(right.into_iter()),
|
||||
)
|
||||
.unwrap();
|
||||
Some(point.to_affine())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Point::new(
|
||||
ecc_chip,
|
||||
layouter.namespace(|| "Witness expected parent"),
|
||||
expected_parent,
|
||||
)?
|
||||
};
|
||||
|
||||
// Parent
|
||||
let parent = {
|
||||
let message = Message::from_pieces(chip1, vec![l, left, right]);
|
||||
merkle_crh.hash_to_point(layouter.namespace(|| "parent"), message)?
|
||||
};
|
||||
|
||||
parent.constrain_equal(
|
||||
layouter.namespace(|| "parent == expected parent"),
|
||||
&expected_parent,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sinsemilla_chip() {
|
||||
let k = 11;
|
||||
let circuit = MyCircuit {};
|
||||
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
|
||||
assert_eq!(prover.verify(), Ok(()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "dev-graph")]
|
||||
#[test]
|
||||
fn print_sinsemilla_chip() {
|
||||
use plotters::prelude::*;
|
||||
|
||||
let root =
|
||||
BitMapBackend::new("sinsemilla-hash-layout.png", (1024, 7680)).into_drawing_area();
|
||||
root.fill(&WHITE).unwrap();
|
||||
let root = root.titled("SinsemillaHash", ("sans-serif", 60)).unwrap();
|
||||
|
||||
let circuit = MyCircuit {};
|
||||
halo2::dev::circuit_layout(&circuit, &root).unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
use super::{
|
||||
message::{Message, MessagePiece},
|
||||
HashDomains, SinsemillaInstructions,
|
||||
};
|
||||
use crate::{
|
||||
circuit::gadget::{
|
||||
ecc::chip::EccPoint,
|
||||
utilities::{CellValue, Var},
|
||||
},
|
||||
primitives::sinsemilla::{
|
||||
self, Q_COMMIT_IVK_M_GENERATOR, Q_MERKLE_CRH, Q_NOTE_COMMITMENT_M_GENERATOR,
|
||||
},
|
||||
};
|
||||
|
||||
use ff::PrimeField;
|
||||
use halo2::{
|
||||
arithmetic::{CurveAffine, FieldExt},
|
||||
circuit::{Chip, Layouter},
|
||||
plonk::{
|
||||
Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector,
|
||||
VirtualCells,
|
||||
},
|
||||
poly::Rotation,
|
||||
};
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
mod generator_table;
|
||||
pub use generator_table::get_s_by_idx;
|
||||
use generator_table::GeneratorTableConfig;
|
||||
|
||||
mod hash_to_point;
|
||||
|
||||
/// Configuration for the Sinsemilla hash chip
|
||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||
pub struct SinsemillaConfig {
|
||||
/// Selector used in the lookup argument as well as Sinsemilla custom gates.
|
||||
q_sinsemilla1: Selector,
|
||||
/// Fixed column used in Sinsemilla custom gates, to toggle behaviour at the ends of
|
||||
/// message pieces.
|
||||
q_sinsemilla2: Column<Fixed>,
|
||||
/// Fixed column used to constrain hash initialization to be consistent with
|
||||
/// the y-coordinate of the domain $Q$.
|
||||
fixed_y_q: Column<Fixed>,
|
||||
/// Advice column used to store the x-coordinate of the accumulator at each
|
||||
/// iteration of the hash.
|
||||
x_a: Column<Advice>,
|
||||
/// Advice column used to store the x-coordinate of the generator corresponding
|
||||
/// to the message word at each iteration of the hash. This is looked up in the
|
||||
/// generator table.
|
||||
x_p: Column<Advice>,
|
||||
/// Advice column used to load the message.
|
||||
bits: Column<Advice>,
|
||||
/// Advice column used to store the $\lambda_1$ intermediate value at each
|
||||
/// iteration.
|
||||
lambda_1: Column<Advice>,
|
||||
/// Advice column used to store the $\lambda_2$ intermediate value at each
|
||||
/// iteration.
|
||||
lambda_2: Column<Advice>,
|
||||
/// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$
|
||||
/// generators of the Sinsemilla hash.
|
||||
generator_table: GeneratorTableConfig,
|
||||
/// 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
|
||||
/// initial $x_a$.
|
||||
constants: Column<Fixed>,
|
||||
/// Permutation over all advice columns and the `constants` fixed column.
|
||||
perm: Permutation,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||
pub struct SinsemillaChip {
|
||||
config: SinsemillaConfig,
|
||||
}
|
||||
|
||||
impl Chip<pallas::Base> for SinsemillaChip {
|
||||
type Config = SinsemillaConfig;
|
||||
type Loaded = ();
|
||||
|
||||
fn config(&self) -> &Self::Config {
|
||||
&self.config
|
||||
}
|
||||
|
||||
fn loaded(&self) -> &Self::Loaded {
|
||||
&()
|
||||
}
|
||||
}
|
||||
|
||||
impl SinsemillaChip {
|
||||
pub fn construct(config: <Self as Chip<pallas::Base>>::Config) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
config: SinsemillaConfig,
|
||||
layouter: &mut impl Layouter<pallas::Base>,
|
||||
) -> Result<<Self as Chip<pallas::Base>>::Loaded, Error> {
|
||||
// Load the lookup table.
|
||||
config.generator_table.load(layouter)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn configure(
|
||||
meta: &mut ConstraintSystem<pallas::Base>,
|
||||
advices: [Column<Advice>; 5],
|
||||
lookup: (Column<Fixed>, Column<Fixed>, Column<Fixed>),
|
||||
constants: Column<Fixed>,
|
||||
perm: Permutation,
|
||||
) -> <Self as Chip<pallas::Base>>::Config {
|
||||
let config = SinsemillaConfig {
|
||||
q_sinsemilla1: meta.selector(),
|
||||
q_sinsemilla2: meta.fixed_column(),
|
||||
fixed_y_q: meta.fixed_column(),
|
||||
x_a: advices[0],
|
||||
x_p: advices[1],
|
||||
bits: advices[2],
|
||||
lambda_1: advices[3],
|
||||
lambda_2: advices[4],
|
||||
generator_table: GeneratorTableConfig {
|
||||
table_idx: lookup.0,
|
||||
table_x: lookup.1,
|
||||
table_y: lookup.2,
|
||||
},
|
||||
constants,
|
||||
perm,
|
||||
};
|
||||
|
||||
// Set up lookup argument
|
||||
GeneratorTableConfig::configure(meta, config.clone());
|
||||
|
||||
// Constant expressions
|
||||
let two = Expression::Constant(pallas::Base::from_u64(2));
|
||||
|
||||
// Closures for expressions that are derived multiple times
|
||||
// x_r = lambda_1^2 - x_a - x_p
|
||||
let x_r = |meta: &mut VirtualCells<pallas::Base>, rotation| {
|
||||
let x_a = meta.query_advice(config.x_a, rotation);
|
||||
let x_p = meta.query_advice(config.x_p, rotation);
|
||||
let lambda_1 = meta.query_advice(config.lambda_1, rotation);
|
||||
lambda_1.square() - x_a - x_p
|
||||
};
|
||||
|
||||
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
|
||||
let Y_A = |meta: &mut VirtualCells<pallas::Base>, rotation| {
|
||||
let x_a = meta.query_advice(config.x_a, rotation);
|
||||
let lambda_1 = meta.query_advice(config.lambda_1, rotation);
|
||||
let lambda_2 = meta.query_advice(config.lambda_2, rotation);
|
||||
(lambda_1 + lambda_2) * (x_a - x_r(meta, rotation))
|
||||
};
|
||||
|
||||
meta.create_gate("Sinsemilla gate", |meta| {
|
||||
let q_s1 = meta.query_selector(config.q_sinsemilla1);
|
||||
let q_s2 = meta.query_fixed(config.q_sinsemilla2, Rotation::cur());
|
||||
let q_s3 = {
|
||||
let one = Expression::Constant(pallas::Base::one());
|
||||
q_s2.clone() * (q_s2 - one)
|
||||
};
|
||||
let fixed_y_q = meta.query_fixed(config.fixed_y_q, Rotation::cur());
|
||||
|
||||
let lambda_1_next = meta.query_advice(config.lambda_1, Rotation::next());
|
||||
let lambda_2_cur = meta.query_advice(config.lambda_2, Rotation::cur());
|
||||
let x_a_cur = meta.query_advice(config.x_a, Rotation::cur());
|
||||
let x_a_next = meta.query_advice(config.x_a, Rotation::next());
|
||||
|
||||
// x_r = lambda_1^2 - x_a_cur - x_p
|
||||
let x_r = x_r(meta, Rotation::cur());
|
||||
|
||||
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
|
||||
let Y_A_cur = Y_A(meta, Rotation::cur());
|
||||
|
||||
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
|
||||
let Y_A_next = Y_A(meta, Rotation::next());
|
||||
|
||||
// Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q.
|
||||
// fixed_y_q * (2 * fixed_y_q - Y_{A,0}) = 0
|
||||
let init_y_q_check = fixed_y_q.clone() * (two.clone() * fixed_y_q - Y_A_cur.clone());
|
||||
|
||||
// lambda2^2 - (x_a_next + x_r + x_a_cur) = 0
|
||||
let secant_line =
|
||||
lambda_2_cur.clone().square() - (x_a_next.clone() + x_r + x_a_cur.clone());
|
||||
|
||||
// lhs - rhs = 0, where
|
||||
// - lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next)
|
||||
// - rhs = (2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final)
|
||||
let y_check = {
|
||||
// lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next)
|
||||
let lhs = lambda_2_cur * pallas::Base::from_u64(4) * (x_a_cur - x_a_next);
|
||||
|
||||
// rhs = 2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final
|
||||
let rhs = {
|
||||
// y_a_final is assigned to the lambda1 column on the next offset.
|
||||
let y_a_final = lambda_1_next;
|
||||
|
||||
two.clone() * Y_A_cur
|
||||
+ (two.clone() - q_s3.clone()) * Y_A_next
|
||||
+ two * q_s3 * y_a_final
|
||||
};
|
||||
lhs - rhs
|
||||
};
|
||||
|
||||
vec![
|
||||
("Initial y_q", init_y_q_check),
|
||||
("Secant line", q_s1.clone() * secant_line),
|
||||
("y check", q_s1 * y_check),
|
||||
]
|
||||
});
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
// Implement `SinsemillaInstructions` for `SinsemillaChip`
|
||||
impl SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }>
|
||||
for SinsemillaChip
|
||||
{
|
||||
type CellValue = CellValue<pallas::Base>;
|
||||
|
||||
type Message = Message<pallas::Base, { sinsemilla::K }, { sinsemilla::C }>;
|
||||
type MessagePiece = MessagePiece<pallas::Base, { sinsemilla::K }>;
|
||||
|
||||
type X = CellValue<pallas::Base>;
|
||||
type Point = EccPoint;
|
||||
|
||||
type HashDomains = SinsemillaHashDomains;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
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,
|
||||
mut layouter: impl Layouter<pallas::Base>,
|
||||
field_elem: Option<pallas::Base>,
|
||||
num_words: usize,
|
||||
) -> Result<Self::MessagePiece, Error> {
|
||||
let config = self.config().clone();
|
||||
|
||||
let cell = layouter.assign_region(
|
||||
|| "witness message piece",
|
||||
|mut region| {
|
||||
region.assign_advice(
|
||||
|| "witness message piece",
|
||||
config.bits,
|
||||
0,
|
||||
|| field_elem.ok_or(Error::SynthesisError),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
Ok(MessagePiece::new(cell, field_elem, num_words))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn hash_to_point(
|
||||
&self,
|
||||
mut layouter: impl Layouter<pallas::Base>,
|
||||
Q: pallas::Affine,
|
||||
message: Self::Message,
|
||||
) -> Result<(Self::Point, Vec<Vec<Self::CellValue>>), Error> {
|
||||
layouter.assign_region(
|
||||
|| "hash_to_point",
|
||||
|mut region| self.hash_message(&mut region, Q, &message),
|
||||
)
|
||||
}
|
||||
|
||||
fn extract(point: &Self::Point) -> Self::X {
|
||||
point.x()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SinsemillaHashDomains {
|
||||
NoteCommit,
|
||||
CommitIvk,
|
||||
MerkleCrh,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl HashDomains<pallas::Affine> for SinsemillaHashDomains {
|
||||
fn Q(&self) -> pallas::Affine {
|
||||
match self {
|
||||
SinsemillaHashDomains::CommitIvk => pallas::Affine::from_xy(
|
||||
pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.0).unwrap(),
|
||||
pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.1).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
SinsemillaHashDomains::NoteCommit => pallas::Affine::from_xy(
|
||||
pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(),
|
||||
pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
SinsemillaHashDomains::MerkleCrh => pallas::Affine::from_xy(
|
||||
pallas::Base::from_bytes(&Q_MERKLE_CRH.0).unwrap(),
|
||||
pallas::Base::from_bytes(&Q_MERKLE_CRH.1).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
use crate::primitives::sinsemilla::{self, sinsemilla_s_generators, S_PERSONALIZATION};
|
||||
use halo2::{
|
||||
circuit::Layouter,
|
||||
plonk::{Column, ConstraintSystem, Error, Expression, Fixed},
|
||||
poly::Rotation,
|
||||
};
|
||||
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, CurveExt, FieldExt},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use group::Curve;
|
||||
|
||||
/// Table containing independent generators S[0..2^k]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct GeneratorTableConfig {
|
||||
pub table_idx: Column<Fixed>,
|
||||
pub table_x: Column<Fixed>,
|
||||
pub table_y: Column<Fixed>,
|
||||
}
|
||||
|
||||
impl GeneratorTableConfig {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(non_snake_case)]
|
||||
/// Even though the lookup table can be used in other parts of the circuit,
|
||||
/// this specific configuration sets up Sinsemilla-specific constraints
|
||||
/// controlled by `q_sinsemilla`, and would likely not apply to other chips.
|
||||
pub fn configure(meta: &mut ConstraintSystem<pallas::Base>, config: super::SinsemillaConfig) {
|
||||
let (table_idx, table_x, table_y) = (
|
||||
config.generator_table.table_idx,
|
||||
config.generator_table.table_x,
|
||||
config.generator_table.table_y,
|
||||
);
|
||||
|
||||
meta.lookup(|meta| {
|
||||
let q_s1 = meta.query_selector(config.q_sinsemilla1);
|
||||
let q_s2 = meta.query_fixed(config.q_sinsemilla2, Rotation::cur());
|
||||
let q_s3 = {
|
||||
let one = Expression::Constant(pallas::Base::one());
|
||||
q_s2.clone() * (q_s2.clone() - one)
|
||||
};
|
||||
|
||||
let table_idx_cur = meta.query_fixed(table_idx, Rotation::cur());
|
||||
let table_x_cur = meta.query_fixed(table_x, Rotation::cur());
|
||||
let table_y_cur = meta.query_fixed(table_y, Rotation::cur());
|
||||
|
||||
// m_{i+1} = z_{i} - 2^K * (q_s2 - q_s3) * z_{i + 1}
|
||||
// Note that the message words m_i's are 1-indexed while the
|
||||
// running sum z_i's are 0-indexed.
|
||||
let word = {
|
||||
let z_cur = meta.query_advice(config.bits, Rotation::cur());
|
||||
let z_next = meta.query_advice(config.bits, Rotation::next());
|
||||
z_cur - ((q_s2 - q_s3) * z_next * pallas::Base::from_u64(1 << sinsemilla::K))
|
||||
};
|
||||
|
||||
let x_p = meta.query_advice(config.x_p, Rotation::cur());
|
||||
|
||||
// y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}),
|
||||
// where Y_{A,i} = (lambda1_i + lambda2_i) * (x_{A,i} - x_{R,i}),
|
||||
// x_{R,i} = lambda1^2 - x_{A,i} - x_{P,i}
|
||||
//
|
||||
let y_p = {
|
||||
let lambda1 = meta.query_advice(config.lambda_1, Rotation::cur());
|
||||
let lambda2 = meta.query_advice(config.lambda_2, Rotation::cur());
|
||||
let x_a = meta.query_advice(config.x_a, Rotation::cur());
|
||||
|
||||
let x_r = lambda1.clone().square() - x_a.clone() - x_p.clone();
|
||||
let Y_A = (lambda1.clone() + lambda2) * (x_a.clone() - x_r);
|
||||
|
||||
(Y_A * pallas::Base::TWO_INV) - (lambda1 * (x_a - x_p.clone()))
|
||||
};
|
||||
|
||||
// Lookup expressions default to the first entry when `q_s1`
|
||||
// is not enabled.
|
||||
let (init_x, init_y) = {
|
||||
let init_p = get_s_by_idx(0).to_affine().coordinates().unwrap();
|
||||
(*init_p.x(), *init_p.y())
|
||||
};
|
||||
let not_q_s1 = Expression::Constant(pallas::Base::one()) - q_s1.clone();
|
||||
|
||||
let m = q_s1.clone() * word; // The first table index is 0.
|
||||
let x_p = q_s1.clone() * x_p + not_q_s1.clone() * init_x;
|
||||
let y_p = q_s1 * y_p + not_q_s1 * init_y;
|
||||
|
||||
vec![(m, table_idx_cur), (x_p, table_x_cur), (y_p, table_y_cur)]
|
||||
});
|
||||
}
|
||||
|
||||
pub fn load(&self, layouter: &mut impl Layouter<pallas::Base>) -> Result<(), Error> {
|
||||
layouter.assign_region(
|
||||
|| "generator_table",
|
||||
|mut gate| {
|
||||
// We generate the row values lazily (we only need them during keygen).
|
||||
let mut rows = sinsemilla_s_generators();
|
||||
|
||||
for index in 0..(1 << sinsemilla::K) {
|
||||
let mut row = None;
|
||||
gate.assign_fixed(
|
||||
|| "table_idx",
|
||||
self.table_idx,
|
||||
index,
|
||||
|| {
|
||||
row = rows.next();
|
||||
Ok(pallas::Base::from_u64(index as u64))
|
||||
},
|
||||
)?;
|
||||
gate.assign_fixed(
|
||||
|| "table_x",
|
||||
self.table_x,
|
||||
index,
|
||||
|| row.map(|(x, _)| x).ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
gate.assign_fixed(
|
||||
|| "table_y",
|
||||
self.table_y,
|
||||
index,
|
||||
|| row.map(|(_, y)| y).ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get generator S by index
|
||||
pub fn get_s_by_idx(idx: u32) -> pallas::Point {
|
||||
let hash = pallas::Point::hash_to_curve(S_PERSONALIZATION);
|
||||
hash(&idx.to_le_bytes())
|
||||
}
|
|
@ -0,0 +1,461 @@
|
|||
use super::super::SinsemillaInstructions;
|
||||
use super::{get_s_by_idx, CellValue, EccPoint, SinsemillaChip, Var};
|
||||
use crate::{
|
||||
circuit::gadget::utilities::copy,
|
||||
primitives::sinsemilla::{self, lebs2ip_k, INV_TWO_POW_K},
|
||||
};
|
||||
use halo2::{
|
||||
circuit::{Chip, Region},
|
||||
plonk::Error,
|
||||
};
|
||||
|
||||
use ff::{Field, PrimeFieldBits};
|
||||
use group::Curve;
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, FieldExt},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
impl SinsemillaChip {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(super) fn hash_message(
|
||||
&self,
|
||||
region: &mut Region<'_, pallas::Base>,
|
||||
Q: pallas::Affine,
|
||||
message: &<Self as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::Message,
|
||||
) -> Result<(EccPoint, Vec<Vec<CellValue<pallas::Base>>>), Error> {
|
||||
let config = self.config().clone();
|
||||
let mut offset = 0;
|
||||
|
||||
// Get the `x`- and `y`-coordinates of the starting `Q` base.
|
||||
let x_q = *Q.coordinates().unwrap().x();
|
||||
let y_q = *Q.coordinates().unwrap().y();
|
||||
|
||||
// Initialize the accumulator to `Q`.
|
||||
let (mut x_a, mut y_a): (X<pallas::Base>, Y<pallas::Base>) = {
|
||||
// Constrain the initial x_q to equal the x-coordinate of the domain's `Q`.
|
||||
let fixed_x_q = {
|
||||
let cell =
|
||||
region.assign_fixed(|| "fixed x_q", config.constants, offset, || Ok(x_q))?;
|
||||
CellValue::new(cell, Some(x_q))
|
||||
};
|
||||
|
||||
let x_a = copy(
|
||||
region,
|
||||
|| "x_q",
|
||||
config.x_a,
|
||||
offset,
|
||||
&fixed_x_q,
|
||||
&config.perm,
|
||||
)?;
|
||||
|
||||
// Constrain the initial x_a, lambda_1, lambda_2, x_p using the fixed y_q
|
||||
// initializer. Assign `fixed_y_q` to be zero on every other row.
|
||||
{
|
||||
region.assign_fixed(|| "fixed y_q", config.fixed_y_q, offset, || Ok(y_q))?;
|
||||
|
||||
let total_num_words = message.iter().map(|piece| piece.num_words()).sum();
|
||||
for row in 1..total_num_words {
|
||||
region.assign_fixed(
|
||||
|| "fixed y_q",
|
||||
config.fixed_y_q,
|
||||
offset + row,
|
||||
|| Ok(pallas::Base::zero()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let y_a = Some(y_q);
|
||||
|
||||
(x_a.into(), y_a.into())
|
||||
};
|
||||
|
||||
let mut zs_sum: Vec<Vec<CellValue<pallas::Base>>> = Vec::new();
|
||||
|
||||
// Hash each piece in the message.
|
||||
for (idx, piece) in message.iter().enumerate() {
|
||||
let final_piece = idx == message.len() - 1;
|
||||
|
||||
// The value of the accumulator after this piece is processed.
|
||||
let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?;
|
||||
|
||||
// Since each message word takes one row to process, we increase
|
||||
// the offset by `piece.num_words` on each iteration.
|
||||
offset += piece.num_words();
|
||||
|
||||
// Update the accumulator to the latest value.
|
||||
x_a = x;
|
||||
y_a = y;
|
||||
zs_sum.push(zs);
|
||||
}
|
||||
|
||||
// Assign the final y_a.
|
||||
let y_a = {
|
||||
// Assign the final y_a.
|
||||
let y_a_cell = region.assign_advice(
|
||||
|| "y_a",
|
||||
config.lambda_1,
|
||||
offset,
|
||||
|| y_a.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
|
||||
// Assign lambda_2 and x_p zero values since they are queried
|
||||
// in the gate. (The actual values do not matter since they are
|
||||
// multiplied by zero.)
|
||||
{
|
||||
region.assign_advice(
|
||||
|| "dummy lambda2",
|
||||
config.lambda_2,
|
||||
offset,
|
||||
|| Ok(pallas::Base::zero()),
|
||||
)?;
|
||||
region.assign_advice(
|
||||
|| "dummy x_p",
|
||||
config.x_p,
|
||||
offset,
|
||||
|| Ok(pallas::Base::zero()),
|
||||
)?;
|
||||
}
|
||||
|
||||
CellValue::new(y_a_cell, y_a.0)
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(non_snake_case)]
|
||||
// Check equivalence to result from primitives::sinsemilla::hash_to_point
|
||||
{
|
||||
use crate::circuit::gadget::sinsemilla::message::MessagePiece;
|
||||
use crate::primitives::sinsemilla::{K, S_PERSONALIZATION};
|
||||
use group::prime::PrimeCurveAffine;
|
||||
use pasta_curves::arithmetic::CurveExt;
|
||||
|
||||
let field_elems: Option<Vec<pallas::Base>> =
|
||||
message.iter().map(|piece| piece.field_elem()).collect();
|
||||
|
||||
if field_elems.is_some() {
|
||||
// Get message as a bitstring.
|
||||
let bitstring: Vec<bool> = message
|
||||
.iter()
|
||||
.map(|piece: &MessagePiece<pallas::Base, K>| {
|
||||
piece
|
||||
.field_elem()
|
||||
.unwrap()
|
||||
.to_le_bits()
|
||||
.into_iter()
|
||||
.take(K * piece.num_words())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
|
||||
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());
|
||||
|
||||
// We can use complete addition here because it differs from
|
||||
// incomplete addition with negligible probability.
|
||||
let expected_point = bitstring
|
||||
.chunks(K)
|
||||
.fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc);
|
||||
let actual_point =
|
||||
pallas::Affine::from_xy(x_a.value().unwrap(), y_a.value().unwrap()).unwrap();
|
||||
assert_eq!(expected_point.to_affine(), actual_point);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((EccPoint::from_coordinates_unchecked(x_a.0, y_a), zs_sum))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
/// Hashes a message piece containing `piece.length` number of `K`-bit words.
|
||||
///
|
||||
/// To avoid a duplicate assignment, the accumulator x-coordinate provided
|
||||
/// by the caller is not copied. This only works because `hash_piece()` is
|
||||
/// an internal API. Before this call to `hash_piece()`, x_a MUST have been
|
||||
/// already assigned within this region at the correct offset.
|
||||
fn hash_piece(
|
||||
&self,
|
||||
region: &mut Region<'_, pallas::Base>,
|
||||
offset: usize,
|
||||
piece: &<Self as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::MessagePiece,
|
||||
mut x_a: X<pallas::Base>,
|
||||
mut y_a: Y<pallas::Base>,
|
||||
final_piece: bool,
|
||||
) -> Result<
|
||||
(
|
||||
X<pallas::Base>,
|
||||
Y<pallas::Base>,
|
||||
Vec<CellValue<pallas::Base>>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let config = self.config().clone();
|
||||
|
||||
// Selector assignments
|
||||
{
|
||||
// Enable `q_sinsemilla1` selector on every row.
|
||||
for row in 0..piece.num_words() {
|
||||
config.q_sinsemilla1.enable(region, offset + row)?;
|
||||
}
|
||||
|
||||
// Set `q_sinsemilla2` fixed column to 1 on every row but the last.
|
||||
for row in 0..(piece.num_words() - 1) {
|
||||
region.assign_fixed(
|
||||
|| "q_s2 = 1",
|
||||
config.q_sinsemilla2,
|
||||
offset + row,
|
||||
|| Ok(pallas::Base::one()),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Set `q_sinsemilla2` fixed column to 0 on the last row if this is
|
||||
// not the final piece, or to 2 on the last row of the final piece.
|
||||
region.assign_fixed(
|
||||
|| {
|
||||
if final_piece {
|
||||
"q_s2 for final piece"
|
||||
} else {
|
||||
"q_s2 between pieces"
|
||||
}
|
||||
},
|
||||
config.q_sinsemilla2,
|
||||
offset + piece.num_words() - 1,
|
||||
|| {
|
||||
Ok(if final_piece {
|
||||
pallas::Base::from_u64(2)
|
||||
} else {
|
||||
pallas::Base::zero()
|
||||
})
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// Message piece as K * piece.length bitstring
|
||||
let bitstring: Option<Vec<bool>> = piece.field_elem().map(|value| {
|
||||
value
|
||||
.to_le_bits()
|
||||
.into_iter()
|
||||
.take(sinsemilla::K * piece.num_words())
|
||||
.collect()
|
||||
});
|
||||
|
||||
let words: Option<Vec<u32>> = bitstring.map(|bitstring| {
|
||||
bitstring
|
||||
.chunks_exact(sinsemilla::K)
|
||||
.map(|word| lebs2ip_k(word))
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Get (x_p, y_p) for each word. We precompute this here so that we can use `batch_normalize()`.
|
||||
let generators_projective: Option<Vec<pallas::Point>> = words
|
||||
.clone()
|
||||
.map(|words| words.iter().map(|word| get_s_by_idx(*word)).collect());
|
||||
let generators: Option<Vec<(pallas::Base, pallas::Base)>> =
|
||||
generators_projective.map(|generators_projective| {
|
||||
let mut generators = vec![pallas::Affine::default(); generators_projective.len()];
|
||||
pallas::Point::batch_normalize(&generators_projective, &mut generators);
|
||||
generators
|
||||
.iter()
|
||||
.map(|gen| {
|
||||
let point = gen.coordinates().unwrap();
|
||||
(*point.x(), *point.y())
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Convert `words` from `Option<Vec<u32>>` to `Vec<Option<u32>>`
|
||||
let words: Vec<Option<u32>> = if let Some(words) = words {
|
||||
words.into_iter().map(Some).collect()
|
||||
} else {
|
||||
vec![None; piece.num_words()]
|
||||
};
|
||||
|
||||
// Decompose message piece into `K`-bit pieces with a running sum `z`.
|
||||
let zs = {
|
||||
let mut zs = Vec::with_capacity(piece.num_words() + 1);
|
||||
|
||||
// Copy message and initialize running sum `z` to decompose message in-circuit
|
||||
let cell = region.assign_advice(
|
||||
|| "z_0 (copy of message piece)",
|
||||
config.bits,
|
||||
offset,
|
||||
|| piece.field_elem().ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
region.constrain_equal(&config.perm, piece.cell(), cell)?;
|
||||
zs.push(CellValue::new(cell, piece.field_elem()));
|
||||
|
||||
// Assign cumulative sum such that for 0 <= i < n,
|
||||
// z_i = 2^K * z_{i + 1} + m_{i + 1}
|
||||
// => z_{i + 1} = (z_i - m_{i + 1}) / 2^K
|
||||
//
|
||||
// For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m.
|
||||
// We end up with z_n = 0. (z_n is not directly encoded as a cell value;
|
||||
// it is implicitly taken as 0 by adjusting the definition of m_{i+1}.)
|
||||
let mut z = piece.field_elem();
|
||||
let inv_2_k = pallas::Base::from_bytes(&INV_TWO_POW_K).unwrap();
|
||||
|
||||
// We do not assign the final z_n as it is constrained to be zero.
|
||||
for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() {
|
||||
// z_{i + 1} = (z_i - m_{i + 1}) / 2^K
|
||||
z = z
|
||||
.zip(*word)
|
||||
.map(|(z, word)| (z - pallas::Base::from_u64(word as u64)) * inv_2_k);
|
||||
let cell = region.assign_advice(
|
||||
|| format!("z_{:?}", idx + 1),
|
||||
config.bits,
|
||||
offset + idx + 1,
|
||||
|| z.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
zs.push(CellValue::new(cell, z))
|
||||
}
|
||||
|
||||
zs
|
||||
};
|
||||
|
||||
// The accumulator x-coordinate provided by the caller MUST have been assigned
|
||||
// within this region.
|
||||
|
||||
let generators: Vec<Option<(pallas::Base, pallas::Base)>> =
|
||||
if let Some(generators) = generators {
|
||||
generators.into_iter().map(Some).collect()
|
||||
} else {
|
||||
vec![None; piece.num_words()]
|
||||
};
|
||||
|
||||
for (row, gen) in generators.iter().enumerate() {
|
||||
let x_p = gen.map(|gen| gen.0);
|
||||
let y_p = gen.map(|gen| gen.1);
|
||||
|
||||
// Assign `x_p`
|
||||
region.assign_advice(
|
||||
|| "x_p",
|
||||
config.x_p,
|
||||
offset + row,
|
||||
|| x_p.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
|
||||
// Compute and assign `lambda_1`
|
||||
let lambda_1 = {
|
||||
let lambda_1 = x_a
|
||||
.value()
|
||||
.zip(y_a.0)
|
||||
.zip(x_p)
|
||||
.zip(y_p)
|
||||
.map(|(((x_a, y_a), x_p), y_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap());
|
||||
|
||||
// Assign lambda_1
|
||||
region.assign_advice(
|
||||
|| "lambda_1",
|
||||
config.lambda_1,
|
||||
offset + row,
|
||||
|| lambda_1.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
|
||||
lambda_1
|
||||
};
|
||||
|
||||
// Compute `x_r`
|
||||
let x_r = lambda_1
|
||||
.zip(x_a.value())
|
||||
.zip(x_p)
|
||||
.map(|((lambda_1, x_a), x_p)| lambda_1.square() - x_a - x_p);
|
||||
|
||||
// Compute and assign `lambda_2`
|
||||
let lambda_2 = {
|
||||
let lambda_2 = x_a.value().zip(y_a.0).zip(x_r).zip(lambda_1).map(
|
||||
|(((x_a, y_a), x_r), lambda_1)| {
|
||||
pallas::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda_1
|
||||
},
|
||||
);
|
||||
|
||||
region.assign_advice(
|
||||
|| "lambda_2",
|
||||
config.lambda_2,
|
||||
offset + row,
|
||||
|| lambda_2.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
|
||||
lambda_2
|
||||
};
|
||||
|
||||
// Compute and assign `x_a` for the next row.
|
||||
let x_a_new: X<pallas::Base> = {
|
||||
let x_a_new = lambda_2
|
||||
.zip(x_a.value())
|
||||
.zip(x_r)
|
||||
.map(|((lambda_2, x_a), x_r)| lambda_2.square() - x_a - x_r);
|
||||
|
||||
let x_a_cell = region.assign_advice(
|
||||
|| "x_a",
|
||||
config.x_a,
|
||||
offset + row + 1,
|
||||
|| x_a_new.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
|
||||
CellValue::new(x_a_cell, x_a_new).into()
|
||||
};
|
||||
|
||||
// Compute y_a for the next row.
|
||||
let y_a_new: Y<pallas::Base> = lambda_2
|
||||
.zip(x_a.value())
|
||||
.zip(x_a_new.value())
|
||||
.zip(y_a.0)
|
||||
.map(|(((lambda_2, x_a), x_a_new), y_a)| lambda_2 * (x_a - x_a_new) - y_a)
|
||||
.into();
|
||||
|
||||
// Update the mutable `x_a`, `y_a` variables.
|
||||
x_a = x_a_new;
|
||||
y_a = y_a_new;
|
||||
}
|
||||
|
||||
Ok((x_a, y_a, zs))
|
||||
}
|
||||
}
|
||||
|
||||
/// The x-coordinate of the accumulator in a Sinsemilla hash instance.
|
||||
struct X<F: FieldExt>(CellValue<F>);
|
||||
|
||||
impl<F: FieldExt> From<CellValue<F>> for X<F> {
|
||||
fn from(cell_value: CellValue<F>) -> Self {
|
||||
X(cell_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FieldExt> Deref for X<F> {
|
||||
type Target = CellValue<F>;
|
||||
|
||||
fn deref(&self) -> &CellValue<F> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The y-coordinate of the accumulator in a Sinsemilla hash instance.
|
||||
///
|
||||
/// This is never actually witnessed until the last round, since it
|
||||
/// can be derived from other variables. Thus it only exists as a field
|
||||
/// element, not a `CellValue`.
|
||||
struct Y<F: FieldExt>(Option<F>);
|
||||
|
||||
impl<F: FieldExt> From<Option<F>> for Y<F> {
|
||||
fn from(value: Option<F>) -> Self {
|
||||
Y(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FieldExt> Deref for Y<F> {
|
||||
type Target = Option<F>;
|
||||
|
||||
fn deref(&self) -> &Option<F> {
|
||||
&self.0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
//! Gadget and chips for the Sinsemilla hash function.
|
||||
use crate::circuit::gadget::utilities::{CellValue, Var};
|
||||
use ff::PrimeFieldBits;
|
||||
use halo2::{arithmetic::FieldExt, circuit::Cell};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A [`Message`] composed of several [`MessagePiece`]s.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Message<F: FieldExt, const K: usize, const MAX_WORDS: usize>(Vec<MessagePiece<F, K>>);
|
||||
|
||||
impl<F: FieldExt + PrimeFieldBits, const K: usize, const MAX_WORDS: usize>
|
||||
From<Vec<MessagePiece<F, K>>> for Message<F, K, MAX_WORDS>
|
||||
{
|
||||
fn from(pieces: Vec<MessagePiece<F, K>>) -> Self {
|
||||
// A message cannot contain more than `MAX_WORDS` words.
|
||||
assert!(pieces.iter().map(|piece| piece.num_words()).sum::<usize>() < MAX_WORDS);
|
||||
Message(pieces)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FieldExt + PrimeFieldBits, const K: usize, const MAX_WORDS: usize> std::ops::Deref
|
||||
for Message<F, K, MAX_WORDS>
|
||||
{
|
||||
type Target = [MessagePiece<F, K>];
|
||||
|
||||
fn deref(&self) -> &[MessagePiece<F, K>] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`MessagePiece`] of some bitlength.
|
||||
///
|
||||
/// The piece must fit within a base field element, which means its length
|
||||
/// cannot exceed the base field's `NUM_BITS`.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MessagePiece<F: FieldExt, const K: usize> {
|
||||
cell: Cell,
|
||||
field_elem: Option<F>,
|
||||
/// The number of K-bit words in this message piece.
|
||||
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> {
|
||||
pub fn new(cell: Cell, field_elem: Option<F>, num_words: usize) -> Self {
|
||||
assert!(num_words * K < F::NUM_BITS as usize);
|
||||
Self {
|
||||
cell,
|
||||
field_elem,
|
||||
num_words,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_words(&self) -> usize {
|
||||
self.num_words
|
||||
}
|
||||
|
||||
pub fn cell(&self) -> Cell {
|
||||
self.cell
|
||||
}
|
||||
|
||||
pub fn field_elem(&self) -> Option<F> {
|
||||
self.field_elem
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ mod constants;
|
|||
mod sinsemilla_s;
|
||||
pub use constants::*;
|
||||
|
||||
fn lebs2ip_k(bits: &[bool]) -> u32 {
|
||||
pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 {
|
||||
assert!(bits.len() == K);
|
||||
bits.iter()
|
||||
.enumerate()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Sinsemilla generators
|
||||
use group::Curve;
|
||||
use halo2::arithmetic::{CurveAffine, CurveExt};
|
||||
use pasta_curves::pallas;
|
||||
|
||||
/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$
|
||||
pub const K: usize = 10;
|
||||
|
@ -62,8 +63,8 @@ pub const Q_MERKLE_CRH: ([u8; 32], [u8; 32]) = (
|
|||
pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS";
|
||||
|
||||
/// Creates the Sinsemilla S generators used in each round of the Sinsemilla hash
|
||||
pub fn sinsemilla_s_generators<C: CurveAffine>() -> impl Iterator<Item = (C::Base, C::Base)> {
|
||||
let hasher = C::CurveExt::hash_to_curve(S_PERSONALIZATION);
|
||||
pub fn sinsemilla_s_generators() -> impl Iterator<Item = (pallas::Base, pallas::Base)> {
|
||||
let hasher = pallas::Point::hash_to_curve(S_PERSONALIZATION);
|
||||
(0..(1u32 << K)).map(move |j| {
|
||||
let point = hasher(&j.to_le_bytes()).to_affine().coordinates().unwrap();
|
||||
(*point.x(), *point.y())
|
||||
|
@ -84,7 +85,7 @@ mod tests {
|
|||
#[test]
|
||||
fn sinsemilla_s() {
|
||||
use super::super::sinsemilla_s::SINSEMILLA_S;
|
||||
let sinsemilla_s: Vec<_> = sinsemilla_s_generators::<pallas::Affine>().collect();
|
||||
let sinsemilla_s: Vec<_> = sinsemilla_s_generators().collect();
|
||||
assert_eq!(sinsemilla_s.len(), SINSEMILLA_S.len());
|
||||
for (expected, actual) in sinsemilla_s.iter().zip(&SINSEMILLA_S[..]) {
|
||||
assert_eq!(expected, actual);
|
||||
|
|
Loading…
Reference in New Issue