diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index b1bd1276..f2f11308 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -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 + for MerkleChip +{ + #[allow(non_snake_case)] + fn hash_layer( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + l_star: usize, + left: >::Var, + right: >::Var, + ) -> Result<>::Var, Error> { + let config = self.config().clone(); + + // + // 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 for MerkleChip { type Var = CellValue; } @@ -240,3 +486,23 @@ impl SinsemillaInstructions) -> pallas::Base { + assert!(bitrange.end <= L_ORCHARD_BASE); + + let bits: Vec = 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 = 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() +}