mirror of https://github.com/zcash/halo2.git
sinsemilla::merkle.rs: Implement MerkleInstructions for MerkleChip.
Co-authored-by: Daira Hopwood <daira@jacaranda.org> Co-authored-by: Jack Grigg <jack@electriccoin.co>
This commit is contained in:
parent
569eb4baa6
commit
f30de79fc6
|
@ -18,7 +18,7 @@ use crate::{
|
|||
lookup_range_check::LookupRangeCheckConfig,
|
||||
CellValue, UtilitiesInstructions, Var,
|
||||
},
|
||||
constants::MERKLE_DEPTH_ORCHARD,
|
||||
constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD},
|
||||
primitives::sinsemilla,
|
||||
};
|
||||
use ff::PrimeFieldBits;
|
||||
|
@ -159,6 +159,252 @@ impl MerkleChip {
|
|||
}
|
||||
}
|
||||
|
||||
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_star: usize,
|
||||
left: <Self as UtilitiesInstructions<pallas::Base>>::Var,
|
||||
right: <Self as UtilitiesInstructions<pallas::Base>>::Var,
|
||||
) -> Result<<Self as UtilitiesInstructions<pallas::Base>>::Var, Error> {
|
||||
let config = self.config().clone();
|
||||
|
||||
// <https://zips.z.cash/protocol/nu5.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` || (bits 0..=239 of `left`)
|
||||
let a = {
|
||||
let a = {
|
||||
// a_0 = l_star
|
||||
let a_0 = bitrange_subset(pallas::Base::from_u64(l_star 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_3"),
|
||||
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!("l_star {}", l_star)),
|
||||
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.
|
||||
{
|
||||
layouter.assign_region(
|
||||
|| "Check piece decomposition",
|
||||
|mut region| {
|
||||
// Set the fixed column `l_star_plus1` to the current l_star + 1.
|
||||
let l_star_plus1 = (l_star as u64) + 1;
|
||||
region.assign_fixed(
|
||||
|| format!("l_star_plus1 {}", l_star_plus1),
|
||||
config.l_star_plus1,
|
||||
0,
|
||||
|| Ok(pallas::Base::from_u64(l_star_plus1)),
|
||||
)?;
|
||||
|
||||
// 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,
|
||||
|| "a_0",
|
||||
config.advices[0],
|
||||
1,
|
||||
&z1_a,
|
||||
&config.perm,
|
||||
)?;
|
||||
// Copy and assign z_1 of SinsemillaHash(b) = b_1
|
||||
copy(
|
||||
&mut region,
|
||||
|| "b_0",
|
||||
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_star 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>;
|
||||
}
|
||||
|
@ -240,3 +486,23 @@ impl SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }
|
|||
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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue