mirror of https://github.com/zcash/orchard.git
commit
bb159a2ccf
|
@ -9,6 +9,7 @@ use pasta_curves::arithmetic::{CurveAffine, FieldExt};
|
|||
use std::{convert::TryInto, fmt::Debug};
|
||||
|
||||
pub mod chip;
|
||||
pub mod merkle;
|
||||
mod message;
|
||||
|
||||
/// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget.
|
||||
|
|
|
@ -61,7 +61,7 @@ pub struct SinsemillaConfig {
|
|||
/// 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>,
|
||||
pub(super) constants: Column<Fixed>,
|
||||
/// Permutation over all advice columns and the `constants` fixed column.
|
||||
pub(super) perm: Permutation,
|
||||
/// Configure each advice column to be able to perform lookup range checks.
|
||||
|
@ -72,6 +72,13 @@ pub struct SinsemillaConfig {
|
|||
pub(super) lookup_config_4: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
|
||||
}
|
||||
|
||||
impl SinsemillaConfig {
|
||||
/// Returns an array of all advice columns in this config, in arbitrary order.
|
||||
pub(super) fn advices(&self) -> [Column<Advice>; 5] {
|
||||
[self.x_a, self.x_p, self.bits, self.lambda_1, self.lambda_2]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||
pub struct SinsemillaChip {
|
||||
config: SinsemillaConfig,
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
use halo2::{
|
||||
circuit::{Chip, Layouter},
|
||||
plonk::Error,
|
||||
};
|
||||
use pasta_curves::arithmetic::CurveAffine;
|
||||
|
||||
use super::{HashDomains, SinsemillaInstructions};
|
||||
|
||||
use crate::{
|
||||
circuit::gadget::utilities::{
|
||||
cond_swap::CondSwapInstructions, transpose_option_array, UtilitiesInstructions,
|
||||
},
|
||||
spec::i2lebsp,
|
||||
};
|
||||
use std::iter;
|
||||
|
||||
mod chip;
|
||||
|
||||
/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`.
|
||||
/// The hash function used is a Sinsemilla instance with `K`-bit words.
|
||||
/// The hash function can process `MAX_WORDS` words.
|
||||
pub trait MerkleInstructions<
|
||||
C: CurveAffine,
|
||||
const PATH_LENGTH: usize,
|
||||
const K: usize,
|
||||
const MAX_WORDS: usize,
|
||||
>:
|
||||
SinsemillaInstructions<C, K, MAX_WORDS>
|
||||
+ CondSwapInstructions<C::Base>
|
||||
+ UtilitiesInstructions<C::Base>
|
||||
+ Chip<C::Base>
|
||||
{
|
||||
/// Compute MerkleCRH for a given `layer`. The hash that computes the root
|
||||
/// is at layer 0, and the hashes that are applied to two leaves are at
|
||||
/// layer `MERKLE_DEPTH_ORCHARD - 1` = layer 31.
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_layer(
|
||||
&self,
|
||||
layouter: impl Layouter<C::Base>,
|
||||
Q: C,
|
||||
l: usize,
|
||||
left: Self::Var,
|
||||
right: Self::Var,
|
||||
) -> Result<Self::Var, Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MerklePath<
|
||||
C: CurveAffine,
|
||||
MerkleChip,
|
||||
const PATH_LENGTH: usize,
|
||||
const K: usize,
|
||||
const MAX_WORDS: usize,
|
||||
> where
|
||||
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
|
||||
{
|
||||
chip_1: MerkleChip,
|
||||
chip_2: MerkleChip,
|
||||
domain: MerkleChip::HashDomains,
|
||||
leaf_pos: Option<u32>,
|
||||
// The Merkle path is ordered from leaves to root.
|
||||
path: Option<[C::Base; PATH_LENGTH]>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl<
|
||||
C: CurveAffine,
|
||||
MerkleChip,
|
||||
const PATH_LENGTH: usize,
|
||||
const K: usize,
|
||||
const MAX_WORDS: usize,
|
||||
> MerklePath<C, MerkleChip, PATH_LENGTH, K, MAX_WORDS>
|
||||
where
|
||||
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
|
||||
{
|
||||
/// Calculates the root of the tree containing the given leaf at this Merkle path.
|
||||
fn calculate_root(
|
||||
&self,
|
||||
mut layouter: impl Layouter<C::Base>,
|
||||
leaf: MerkleChip::Var,
|
||||
) -> Result<MerkleChip::Var, Error> {
|
||||
// A Sinsemilla chip uses 5 advice columns, but the full Orchard action circuit
|
||||
// uses 10 advice columns. We distribute the path hashing across two Sinsemilla
|
||||
// chips to make better use of the available circuit area.
|
||||
let chips = iter::empty()
|
||||
.chain(iter::repeat(self.chip_1.clone()).take(PATH_LENGTH / 2))
|
||||
.chain(iter::repeat(self.chip_2.clone()));
|
||||
|
||||
// The Merkle path is ordered from leaves to root, which is consistent with the
|
||||
// little-endian representation of `pos` below.
|
||||
let path = transpose_option_array(self.path);
|
||||
|
||||
// Get position as a PATH_LENGTH-bit bitstring (little-endian bit order).
|
||||
let pos: [Option<bool>; PATH_LENGTH] = {
|
||||
let pos: Option<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64));
|
||||
transpose_option_array(pos)
|
||||
};
|
||||
|
||||
let Q = self.domain.Q();
|
||||
|
||||
let mut node = leaf;
|
||||
for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() {
|
||||
// `l` = MERKLE_DEPTH_ORCHARD - layer - 1, which is the index obtained from
|
||||
// enumerating this Merkle path (going from leaf to root).
|
||||
// For example, when `layer = 31` (the first sibling on the Merkle path),
|
||||
// we have `l` = 32 - 31 - 1 = 0.
|
||||
// On the other hand, when `layer = 0` (the final sibling on the Merkle path),
|
||||
// we have `l` = 32 - 0 - 1 = 31.
|
||||
let pair = {
|
||||
let pair = (node, *sibling);
|
||||
|
||||
// Swap node and sibling if needed
|
||||
chip.swap(layouter.namespace(|| "node position"), pair, *pos)?
|
||||
};
|
||||
|
||||
// Each `hash_layer` consists of 52 Sinsemilla words:
|
||||
// - l (10 bits) = 1 word
|
||||
// - left (255 bits) || right (255 bits) = 51 words (510 bits)
|
||||
node = chip.hash_layer(
|
||||
layouter.namespace(|| format!("hash l {}", l)),
|
||||
Q,
|
||||
l,
|
||||
pair.0,
|
||||
pair.1,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::{
|
||||
chip::{MerkleChip, MerkleConfig},
|
||||
MerklePath,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
circuit::gadget::{
|
||||
sinsemilla::chip::{SinsemillaChip, SinsemillaHashDomains},
|
||||
utilities::{UtilitiesInstructions, Var},
|
||||
},
|
||||
constants::{L_ORCHARD_BASE, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD},
|
||||
primitives::sinsemilla::HashDomain,
|
||||
spec::i2lebsp,
|
||||
};
|
||||
|
||||
use ff::PrimeFieldBits;
|
||||
use halo2::{
|
||||
arithmetic::FieldExt,
|
||||
circuit::{layouter::SingleChipLayouter, Layouter},
|
||||
dev::MockProver,
|
||||
pasta::pallas,
|
||||
plonk::{Assignment, Circuit, ConstraintSystem, Error},
|
||||
};
|
||||
|
||||
use rand::random;
|
||||
use std::convert::TryInto;
|
||||
|
||||
struct MyCircuit {
|
||||
leaf: Option<pallas::Base>,
|
||||
leaf_pos: Option<u32>,
|
||||
merkle_path: Option<[pallas::Base; MERKLE_DEPTH_ORCHARD]>,
|
||||
}
|
||||
|
||||
impl Circuit<pallas::Base> for MyCircuit {
|
||||
type Config = (MerkleConfig, MerkleConfig);
|
||||
|
||||
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(),
|
||||
];
|
||||
|
||||
// Shared fixed column for loading constants
|
||||
// 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(
|
||||
&advices
|
||||
.iter()
|
||||
.map(|advice| (*advice).into())
|
||||
.chain(constants_1.iter().map(|fixed| (*fixed).into()))
|
||||
.chain(constants_2.iter().map(|fixed| (*fixed).into()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// Fixed columns for the Sinsemilla generator lookup table
|
||||
let lookup = (
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
meta.fixed_column(),
|
||||
);
|
||||
|
||||
let sinsemilla_config_1 = SinsemillaChip::configure(
|
||||
meta,
|
||||
advices[5..].try_into().unwrap(),
|
||||
lookup,
|
||||
constants_1,
|
||||
perm.clone(),
|
||||
);
|
||||
let config1 = MerkleChip::configure(meta, sinsemilla_config_1);
|
||||
|
||||
let sinsemilla_config_2 = SinsemillaChip::configure(
|
||||
meta,
|
||||
advices[..5].try_into().unwrap(),
|
||||
lookup,
|
||||
constants_2,
|
||||
perm,
|
||||
);
|
||||
let config2 = MerkleChip::configure(meta, sinsemilla_config_2);
|
||||
|
||||
(config1, config2)
|
||||
}
|
||||
|
||||
fn synthesize(
|
||||
&self,
|
||||
cs: &mut impl Assignment<pallas::Base>,
|
||||
config: Self::Config,
|
||||
) -> Result<(), Error> {
|
||||
let mut layouter = SingleChipLayouter::new(cs)?;
|
||||
|
||||
// Load generator table (shared across both configs)
|
||||
SinsemillaChip::load(config.0.sinsemilla_config.clone(), &mut layouter)?;
|
||||
|
||||
// Construct Merkle chips which will be placed side-by-side in the circuit.
|
||||
let chip_1 = MerkleChip::construct(config.0.clone());
|
||||
let chip_2 = MerkleChip::construct(config.1.clone());
|
||||
|
||||
let leaf = chip_1.load_private(
|
||||
layouter.namespace(|| ""),
|
||||
config.0.cond_swap_config.a,
|
||||
self.leaf,
|
||||
)?;
|
||||
|
||||
let path = MerklePath {
|
||||
chip_1,
|
||||
chip_2,
|
||||
domain: SinsemillaHashDomains::MerkleCrh,
|
||||
leaf_pos: self.leaf_pos,
|
||||
path: self.merkle_path,
|
||||
};
|
||||
|
||||
let computed_final_root =
|
||||
path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?;
|
||||
|
||||
// The expected final root
|
||||
let pos_bool = i2lebsp::<32>(self.leaf_pos.unwrap() as u64);
|
||||
let path: Option<Vec<pallas::Base>> = self.merkle_path.map(|path| path.to_vec());
|
||||
let final_root = hash_path(self.leaf.unwrap(), &pos_bool, &path.unwrap());
|
||||
|
||||
// Check the computed final root against the expected final root.
|
||||
assert_eq!(computed_final_root.value().unwrap(), final_root);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_path(leaf: pallas::Base, pos_bool: &[bool], path: &[pallas::Base]) -> pallas::Base {
|
||||
let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION);
|
||||
|
||||
// Compute the root
|
||||
let mut node = leaf;
|
||||
for (l, (sibling, pos)) in path.iter().zip(pos_bool.iter()).enumerate() {
|
||||
let (left, right) = if *pos {
|
||||
(*sibling, node)
|
||||
} else {
|
||||
(node, *sibling)
|
||||
};
|
||||
|
||||
let l_star = i2lebsp::<10>(l as u64);
|
||||
let left: Vec<_> = left
|
||||
.to_le_bits()
|
||||
.iter()
|
||||
.by_val()
|
||||
.take(L_ORCHARD_BASE)
|
||||
.collect();
|
||||
let right: Vec<_> = right
|
||||
.to_le_bits()
|
||||
.iter()
|
||||
.by_val()
|
||||
.take(L_ORCHARD_BASE)
|
||||
.collect();
|
||||
|
||||
let mut message = l_star.to_vec();
|
||||
message.extend_from_slice(&left);
|
||||
message.extend_from_slice(&right);
|
||||
|
||||
node = domain.hash(message.into_iter()).unwrap();
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merkle_chip() {
|
||||
// Choose a random leaf and position
|
||||
let leaf = pallas::Base::rand();
|
||||
let pos = random::<u32>();
|
||||
let pos_bool = i2lebsp::<32>(pos as u64);
|
||||
|
||||
// Choose a path of random inner nodes
|
||||
let path: Vec<_> = (0..(MERKLE_DEPTH_ORCHARD))
|
||||
.map(|_| pallas::Base::rand())
|
||||
.collect();
|
||||
|
||||
// This root is provided as a public input in the Orchard circuit.
|
||||
let _root = hash_path(leaf, &pos_bool, &path);
|
||||
|
||||
let circuit = MyCircuit {
|
||||
leaf: Some(leaf),
|
||||
leaf_pos: Some(pos),
|
||||
merkle_path: Some(path.try_into().unwrap()),
|
||||
};
|
||||
|
||||
let prover = MockProver::run(11, &circuit, vec![]).unwrap();
|
||||
assert_eq!(prover.verify(), Ok(()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,523 @@
|
|||
use halo2::{
|
||||
circuit::{Chip, Layouter},
|
||||
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation},
|
||||
poly::Rotation,
|
||||
};
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
|
||||
use super::super::{
|
||||
chip::{SinsemillaChip, SinsemillaConfig},
|
||||
SinsemillaInstructions,
|
||||
};
|
||||
use super::MerkleInstructions;
|
||||
|
||||
use crate::{
|
||||
circuit::gadget::utilities::{
|
||||
cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions},
|
||||
copy, CellValue, UtilitiesInstructions, Var,
|
||||
},
|
||||
constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD},
|
||||
primitives::sinsemilla,
|
||||
};
|
||||
use ff::PrimeFieldBits;
|
||||
use std::{array, convert::TryInto};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MerkleConfig {
|
||||
advices: [Column<Advice>; 5],
|
||||
l_plus_1: Column<Fixed>,
|
||||
perm: Permutation,
|
||||
pub(super) cond_swap_config: CondSwapConfig,
|
||||
pub(super) sinsemilla_config: SinsemillaConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MerkleChip {
|
||||
config: MerkleConfig,
|
||||
}
|
||||
|
||||
impl Chip<pallas::Base> for MerkleChip {
|
||||
type Config = MerkleConfig;
|
||||
type Loaded = ();
|
||||
|
||||
fn config(&self) -> &Self::Config {
|
||||
&self.config
|
||||
}
|
||||
|
||||
fn loaded(&self) -> &Self::Loaded {
|
||||
&()
|
||||
}
|
||||
}
|
||||
|
||||
impl MerkleChip {
|
||||
pub fn configure(
|
||||
meta: &mut ConstraintSystem<pallas::Base>,
|
||||
sinsemilla_config: SinsemillaConfig,
|
||||
) -> MerkleConfig {
|
||||
let advices = sinsemilla_config.advices();
|
||||
let cond_swap_config =
|
||||
CondSwapChip::configure(meta, advices, sinsemilla_config.perm.clone());
|
||||
|
||||
// This fixed column serves two purposes:
|
||||
// - Fixing the value of l* for rows in which a Merkle path layer
|
||||
// is decomposed.
|
||||
// - Disabling the entire decomposition gate when set to zero
|
||||
// (i.e. replacing a Selector).
|
||||
|
||||
let l_plus_1 = meta.fixed_column();
|
||||
|
||||
// Check that pieces have been decomposed correctly for Sinsemilla hash.
|
||||
// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
|
||||
//
|
||||
// a = a_0||a_1 = l_star || (bits 0..=239 of left)
|
||||
// b = b_0||b_1||b_2
|
||||
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
|
||||
// c = bits 5..=254 of right
|
||||
//
|
||||
// The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be
|
||||
// 250 bits, 20 bits, and 250 bits respectively.
|
||||
//
|
||||
/*
|
||||
The pieces and subpieces are arranged in the following configuration:
|
||||
| A_0 | A_1 | A_2 | A_3 | A_4 | l_plus_1 |
|
||||
----------------------------------------------------
|
||||
| a | b | c | left | right | l + 1 |
|
||||
| z1_a | z1_b | b_1 | b_2 | | |
|
||||
*/
|
||||
meta.create_gate("Decomposition check", |meta| {
|
||||
let l_plus_1_whole = meta.query_fixed(l_plus_1, Rotation::cur());
|
||||
|
||||
let two_pow_5 = pallas::Base::from_u64(1 << 5);
|
||||
let two_pow_10 = two_pow_5.square();
|
||||
|
||||
// a_whole is constrained by Sinsemilla to be 250 bits.
|
||||
let a_whole = meta.query_advice(advices[0], Rotation::cur());
|
||||
// b_whole is constrained by Sinsemilla to be 20 bits.
|
||||
let b_whole = meta.query_advice(advices[1], Rotation::cur());
|
||||
// c_whole is constrained by Sinsemilla to be 250 bits.
|
||||
let c_whole = meta.query_advice(advices[2], Rotation::cur());
|
||||
let left_node = meta.query_advice(advices[3], Rotation::cur());
|
||||
let right_node = meta.query_advice(advices[4], Rotation::cur());
|
||||
|
||||
// a = a_0||a_1 = l_star || (bits 0..=239 of left)
|
||||
// Check that a_0 = l_star
|
||||
//
|
||||
// z_1 of SinsemillaHash(a) = a_1
|
||||
let z1_a = meta.query_advice(advices[0], Rotation::next());
|
||||
let a_1 = z1_a;
|
||||
// a_0 = a - (a_1 * 2^10)
|
||||
let a_0 = a_whole - a_1.clone() * pallas::Base::from_u64(1 << 10);
|
||||
let l_star_check =
|
||||
a_0 - (l_plus_1_whole.clone() - Expression::Constant(pallas::Base::one()));
|
||||
|
||||
// b = b_0||b_1||b_2
|
||||
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
|
||||
// The Orchard specification allows this representation to be non-canonical.
|
||||
// <https://zips.z.cash/protocol/protocol.pdf#merklepath>
|
||||
//
|
||||
// z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2
|
||||
// => b_0 = b - (z1_b * 2^10)
|
||||
let z1_b = meta.query_advice(advices[1], Rotation::next());
|
||||
// b_1 has been constrained to be 5 bits outside this gate.
|
||||
let b_1 = meta.query_advice(advices[2], Rotation::next());
|
||||
// b_2 has been constrained to be 5 bits outside this gate.
|
||||
let b_2 = meta.query_advice(advices[3], Rotation::next());
|
||||
// Constrain b_1 + 2^5 b_2 = z1_b
|
||||
let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5);
|
||||
// Derive b_0 (constrained by SinsemillaHash to be 10 bits)
|
||||
let b_0 = b_whole - (z1_b * two_pow_10);
|
||||
|
||||
// Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits)
|
||||
let left_check = {
|
||||
let reconstructed = {
|
||||
let two_pow_240 = pallas::Base::from_u128(1 << 120).square();
|
||||
a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240
|
||||
};
|
||||
reconstructed - left_node
|
||||
};
|
||||
|
||||
// Check that right = b_2 (5 bits) || c (250 bits)
|
||||
// The Orchard specification allows this representation to be non-canonical.
|
||||
// <https://zips.z.cash/protocol/protocol.pdf#merklepath>
|
||||
let right_check = b_2 + c_whole * two_pow_5 - right_node;
|
||||
|
||||
array::IntoIter::new([
|
||||
("l_star_check", l_star_check),
|
||||
("left_check", left_check),
|
||||
("right_check", right_check),
|
||||
("b1_b2_check", b1_b2_check),
|
||||
])
|
||||
.map(move |(name, poly)| (name, l_plus_1_whole.clone() * poly))
|
||||
});
|
||||
|
||||
MerkleConfig {
|
||||
advices,
|
||||
l_plus_1,
|
||||
perm: sinsemilla_config.perm.clone(),
|
||||
cond_swap_config,
|
||||
sinsemilla_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct(config: MerkleConfig) -> Self {
|
||||
MerkleChip { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K }, { sinsemilla::C }>
|
||||
for MerkleChip
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_layer(
|
||||
&self,
|
||||
mut layouter: impl Layouter<pallas::Base>,
|
||||
Q: pallas::Affine,
|
||||
// l = MERKLE_DEPTH_ORCHARD - layer - 1
|
||||
l: usize,
|
||||
left: Self::Var,
|
||||
right: Self::Var,
|
||||
) -> Result<Self::Var, Error> {
|
||||
let config = self.config().clone();
|
||||
|
||||
// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
|
||||
// We need to hash `l_star || left || right`, where `l_star` is a 10-bit value.
|
||||
// We allow `left` and `right` to be non-canonical 255-bit encodings.
|
||||
//
|
||||
// a = a_0||a_1 = l_star || (bits 0..=239 of left)
|
||||
// b = b_0||b_1||b_2
|
||||
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
|
||||
// c = bits 5..=254 of right
|
||||
|
||||
// `a = a_0||a_1` = `l_star` || (bits 0..=239 of `left`)
|
||||
let a = {
|
||||
let a = {
|
||||
// a_0 = l_star
|
||||
let a_0 = bitrange_subset(pallas::Base::from_u64(l as u64), 0..10);
|
||||
|
||||
// a_1 = (bits 0..=239 of `left`)
|
||||
let a_1 = left.value().map(|value| bitrange_subset(value, 0..240));
|
||||
|
||||
a_1.map(|a_1| a_0 + a_1 * pallas::Base::from_u64(1 << 10))
|
||||
};
|
||||
|
||||
self.witness_message_piece(layouter.namespace(|| "Witness a = a_0 || a_1"), a, 25)?
|
||||
};
|
||||
|
||||
// b = b_0 || b_1 || b_2
|
||||
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
|
||||
let (b_1, b_2, b) = {
|
||||
// b_0 = (bits 240..=249 of `left`)
|
||||
let b_0 = left.value().map(|value| bitrange_subset(value, 240..250));
|
||||
|
||||
// b_1 = (bits 250..=254 of `left`)
|
||||
// Constrain b_1 to 5 bits.
|
||||
let b_1 = {
|
||||
let b_1 = left
|
||||
.value()
|
||||
.map(|value| bitrange_subset(value, 250..L_ORCHARD_BASE));
|
||||
|
||||
config
|
||||
.sinsemilla_config
|
||||
.lookup_config_0
|
||||
.witness_short_check(layouter.namespace(|| "Constrain b_1 to 5 bits"), b_1, 5)?
|
||||
};
|
||||
|
||||
// b_2 = (bits 0..=4 of `right`)
|
||||
// Constrain b_2 to 5 bits.
|
||||
let b_2 = {
|
||||
let b_2 = right.value().map(|value| bitrange_subset(value, 0..5));
|
||||
|
||||
config
|
||||
.sinsemilla_config
|
||||
.lookup_config_1
|
||||
.witness_short_check(layouter.namespace(|| "Constrain b_2 to 5 bits"), b_2, 5)?
|
||||
};
|
||||
|
||||
let b = {
|
||||
let b = b_0
|
||||
.zip(b_1.value())
|
||||
.zip(b_2.value())
|
||||
.map(|((b_0, b_1), b_2)| {
|
||||
b_0 + b_1 * pallas::Base::from_u64(1 << 10)
|
||||
+ b_2 * pallas::Base::from_u64(1 << 15)
|
||||
});
|
||||
self.witness_message_piece(
|
||||
layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"),
|
||||
b,
|
||||
2,
|
||||
)?
|
||||
};
|
||||
|
||||
(b_1, b_2, b)
|
||||
};
|
||||
|
||||
let c = {
|
||||
// `c = bits 5..=254 of `right`
|
||||
let c = right
|
||||
.value()
|
||||
.map(|value| bitrange_subset(value, 5..L_ORCHARD_BASE));
|
||||
self.witness_message_piece(layouter.namespace(|| "Witness c"), c, 25)?
|
||||
};
|
||||
|
||||
let (point, zs) = self.hash_to_point(
|
||||
layouter.namespace(|| format!("hash at l = {}", l)),
|
||||
Q,
|
||||
vec![a, b, c].into(),
|
||||
)?;
|
||||
let z1_a = zs[0][1];
|
||||
let z1_b = zs[1][1];
|
||||
|
||||
// Check that the pieces have been decomposed properly.
|
||||
/*
|
||||
The pieces and subpieces are arranged in the following configuration:
|
||||
| A_0 | A_1 | A_2 | A_3 | A_4 | l_plus_1 |
|
||||
----------------------------------------------------
|
||||
| a | b | c | left | right | l + 1 |
|
||||
| z1_a | z1_b | b_1 | b_2 | | |
|
||||
*/
|
||||
{
|
||||
layouter.assign_region(
|
||||
|| "Check piece decomposition",
|
||||
|mut region| {
|
||||
// Set the fixed column `l_plus_1` to the current l + 1.
|
||||
// Recall that l = MERKLE_DEPTH_ORCHARD - layer - 1.
|
||||
// The layer with 2^n nodes is called "layer n".
|
||||
let l_plus_1 = (l as u64) + 1;
|
||||
region.assign_fixed(
|
||||
|| format!("l_plus_1 {}", l_plus_1),
|
||||
config.l_plus_1,
|
||||
0,
|
||||
|| Ok(pallas::Base::from_u64(l_plus_1)),
|
||||
)?;
|
||||
|
||||
// Offset 0
|
||||
// Copy and assign `a` at the correct position.
|
||||
copy(
|
||||
&mut region,
|
||||
|| "copy a",
|
||||
config.advices[0],
|
||||
0,
|
||||
&a.cell_value(),
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy and assign `b` at the correct position.
|
||||
copy(
|
||||
&mut region,
|
||||
|| "copy b",
|
||||
config.advices[1],
|
||||
0,
|
||||
&b.cell_value(),
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy and assign `c` at the correct position.
|
||||
copy(
|
||||
&mut region,
|
||||
|| "copy c",
|
||||
config.advices[2],
|
||||
0,
|
||||
&c.cell_value(),
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy and assign the left node at the correct position.
|
||||
copy(
|
||||
&mut region,
|
||||
|| "left",
|
||||
config.advices[3],
|
||||
0,
|
||||
&left,
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy and assign the right node at the correct position.
|
||||
copy(
|
||||
&mut region,
|
||||
|| "right",
|
||||
config.advices[4],
|
||||
0,
|
||||
&right,
|
||||
&config.perm,
|
||||
)?;
|
||||
|
||||
// Offset 1
|
||||
// Copy and assign z_1 of SinsemillaHash(a) = a_1
|
||||
copy(
|
||||
&mut region,
|
||||
|| "z1_a",
|
||||
config.advices[0],
|
||||
1,
|
||||
&z1_a,
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy and assign z_1 of SinsemillaHash(b) = b_1
|
||||
copy(
|
||||
&mut region,
|
||||
|| "z1_b",
|
||||
config.advices[1],
|
||||
1,
|
||||
&z1_b,
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy `b_1`, which has been constrained to be a 5-bit value
|
||||
copy(
|
||||
&mut region,
|
||||
|| "b_1",
|
||||
config.advices[2],
|
||||
1,
|
||||
&b_1,
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy `b_2`, which has been constrained to be a 5-bit value
|
||||
copy(
|
||||
&mut region,
|
||||
|| "b_2",
|
||||
config.advices[3],
|
||||
1,
|
||||
&b_2,
|
||||
&config.perm,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
let result = Self::extract(&point);
|
||||
|
||||
// Check layer hash output against Sinsemilla primitives hash
|
||||
#[cfg(test)]
|
||||
{
|
||||
use crate::{
|
||||
constants::MERKLE_CRH_PERSONALIZATION, primitives::sinsemilla::HashDomain,
|
||||
spec::i2lebsp,
|
||||
};
|
||||
|
||||
if let (Some(left), Some(right)) = (left.value(), right.value()) {
|
||||
let l_star = i2lebsp::<10>(l as u64);
|
||||
let left: Vec<_> = left
|
||||
.to_le_bits()
|
||||
.iter()
|
||||
.by_val()
|
||||
.take(L_ORCHARD_BASE)
|
||||
.collect();
|
||||
let right: Vec<_> = right
|
||||
.to_le_bits()
|
||||
.iter()
|
||||
.by_val()
|
||||
.take(L_ORCHARD_BASE)
|
||||
.collect();
|
||||
let merkle_crh = HashDomain::new(MERKLE_CRH_PERSONALIZATION);
|
||||
|
||||
let mut message = l_star.to_vec();
|
||||
message.extend_from_slice(&left);
|
||||
message.extend_from_slice(&right);
|
||||
|
||||
let expected = merkle_crh.hash(message.into_iter()).unwrap();
|
||||
|
||||
assert_eq!(expected.to_bytes(), result.value().unwrap().to_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl UtilitiesInstructions<pallas::Base> for MerkleChip {
|
||||
type Var = CellValue<pallas::Base>;
|
||||
}
|
||||
|
||||
impl CondSwapInstructions<pallas::Base> for MerkleChip {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn swap(
|
||||
&self,
|
||||
layouter: impl Layouter<pallas::Base>,
|
||||
pair: (Self::Var, Option<pallas::Base>),
|
||||
swap: Option<bool>,
|
||||
) -> Result<(Self::Var, Self::Var), Error> {
|
||||
let config = self.config().cond_swap_config.clone();
|
||||
let chip = CondSwapChip::<pallas::Base>::construct(config);
|
||||
chip.swap(layouter, pair, swap)
|
||||
}
|
||||
}
|
||||
|
||||
impl SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }> for MerkleChip {
|
||||
type CellValue = <SinsemillaChip as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::CellValue;
|
||||
|
||||
type Message = <SinsemillaChip as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::Message;
|
||||
type MessagePiece = <SinsemillaChip as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::MessagePiece;
|
||||
|
||||
type X = <SinsemillaChip as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::X;
|
||||
type Point = <SinsemillaChip as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::Point;
|
||||
|
||||
type HashDomains = <SinsemillaChip as SinsemillaInstructions<
|
||||
pallas::Affine,
|
||||
{ sinsemilla::K },
|
||||
{ sinsemilla::C },
|
||||
>>::HashDomains;
|
||||
|
||||
fn witness_message_piece(
|
||||
&self,
|
||||
layouter: impl Layouter<pallas::Base>,
|
||||
value: Option<pallas::Base>,
|
||||
num_words: usize,
|
||||
) -> Result<Self::MessagePiece, Error> {
|
||||
let config = self.config().sinsemilla_config.clone();
|
||||
let chip = SinsemillaChip::construct(config);
|
||||
chip.witness_message_piece(layouter, value, num_words)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn hash_to_point(
|
||||
&self,
|
||||
layouter: impl Layouter<pallas::Base>,
|
||||
Q: pallas::Affine,
|
||||
message: Self::Message,
|
||||
) -> Result<(Self::Point, Vec<Vec<Self::CellValue>>), Error> {
|
||||
let config = self.config().sinsemilla_config.clone();
|
||||
let chip = SinsemillaChip::construct(config);
|
||||
chip.hash_to_point(layouter, Q, message)
|
||||
}
|
||||
|
||||
fn extract(point: &Self::Point) -> Self::X {
|
||||
SinsemillaChip::extract(point)
|
||||
}
|
||||
}
|
||||
|
||||
fn bitrange_subset(field_elem: pallas::Base, bitrange: std::ops::Range<usize>) -> pallas::Base {
|
||||
assert!(bitrange.end <= L_ORCHARD_BASE);
|
||||
|
||||
let bits: Vec<bool> = field_elem
|
||||
.to_le_bits()
|
||||
.iter()
|
||||
.by_val()
|
||||
.skip(bitrange.start)
|
||||
.take(bitrange.end - bitrange.start)
|
||||
.chain(std::iter::repeat(false))
|
||||
.take(256)
|
||||
.collect();
|
||||
let bytearray: Vec<u8> = bits
|
||||
.chunks_exact(8)
|
||||
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
|
||||
.collect();
|
||||
|
||||
pallas::Base::from_bytes(&bytearray.try_into().unwrap()).unwrap()
|
||||
}
|
|
@ -3,6 +3,7 @@ use halo2::{
|
|||
plonk::{Advice, Column, Error, Permutation},
|
||||
};
|
||||
use pasta_curves::arithmetic::FieldExt;
|
||||
use std::array;
|
||||
|
||||
pub(crate) mod cond_swap;
|
||||
pub(crate) mod enable_flag;
|
||||
|
@ -84,3 +85,15 @@ where
|
|||
|
||||
Ok(CellValue::new(cell, copy.value))
|
||||
}
|
||||
|
||||
pub fn transpose_option_array<T: Copy + std::fmt::Debug, const LEN: usize>(
|
||||
option_array: Option<[T; LEN]>,
|
||||
) -> [Option<T>; LEN] {
|
||||
let mut ret = [None; LEN];
|
||||
if let Some(arr) = option_array {
|
||||
for (entry, value) in ret.iter_mut().zip(array::IntoIter::new(arr)) {
|
||||
*entry = Some(value);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@ use halo2::arithmetic::CurveExt;
|
|||
use pasta_curves::pallas;
|
||||
use subtle::CtOption;
|
||||
|
||||
use crate::constants::util::gen_const_array;
|
||||
use crate::spec::extract_p_bottom;
|
||||
use crate::spec::{extract_p_bottom, i2lebsp};
|
||||
|
||||
mod addition;
|
||||
use self::addition::IncompletePoint;
|
||||
|
@ -25,7 +24,7 @@ pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 {
|
|||
/// up to `2^K` - 1.
|
||||
pub fn i2lebsp_k(int: usize) -> [bool; K] {
|
||||
assert!(int < (1 << K));
|
||||
gen_const_array(|mask: usize| (int & (1 << mask)) != 0)
|
||||
i2lebsp(int as u64)
|
||||
}
|
||||
|
||||
/// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a
|
||||
|
|
58
src/spec.rs
58
src/spec.rs
|
@ -11,7 +11,7 @@ use pasta_curves::pallas;
|
|||
use subtle::{ConditionallySelectable, CtOption};
|
||||
|
||||
use crate::{
|
||||
constants::L_ORCHARD_BASE,
|
||||
constants::{util::gen_const_array, L_ORCHARD_BASE},
|
||||
primitives::{poseidon, sinsemilla},
|
||||
};
|
||||
|
||||
|
@ -252,11 +252,26 @@ pub fn lebs2ip<const L: usize>(bits: &[bool; L]) -> u64 {
|
|||
.fold(0u64, |acc, (i, b)| acc + if *b { 1 << i } else { 0 })
|
||||
}
|
||||
|
||||
/// The sequence of bits representing a u64 in little-endian order.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the expected length of the sequence `NUM_BITS` exceeds
|
||||
/// 64.
|
||||
pub fn i2lebsp<const NUM_BITS: usize>(int: u64) -> [bool; NUM_BITS] {
|
||||
assert!(NUM_BITS <= 64);
|
||||
gen_const_array(|mask: usize| (int & (1 << mask)) != 0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{i2lebsp, lebs2ip};
|
||||
|
||||
use group::Group;
|
||||
use halo2::arithmetic::CurveExt;
|
||||
use pasta_curves::pallas;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
fn diversify_hash_substitution() {
|
||||
|
@ -264,4 +279,45 @@ mod tests {
|
|||
pallas::Point::hash_to_curve("z.cash:Orchard-gd")(&[]).is_identity()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lebs2ip_round_trip() {
|
||||
let mut rng = OsRng;
|
||||
{
|
||||
let int = rng.next_u64();
|
||||
assert_eq!(lebs2ip::<64>(&i2lebsp(int)), int);
|
||||
}
|
||||
|
||||
assert_eq!(lebs2ip::<64>(&i2lebsp(0)), 0);
|
||||
assert_eq!(
|
||||
lebs2ip::<64>(&i2lebsp(0xFFFFFFFFFFFFFFFF)),
|
||||
0xFFFFFFFFFFFFFFFF
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i2lebsp_round_trip() {
|
||||
{
|
||||
let bitstring = (0..64).map(|_| rand::random()).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
i2lebsp::<64>(lebs2ip::<64>(&bitstring.clone().try_into().unwrap())).to_vec(),
|
||||
bitstring
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let bitstring = [false; 64];
|
||||
assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring);
|
||||
}
|
||||
|
||||
{
|
||||
let bitstring = [true; 64];
|
||||
assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring);
|
||||
}
|
||||
|
||||
{
|
||||
let bitstring = [];
|
||||
assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue