//! 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; // pub use chip::{SinsemillaChip, SinsemillaConfig}; /// 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 { /// A variable in the circuit. type CellValue: Var; /// A message composed of [`Self::MessagePiece`]s. type Message: From>; /// 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; /// 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, message: Vec>, ) -> Result; /// 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, message: &[Option], ) -> Result; /// 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, value: Option, num_words: usize, ) -> Result; /// 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, Q: C, message: Self::Message, ) -> Result<(Self::Point, Vec>), 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 where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { chip: SinsemillaChip, inner: SinsemillaChip::Message, } impl Message where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { fn from_bitstring( chip: SinsemillaChip, layouter: impl Layouter, bitstring: Vec>, ) -> Result { 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) -> 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 + Clone + Debug + Eq, EccChip: EccInstructions< C, Point = >::Point, > + Clone + Debug + Eq, { sinsemilla_chip: SinsemillaChip, ecc_chip: EccChip, Q: C, } impl HashDomain where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, EccChip: EccInstructions< C, Point = >::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, message: Message, ) -> Result, 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, message: Message, ) -> Result, 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: Clone + Debug { fn Q(&self) -> C; }