use halo2::{ circuit::Layouter, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; use pasta_curves::{arithmetic::FieldExt, pallas}; use crate::{ circuit::gadget::{ ecc::{ chip::{EccChip, NonIdentityEccPoint}, Point, }, utilities::{bitrange_subset, bool_check, copy, CellValue, Var}, }, constants::T_P, }; use super::{ chip::{SinsemillaChip, SinsemillaCommitDomains, SinsemillaConfig}, CommitDomain, Message, MessagePiece, }; /* We need to hash g★_d || pk★_d || i2lebsp_{64}(v) || rho || psi, where - g★_d is the representation of the point g_d, with 255 bits used for the x-coordinate and 1 bit used for the y-coordinate; - pk★_d is the representation of the point pk_d, with 255 bits used for the x-coordinate and 1 bit used for the y-coordinate; - v is a 64-bit value; - rho is a base field element (255 bits); and - psi is a base field element (255 bits). */ #[allow(non_snake_case)] #[derive(Clone, Debug)] pub struct NoteCommitConfig { q_notecommit_b: Selector, q_notecommit_d: Selector, q_notecommit_e: Selector, q_notecommit_g: Selector, q_notecommit_h: Selector, q_notecommit_g_d: Selector, q_notecommit_pk_d: Selector, q_notecommit_value: Selector, q_notecommit_rho: Selector, q_notecommit_psi: Selector, q_y_canon: Selector, advices: [Column; 10], sinsemilla_config: SinsemillaConfig, } impl NoteCommitConfig { #[allow(non_snake_case)] #[allow(clippy::many_single_char_names)] pub(in crate::circuit) fn configure( meta: &mut ConstraintSystem, advices: [Column; 10], sinsemilla_config: SinsemillaConfig, ) -> Self { let q_notecommit_b = meta.selector(); let q_notecommit_d = meta.selector(); let q_notecommit_e = meta.selector(); let q_notecommit_g = meta.selector(); let q_notecommit_h = meta.selector(); let q_notecommit_g_d = meta.selector(); let q_notecommit_pk_d = meta.selector(); let q_notecommit_value = meta.selector(); let q_notecommit_rho = meta.selector(); let q_notecommit_psi = meta.selector(); let q_y_canon = meta.selector(); let config = Self { q_notecommit_b, q_notecommit_d, q_notecommit_e, q_notecommit_g, q_notecommit_h, q_notecommit_g_d, q_notecommit_pk_d, q_notecommit_value, q_notecommit_rho, q_notecommit_psi, q_y_canon, advices, sinsemilla_config, }; // Useful constants let two = pallas::Base::from_u64(2); let two_pow_2 = pallas::Base::from_u64(1 << 2); let two_pow_4 = two_pow_2.square(); let two_pow_5 = two_pow_4 * two; let two_pow_6 = two_pow_5 * two; let two_pow_8 = two_pow_4.square(); let two_pow_9 = two_pow_8 * two; let two_pow_10 = two_pow_9 * two; let two_pow_58 = pallas::Base::from_u64(1 << 58); let two_pow_130 = Expression::Constant(pallas::Base::from_u128(1 << 65).square()); let two_pow_140 = Expression::Constant(pallas::Base::from_u128(1 << 70).square()); let two_pow_249 = pallas::Base::from_u128(1 << 124).square() * two; let two_pow_250 = two_pow_249 * two; let two_pow_254 = pallas::Base::from_u128(1 << 127).square(); let t_p = Expression::Constant(pallas::Base::from_u128(T_P)); // Columns used for MessagePiece and message input gates. let col_l = config.advices[6]; let col_m = config.advices[7]; let col_r = config.advices[8]; let col_z = config.advices[9]; // | A_6 | A_7 | A_8 | q_notecommit_b | // ------------------------------------ // | b | b_0 | b_1 | 1 | // | | b_2 | b_3 | 0 | meta.create_gate("NoteCommit MessagePiece b", |meta| { let q_notecommit_b = meta.query_selector(config.q_notecommit_b); // b has been constrained to 10 bits by the Sinsemilla hash. let b = meta.query_advice(col_l, Rotation::cur()); // b_0 has been constrained to be 4 bits outside this gate. let b_0 = meta.query_advice(col_m, Rotation::cur()); // This gate constrains b_1 to be boolean. let b_1 = meta.query_advice(col_r, Rotation::cur()); // This gate constrains b_2 to be boolean. let b_2 = meta.query_advice(col_m, Rotation::next()); // b_3 has been constrained to 4 bits outside this gate. let b_3 = meta.query_advice(col_r, Rotation::next()); // b = b_0 + (2^4) b_1 + (2^5) b_2 + (2^6) b_3 let decomposition_check = b - (b_0 + b_1.clone() * two_pow_4 + b_2.clone() * two_pow_5 + b_3 * two_pow_6); std::iter::empty() .chain(Some(("bool_check b_1", bool_check(b_1)))) .chain(Some(("bool_check b_2", bool_check(b_2)))) .chain(Some(("decomposition", decomposition_check))) .map(move |(name, poly)| (name, q_notecommit_b.clone() * poly)) }); // | A_6 | A_7 | A_8 | q_notecommit_d | // ------------------------------------ // | d | d_0 | d_1 | 1 | // | | d_2 | d_3 | 0 | meta.create_gate("NoteCommit MessagePiece d", |meta| { let q_notecommit_d = meta.query_selector(config.q_notecommit_d); // d has been constrained to 60 bits by the Sinsemilla hash. let d = meta.query_advice(col_l, Rotation::cur()); // This gate constrains d_0 to be boolean. let d_0 = meta.query_advice(col_m, Rotation::cur()); // This gate constrains d_1 to be boolean. let d_1 = meta.query_advice(col_r, Rotation::cur()); // d_2 has been constrained to 8 bits outside this gate. let d_2 = meta.query_advice(col_m, Rotation::next()); // d_3 is set to z1_d. let d_3 = meta.query_advice(col_r, Rotation::next()); // d = d_0 + (2) d_1 + (2^2) d_2 + (2^10) d_3 let decomposition_check = d - (d_0.clone() + d_1.clone() * two + d_2 * two_pow_2 + d_3 * two_pow_10); std::iter::empty() .chain(Some(("bool_check d_0", bool_check(d_0)))) .chain(Some(("bool_check d_1", bool_check(d_1)))) .chain(Some(("decomposition", decomposition_check))) .map(move |(name, poly)| (name, q_notecommit_d.clone() * poly)) }); // | A_6 | A_7 | A_8 | q_notecommit_e | // ------------------------------------ // | e | e_0 | e_1 | 1 | meta.create_gate("NoteCommit MessagePiece e", |meta| { let q_notecommit_e = meta.query_selector(config.q_notecommit_e); // e has been constrained to 10 bits by the Sinsemilla hash. let e = meta.query_advice(col_l, Rotation::cur()); // e_0 has been constrained to 6 bits outside this gate. let e_0 = meta.query_advice(col_m, Rotation::cur()); // e_1 has been constrained to 4 bits outside this gate. let e_1 = meta.query_advice(col_r, Rotation::cur()); // e = e_0 + (2^6) e_1 let decomposition_check = e - (e_0 + e_1 * two_pow_6); std::iter::empty() .chain(Some(("decomposition", decomposition_check))) .map(move |(name, poly)| (name, q_notecommit_e.clone() * poly)) }); // | A_6 | A_7 | q_notecommit_g | // ------------------------------ // | g | g_0 | 1 | // | g_1 | g_2 | 0 | meta.create_gate("NoteCommit MessagePiece g", |meta| { let q_notecommit_g = meta.query_selector(config.q_notecommit_g); // g has been constrained to 250 bits by the Sinsemilla hash. let g = meta.query_advice(col_l, Rotation::cur()); // This gate constrains g_0 to be boolean. let g_0 = meta.query_advice(col_m, Rotation::cur()); // g_1 has been constrained to 9 bits outside this gate. let g_1 = meta.query_advice(col_l, Rotation::next()); // g_2 is set to z1_g. let g_2 = meta.query_advice(col_m, Rotation::next()); // g = g_0 + (2) g_1 + (2^10) g_2 let decomposition_check = g - (g_0.clone() + g_1 * two + g_2 * two_pow_10); std::iter::empty() .chain(Some(("bool_check g_0", bool_check(g_0)))) .chain(Some(("decomposition", decomposition_check))) .map(move |(name, poly)| (name, q_notecommit_g.clone() * poly)) }); // | A_6 | A_7 | A_8 | q_notecommit_h | // ------------------------------------ // | h | h_0 | h_1 | 1 | meta.create_gate("NoteCommit MessagePiece h", |meta| { let q_notecommit_h = meta.query_selector(config.q_notecommit_h); // h has been constrained to 10 bits by the Sinsemilla hash. let h = meta.query_advice(col_l, Rotation::cur()); // h_0 has been constrained to be 5 bits outside this gate. let h_0 = meta.query_advice(col_m, Rotation::cur()); // This gate constrains h_1 to be boolean. let h_1 = meta.query_advice(col_r, Rotation::cur()); // h = h_0 + (2^5) h_1 let decomposition_check = h - (h_0 + h_1.clone() * two_pow_5); std::iter::empty() .chain(Some(("bool_check h_1", bool_check(h_1)))) .chain(Some(("decomposition", decomposition_check))) .map(move |(name, poly)| (name, q_notecommit_h.clone() * poly)) }); // | A_6 | A_7 | A_8 | A_9 | q_notecommit_g_d | // ----------------------------------------------------------- // | x(g_d) | b_0 | a | z13_a | 1 | // | | b_1 | a_prime | z13_a_prime | 0 | meta.create_gate("NoteCommit input g_d", |meta| { let q_notecommit_g_d = meta.query_selector(config.q_notecommit_g_d); let gd_x = meta.query_advice(col_l, Rotation::cur()); // b_0 has been constrained to be 4 bits outside this gate. let b_0 = meta.query_advice(col_m, Rotation::cur()); // b_1 has been constrained to be boolean outside this gate. let b_1 = meta.query_advice(col_m, Rotation::next()); // a has been constrained to 250 bits by the Sinsemilla hash. let a = meta.query_advice(col_r, Rotation::cur()); let a_prime = meta.query_advice(col_r, Rotation::next()); let z13_a = meta.query_advice(col_z, Rotation::cur()); let z13_a_prime = meta.query_advice(col_z, Rotation::next()); // x(g_d) = a + (2^250)b_0 + (2^254)b_1 let decomposition_check = { let sum = a.clone() + b_0.clone() * two_pow_250 + b_1.clone() * two_pow_254; sum - gd_x }; // a_prime = a + 2^130 - t_P let a_prime_check = a + two_pow_130.clone() - t_p.clone() - a_prime; // The gd_x_canonicity_checks are enforced if and only if `b_1` = 1. // x(g_d) = a (250 bits) || b_0 (4 bits) || b_1 (1 bit) let canonicity_checks = std::iter::empty() .chain(Some(("b_1 = 1 => b_0", b_0))) .chain(Some(("b_1 = 1 => z13_a", z13_a))) .chain(Some(("b_1 = 1 => z13_a_prime", z13_a_prime))) .map(move |(name, poly)| (name, b_1.clone() * poly)); std::iter::empty() .chain(Some(("decomposition", decomposition_check))) .chain(Some(("a_prime_check", a_prime_check))) .chain(canonicity_checks) .map(move |(name, poly)| (name, q_notecommit_g_d.clone() * poly)) }); // | A_6 | A_7 | A_8 | A_9 | q_notecommit_pk_d | // ------------------------------------------------------------------- // | x(pk_d) | b_3 | c | z13_c | 1 | // | | d_0 | b3_c_prime | z14_b3_c_prime | 0 | meta.create_gate("NoteCommit input pk_d", |meta| { let q_notecommit_pk_d = meta.query_selector(config.q_notecommit_pk_d); let pkd_x = meta.query_advice(col_l, Rotation::cur()); // `b_3` has been constrained to 4 bits outside this gate. let b_3 = meta.query_advice(col_m, Rotation::cur()); // d_0 has been constrained to be boolean outside this gate. let d_0 = meta.query_advice(col_m, Rotation::next()); // `c` has been constrained to 250 bits by the Sinsemilla hash. let c = meta.query_advice(col_r, Rotation::cur()); let b3_c_prime = meta.query_advice(col_r, Rotation::next()); let z13_c = meta.query_advice(col_z, Rotation::cur()); let z14_b3_c_prime = meta.query_advice(col_z, Rotation::next()); // x(pk_d) = b_3 + (2^4)c + (2^254)d_0 let decomposition_check = { let sum = b_3.clone() + c.clone() * two_pow_4 + d_0.clone() * two_pow_254; sum - pkd_x }; // b3_c_prime = b_3 + (2^4)c + 2^140 - t_P let b3_c_prime_check = b_3 + (c * two_pow_4) + two_pow_140.clone() - t_p.clone() - b3_c_prime; // The pkd_x_canonicity_checks are enforced if and only if `d_0` = 1. // `x(pk_d)` = `b_3 (4 bits) || c (250 bits) || d_0 (1 bit)` let canonicity_checks = std::iter::empty() .chain(Some(("d_0 = 1 => z13_c", z13_c))) .chain(Some(("d_0 = 1 => z14_b3_c_prime", z14_b3_c_prime))) .map(move |(name, poly)| (name, d_0.clone() * poly)); std::iter::empty() .chain(Some(("decomposition", decomposition_check))) .chain(Some(("b3_c_prime_check", b3_c_prime_check))) .chain(canonicity_checks) .map(move |(name, poly)| (name, q_notecommit_pk_d.clone() * poly)) }); // | A_6 | A_7 | A_8 | A_9 | q_notecommit_value | // ------------------------------------------------ // | value | d_2 | d_3 | e_0 | 1 | meta.create_gate("NoteCommit input value", |meta| { let q_notecommit_value = meta.query_selector(config.q_notecommit_value); let value = meta.query_advice(col_l, Rotation::cur()); // d_2 has been constrained to 8 bits outside this gate. let d_2 = meta.query_advice(col_m, Rotation::cur()); // z1_d has been constrained to 50 bits by the Sinsemilla hash. let z1_d = meta.query_advice(col_r, Rotation::cur()); let d_3 = z1_d; // `e_0` has been constrained to 6 bits outside this gate. let e_0 = meta.query_advice(col_z, Rotation::cur()); // value = d_2 + (2^8)d_3 + (2^58)e_0 let value_check = d_2 + d_3 * two_pow_8 + e_0 * two_pow_58 - value; std::iter::empty() .chain(Some(("value_check", value_check))) .map(move |(name, poly)| (name, q_notecommit_value.clone() * poly)) }); // | A_6 | A_7 | A_8 | A_9 | q_notecommit_rho | // -------------------------------------------------------------- // | rho | e_1 | f | z13_f | 1 | // | | g_0 | e1_f_prime | z14_e1_f_prime | 0 | meta.create_gate("NoteCommit input rho", |meta| { let q_notecommit_rho = meta.query_selector(config.q_notecommit_rho); let rho = meta.query_advice(col_l, Rotation::cur()); // `e_1` has been constrained to 4 bits outside this gate. let e_1 = meta.query_advice(col_m, Rotation::cur()); let g_0 = meta.query_advice(col_m, Rotation::next()); // `f` has been constrained to 250 bits by the Sinsemilla hash. let f = meta.query_advice(col_r, Rotation::cur()); let e1_f_prime = meta.query_advice(col_r, Rotation::next()); let z13_f = meta.query_advice(col_z, Rotation::cur()); let z14_e1_f_prime = meta.query_advice(col_z, Rotation::next()); // rho = e_1 + (2^4) f + (2^254) g_0 let decomposition_check = { let sum = e_1.clone() + f.clone() * two_pow_4 + g_0.clone() * two_pow_254; sum - rho }; // e1_f_prime = e_1 + (2^4)f + 2^140 - t_P let e1_f_prime_check = e_1 + (f * two_pow_4) + two_pow_140 - t_p.clone() - e1_f_prime; // The rho_canonicity_checks are enforced if and only if `g_0` = 1. // rho = e_1 (4 bits) || f (250 bits) || g_0 (1 bit) let canonicity_checks = std::iter::empty() .chain(Some(("g_0 = 1 => z13_f", z13_f))) .chain(Some(("g_0 = 1 => z14_e1_f_prime", z14_e1_f_prime))) .map(move |(name, poly)| (name, g_0.clone() * poly)); std::iter::empty() .chain(Some(("decomposition", decomposition_check))) .chain(Some(("e1_f_prime_check", e1_f_prime_check))) .chain(canonicity_checks) .map(move |(name, poly)| (name, q_notecommit_rho.clone() * poly)) }); // | A_6 | A_7 | A_8 | A_9 | q_notecommit_psi | // ---------------------------------------------------------------- // | psi | g_1 | g_2 | z13_g | 1 | // | h_0 | h_1 | g1_g2_prime | z13_g1_g2_prime | 0 | meta.create_gate("NoteCommit input psi", |meta| { let q_notecommit_psi = meta.query_selector(config.q_notecommit_psi); let psi = meta.query_advice(col_l, Rotation::cur()); let h_0 = meta.query_advice(col_l, Rotation::next()); let g_1 = meta.query_advice(col_m, Rotation::cur()); let h_1 = meta.query_advice(col_m, Rotation::next()); let z1_g = meta.query_advice(col_r, Rotation::cur()); let g_2 = z1_g; let g1_g2_prime = meta.query_advice(col_r, Rotation::next()); let z13_g = meta.query_advice(col_z, Rotation::cur()); let z13_g1_g2_prime = meta.query_advice(col_z, Rotation::next()); // psi = g_1 + (2^9) g_2 + (2^249) h_0 + (2^254) h_1 let decomposition_check = { let sum = g_1.clone() + g_2.clone() * two_pow_9 + h_0.clone() * two_pow_249 + h_1.clone() * two_pow_254; sum - psi }; // g1_g2_prime = g_1 + (2^9)g_2 + 2^130 - t_P let g1_g2_prime_check = g_1 + (g_2 * two_pow_9) + two_pow_130.clone() - t_p.clone() - g1_g2_prime; // The psi_canonicity_checks are enforced if and only if `h_1` = 1. // `psi` = `g_1 (9 bits) || g_2 (240 bits) || h_0 (5 bits) || h_1 (1 bit)` let canonicity_checks = std::iter::empty() .chain(Some(("h_1 = 1 => h_0", h_0))) .chain(Some(("h_1 = 1 => z13_g", z13_g))) .chain(Some(("h_1 = 1 => z13_g1_g2_prime", z13_g1_g2_prime))) .map(move |(name, poly)| (name, h_1.clone() * poly)); std::iter::empty() .chain(Some(("decomposition", decomposition_check))) .chain(Some(("g1_g2_prime_check", g1_g2_prime_check))) .chain(canonicity_checks) .map(move |(name, poly)| (name, q_notecommit_psi.clone() * poly)) }); /* Check decomposition and canonicity of y-coordinates. This is used for both y(g_d) and y(pk_d). y = LSB || k_0 || k_1 || k_2 || k_3 = (bit 0) || (bits 1..=9) || (bits 10..=249) || (bits 250..=253) || (bit 254) These pieces are laid out in the following configuration: | A_5 | A_6 | A_7 | A_8 | A_9 | ---------------------------------------------- | y | lsb | k_0 | k_2 | k_3 | | j | z1_j| z13_j | j_prime | z13_j_prime | where z1_j = k_1. */ meta.create_gate("y coordinate checks", |meta| { let q_y_canon = meta.query_selector(q_y_canon); let y = meta.query_advice(advices[5], Rotation::cur()); // LSB has been boolean-constrained outside this gate. let lsb = meta.query_advice(advices[6], Rotation::cur()); // k_0 has been constrained to 9 bits outside this gate. let k_0 = meta.query_advice(advices[7], Rotation::cur()); // k_1 = z1_j (witnessed in the next rotation). // k_2 has been constrained to 4 bits outside this gate. let k_2 = meta.query_advice(advices[8], Rotation::cur()); // This gate constrains k_3 to be boolean. let k_3 = meta.query_advice(advices[9], Rotation::cur()); // j = LSB + (2)k_0 + (2^10)k_1 let j = meta.query_advice(advices[5], Rotation::next()); let z1_j = meta.query_advice(advices[6], Rotation::next()); let z13_j = meta.query_advice(advices[7], Rotation::next()); // j_prime = j + 2^130 - t_P let j_prime = meta.query_advice(advices[8], Rotation::next()); let z13_j_prime = meta.query_advice(advices[9], Rotation::next()); // Decomposition checks let decomposition_checks = { // Check that k_3 is boolean let k3_check = bool_check(k_3.clone()); // Check that j = LSB + (2)k_0 + (2^10)k_1 let k_1 = z1_j; let j_check = j.clone() - (lsb + k_0 * two + k_1 * two_pow_10); // Check that y = j + (2^250)k_2 + (2^254)k_3 let y_check = y - (j.clone() + k_2.clone() * two_pow_250 + k_3.clone() * two_pow_254); // Check that j_prime = j + 2^130 - t_P let j_prime_check = j + two_pow_130 - t_p.clone() - j_prime; std::iter::empty() .chain(Some(("k3_check", k3_check))) .chain(Some(("j_check", j_check))) .chain(Some(("y_check", y_check))) .chain(Some(("j_prime_check", j_prime_check))) }; // Canonicity checks. These are enforced if and only if k_3 = 1. let canonicity_checks = { std::iter::empty() .chain(Some(("k_3 = 1 => k_2 = 0", k_2))) .chain(Some(("k_3 = 1 => z13_j = 0", z13_j))) .chain(Some(("k_3 = 1 => z13_j_prime = 0", z13_j_prime))) .map(move |(name, poly)| (name, k_3.clone() * poly)) }; decomposition_checks .chain(canonicity_checks) .map(move |(name, poly)| (name, q_y_canon.clone() * poly)) }); config } #[allow(clippy::many_single_char_names)] #[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] pub(in crate::circuit) fn assign_region( &self, mut layouter: impl Layouter, chip: SinsemillaChip, ecc_chip: EccChip, g_d: &NonIdentityEccPoint, pk_d: &NonIdentityEccPoint, value: CellValue, rho: CellValue, psi: CellValue, rcm: Option, ) -> Result, Error> { let (gd_x, gd_y) = (g_d.x().value(), g_d.y().value()); let (pkd_x, pkd_y) = (pk_d.x().value(), pk_d.y().value()); let value_val = value.value(); let rho_val = rho.value(); let psi_val = psi.value(); // `a` = bits 0..=249 of `x(g_d)` let a = { let a = gd_x.map(|gd_x| bitrange_subset(gd_x, 0..250)); MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "a"), a, 25)? }; // b = b_0 || b_1 || b_2 || b_3 // = (bits 250..=253 of x(g_d)) || (bit 254 of x(g_d)) || (ỹ bit of g_d) || (bits 0..=3 of pk★_d) let (b_0, b_1, b_2, b_3, b) = { let b_0 = gd_x.map(|gd_x| bitrange_subset(gd_x, 250..254)); let b_1 = gd_x.map(|gd_x| bitrange_subset(gd_x, 254..255)); let b_2 = gd_y.map(|gd_y| bitrange_subset(gd_y, 0..1)); let b_3 = pkd_x.map(|pkd_x| bitrange_subset(pkd_x, 0..4)); // Constrain b_0 to be 4 bits let b_0 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "b_0 is 4 bits"), b_0, 4, )?; // Constrain b_3 to be 4 bits let b_3 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "b_3 is 4 bits"), b_3, 4, )?; // b_1, b_2 will be boolean-constrained in the gate. let b = b_0.value().zip(b_1).zip(b_2).zip(b_3.value()).map( |(((b_0, b_1), b_2), b_3)| { let b1_shifted = b_1 * pallas::Base::from_u64(1 << 4); let b2_shifted = b_2 * pallas::Base::from_u64(1 << 5); let b3_shifted = b_3 * pallas::Base::from_u64(1 << 6); b_0 + b1_shifted + b2_shifted + b3_shifted }, ); let b = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "b"), b, 1)?; (b_0, b_1, b_2, b_3, b) }; // c = bits 4..=253 of pk★_d let c = { let c = pkd_x.map(|pkd_x| bitrange_subset(pkd_x, 4..254)); MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "c"), c, 25)? }; // d = d_0 || d_1 || d_2 || d_3 // = (bit 254 of x(pk_d)) || (ỹ bit of pk_d) || (bits 0..=7 of v) || (bits 8..=57 of v) let (d_0, d_1, d_2, d) = { let d_0 = pkd_x.map(|pkd_x| bitrange_subset(pkd_x, 254..255)); let d_1 = pkd_y.map(|pkd_y| bitrange_subset(pkd_y, 0..1)); let d_2 = value_val.map(|value| bitrange_subset(value, 0..8)); let d_3 = value_val.map(|value| bitrange_subset(value, 8..58)); // Constrain d_2 to be 8 bits let d_2 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "d_2 is 8 bits"), d_2, 8, )?; // d_0, d_1 will be boolean-constrained in the gate. // d_3 = z1_d from the SinsemillaHash(d) running sum output. let d = d_0 .zip(d_1) .zip(d_2.value()) .zip(d_3) .map(|(((d_0, d_1), d_2), d_3)| { let d1_shifted = d_1 * pallas::Base::from_u64(2); let d2_shifted = d_2 * pallas::Base::from_u64(1 << 2); let d3_shifted = d_3 * pallas::Base::from_u64(1 << 10); d_0 + d1_shifted + d2_shifted + d3_shifted }); let d = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "d"), d, 6)?; (d_0, d_1, d_2, d) }; // e = e_0 || e_1 = (bits 58..=63 of v) || (bits 0..=3 of rho) let (e_0, e_1, e) = { let e_0 = value_val.map(|value| bitrange_subset(value, 58..64)); let e_1 = rho_val.map(|rho| bitrange_subset(rho, 0..4)); // Constrain e_0 to be 6 bits. let e_0 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "e_0 is 6 bits"), e_0, 6, )?; // Constrain e_1 to be 4 bits. let e_1 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "e_1 is 4 bits"), e_1, 4, )?; let e = e_0 .value() .zip(e_1.value()) .map(|(e_0, e_1)| e_0 + e_1 * pallas::Base::from_u64(1 << 6)); let e = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "e"), e, 1)?; (e_0, e_1, e) }; // f = bits 4..=253 inclusive of rho let f = { let f = rho_val.map(|rho| bitrange_subset(rho, 4..254)); MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "f"), f, 25)? }; // g = g_0 || g_1 || g_2 // = (bit 254 of rho) || (bits 0..=8 of psi) || (bits 9..=248 of psi) let (g_0, g_1, g) = { let g_0 = rho_val.map(|rho| bitrange_subset(rho, 254..255)); let g_1 = psi_val.map(|psi| bitrange_subset(psi, 0..9)); let g_2 = psi_val.map(|psi| bitrange_subset(psi, 9..249)); // Constrain g_1 to be 9 bits. let g_1 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "g_1 is 9 bits"), g_1, 9, )?; // g_0 will be boolean-constrained in the gate. // g_2 = z1_g from the SinsemillaHash(g) running sum output. let g = g_0.zip(g_1.value()).zip(g_2).map(|((g_0, g_1), g_2)| { let g1_shifted = g_1 * pallas::Base::from_u64(2); let g2_shifted = g_2 * pallas::Base::from_u64(1 << 10); g_0 + g1_shifted + g2_shifted }); let g = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "g"), g, 25)?; (g_0, g_1, g) }; // h = h_0 || h_1 || h_2 // = (bits 249..=253 of psi) || (bit 254 of psi) || 4 zero bits let (h_0, h_1, h) = { let h_0 = psi_val.map(|psi| bitrange_subset(psi, 249..254)); let h_1 = psi_val.map(|psi| bitrange_subset(psi, 254..255)); // Constrain h_0 to be 5 bits. let h_0 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "h_0 is 5 bits"), h_0, 5, )?; // h_1 will be boolean-constrained in the gate. let h = h_0 .value() .zip(h_1) .map(|(h_0, h_1)| h_0 + h_1 * pallas::Base::from_u64(1 << 5)); let h = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "h"), h, 1)?; (h_0, h_1, h) }; // Check decomposition of `y(g_d)`. let b_2 = self.y_canonicity(layouter.namespace(|| "y(g_d) decomposition"), g_d.y(), b_2)?; // Check decomposition of `y(pk_d)`. let d_1 = self.y_canonicity( layouter.namespace(|| "y(pk_d) decomposition"), pk_d.y(), d_1, )?; let (cm, zs) = { let message = Message::from_pieces( chip.clone(), vec![ a.clone(), b.clone(), c.clone(), d.clone(), e.clone(), f.clone(), g.clone(), h.clone(), ], ); let domain = CommitDomain::new(chip, ecc_chip, &SinsemillaCommitDomains::NoteCommit); domain.commit( layouter.namespace(|| "Process NoteCommit inputs"), message, rcm, )? }; let z13_a = zs[0][13]; let z13_c = zs[2][13]; let z1_d = zs[3][1]; let z13_f = zs[5][13]; let z1_g = zs[6][1]; let g_2 = z1_g; let z13_g = zs[6][13]; let (a_prime, z13_a_prime) = self.canon_bitshift_130( layouter.namespace(|| "x(g_d) canonicity"), a.inner().cell_value(), )?; let (b3_c_prime, z14_b3_c_prime) = self.pkd_x_canonicity( layouter.namespace(|| "x(pk_d) canonicity"), b_3, c.inner().cell_value(), )?; let (e1_f_prime, z14_e1_f_prime) = self.rho_canonicity( layouter.namespace(|| "rho canonicity"), e_1, f.inner().cell_value(), )?; let (g1_g2_prime, z13_g1_g2_prime) = self.psi_canonicity(layouter.namespace(|| "psi canonicity"), g_1, g_2)?; let gate_cells = GateCells { a: a.inner().cell_value(), b: b.inner().cell_value(), b_0, b_1, b_2, b_3, c: c.inner().cell_value(), d: d.inner().cell_value(), d_0, d_1, d_2, z1_d, e: e.inner().cell_value(), e_0, e_1, f: f.inner().cell_value(), g: g.inner().cell_value(), g_0, g_1, z1_g, h: h.inner().cell_value(), h_0, h_1, gd_x: g_d.x(), pkd_x: pk_d.x(), value, rho, psi, a_prime, b3_c_prime, e1_f_prime, g1_g2_prime, z13_a_prime, z14_b3_c_prime, z14_e1_f_prime, z13_g1_g2_prime, z13_a, z13_c, z13_f, z13_g, }; self.assign_gate(layouter.namespace(|| "Assign gate cells"), gate_cells)?; Ok(cm) } #[allow(clippy::type_complexity)] // A canonicity check helper used in checking x(g_d), y(g_d), and y(pk_d). fn canon_bitshift_130( &self, mut layouter: impl Layouter, a: CellValue, ) -> Result<(CellValue, CellValue), Error> { // element = `a (250 bits) || b_0 (4 bits) || b_1 (1 bit)` // - b_1 = 1 => b_0 = 0 // - b_1 = 1 => a < t_P // - 0 ≤ a < 2^130 (z_13 of SinsemillaHash(a)) // - 0 ≤ a + 2^130 - t_P < 2^130 (thirteen 10-bit lookups) // Decompose the low 130 bits of a_prime = a + 2^130 - t_P, and output // the running sum at the end of it. If a_prime < 2^130, the running sum // will be 0. let a_prime = a.value().map(|a| { let two_pow_130 = pallas::Base::from_u128(1u128 << 65).square(); let t_p = pallas::Base::from_u128(T_P); a + two_pow_130 - t_p }); let zs = self.sinsemilla_config.lookup_config.witness_check( layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"), a_prime, 13, false, )?; let a_prime = zs[0]; assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z_13] Ok((a_prime, zs[13])) } // Check canonicity of `x(pk_d)` encoding fn pkd_x_canonicity( &self, mut layouter: impl Layouter, b_3: CellValue, c: CellValue, ) -> Result<(CellValue, CellValue), Error> { // `x(pk_d)` = `b_3 (4 bits) || c (250 bits) || d_0 (1 bit)` // - d_0 = 1 => b_3 + 2^4 c < t_P // - 0 ≤ b_3 + 2^4 c < 2^134 // - b_3 is part of the Sinsemilla message piece // b = b_0 (4 bits) || b_1 (1 bit) || b_2 (1 bit) || b_3 (4 bits) // - b_3 is individually constrained to be 4 bits. // - z_13 of SinsemillaHash(c) == 0 constrains bits 4..=253 of pkd_x // to 130 bits. z13_c is directly checked in the gate. // - 0 ≤ b_3 + 2^4 c + 2^140 - t_P < 2^140 (14 ten-bit lookups) // Decompose the low 140 bits of b3_c_prime = b_3 + 2^4 c + 2^140 - t_P, // and output the running sum at the end of it. // If b3_c_prime < 2^140, the running sum will be 0. let b3_c_prime = b_3.value().zip(c.value()).map(|(b_3, c)| { let two_pow_4 = pallas::Base::from_u64(1u64 << 4); let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square(); let t_p = pallas::Base::from_u128(T_P); b_3 + (two_pow_4 * c) + two_pow_140 - t_p }); let zs = self.sinsemilla_config.lookup_config.witness_check( layouter.namespace(|| "Decompose low 140 bits of (b_3 + 2^4 c + 2^140 - t_P)"), b3_c_prime, 14, false, )?; let b3_c_prime = zs[0]; assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14] Ok((b3_c_prime, zs[14])) } #[allow(clippy::type_complexity)] // Check canonicity of `rho` encoding fn rho_canonicity( &self, mut layouter: impl Layouter, e_1: CellValue, f: CellValue, ) -> Result<(CellValue, CellValue), Error> { // `rho` = `e_1 (4 bits) || f (250 bits) || g_0 (1 bit)` // - g_0 = 1 => e_1 + 2^4 f < t_P // - 0 ≤ e_1 + 2^4 f < 2^134 // - e_1 is part of the Sinsemilla message piece // e = e_0 (56 bits) || e_1 (4 bits) // - e_1 is individually constrained to be 4 bits. // - z_13 of SinsemillaHash(f) == 0 constrains bits 4..=253 of rho // to 130 bits. z13_f == 0 is directly checked in the gate. // - 0 ≤ e_1 + 2^4 f + 2^140 - t_P < 2^140 (14 ten-bit lookups) let e1_f_prime = e_1.value().zip(f.value()).map(|(e_1, f)| { let two_pow_4 = pallas::Base::from_u64(1u64 << 4); let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square(); let t_p = pallas::Base::from_u128(T_P); e_1 + (two_pow_4 * f) + two_pow_140 - t_p }); // Decompose the low 140 bits of e1_f_prime = e_1 + 2^4 f + 2^140 - t_P, // and output the running sum at the end of it. // If e1_f_prime < 2^140, the running sum will be 0. let zs = self.sinsemilla_config.lookup_config.witness_check( layouter.namespace(|| "Decompose low 140 bits of (e_1 + 2^4 f + 2^140 - t_P)"), e1_f_prime, 14, false, )?; let e1_f_prime = zs[0]; assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14] Ok((e1_f_prime, zs[14])) } // Check canonicity of `psi` encoding fn psi_canonicity( &self, mut layouter: impl Layouter, g_1: CellValue, g_2: CellValue, ) -> Result<(CellValue, CellValue), Error> { // `psi` = `g_1 (9 bits) || g_2 (240 bits) || h_0 (5 bits) || h_1 (1 bit)` // - h_1 = 1 => (h_0 = 0) ∧ (g_1 + 2^9 g_2 < t_P) // - 0 ≤ g_1 + 2^9 g_2 < 2^130 // - g_1 is individually constrained to be 9 bits // - z_13 of SinsemillaHash(g) == 0 constrains bits 0..=248 of psi // to 130 bits. z13_g == 0 is directly checked in the gate. // - 0 ≤ g_1 + (2^9)g_2 + 2^130 - t_P < 2^130 (13 ten-bit lookups) // Decompose the low 130 bits of g1_g2_prime = g_1 + (2^9)g_2 + 2^130 - t_P, // and output the running sum at the end of it. // If g1_g2_prime < 2^130, the running sum will be 0. let g1_g2_prime = g_1.value().zip(g_2.value()).map(|(g_1, g_2)| { let two_pow_9 = pallas::Base::from_u64(1u64 << 9); let two_pow_130 = pallas::Base::from_u128(1u128 << 65).square(); let t_p = pallas::Base::from_u128(T_P); g_1 + (two_pow_9 * g_2) + two_pow_130 - t_p }); let zs = self.sinsemilla_config.lookup_config.witness_check( layouter.namespace(|| "Decompose low 130 bits of (g_1 + (2^9)g_2 + 2^130 - t_P)"), g1_g2_prime, 13, false, )?; let g1_g2_prime = zs[0]; assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z_13] Ok((g1_g2_prime, zs[13])) } // Check canonicity of y-coordinate given its LSB as a value. // Also, witness the LSB and return the witnessed cell. fn y_canonicity( &self, mut layouter: impl Layouter, y: CellValue, lsb: Option, ) -> Result, Error> { // Decompose the field element // y = LSB || k_0 || k_1 || k_2 || k_3 // = (bit 0) || (bits 1..=9) || (bits 10..=249) || (bits 250..=253) || (bit 254) let (k_0, k_1, k_2, k_3) = { let k_0 = y.value().map(|y| bitrange_subset(y, 1..10)); let k_1 = y.value().map(|y| bitrange_subset(y, 10..250)); let k_2 = y.value().map(|y| bitrange_subset(y, 250..254)); let k_3 = y.value().map(|y| bitrange_subset(y, 254..255)); (k_0, k_1, k_2, k_3) }; // Range-constrain k_0 to be 9 bits. let k_0 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "Constrain k_0 to be 9 bits"), k_0, 9, )?; // Range-constrain k_2 to be 4 bits. let k_2 = self.sinsemilla_config.lookup_config.witness_short_check( layouter.namespace(|| "Constrain k_2 to be 4 bits"), k_2, 4, )?; // Decompose j = LSB + (2)k_0 + (2^10)k_1 using 25 ten-bit lookups. let (j, z1_j, z13_j) = { let j = lsb.zip(k_0.value()).zip(k_1).map(|((lsb, k_0), k_1)| { let two = pallas::Base::from_u64(2); let two_pow_10 = pallas::Base::from_u64(1 << 10); lsb + two * k_0 + two_pow_10 * k_1 }); let zs = self.sinsemilla_config.lookup_config.witness_check( layouter.namespace(|| "Decompose j = LSB + (2)k_0 + (2^10)k_1"), j, 25, true, )?; (zs[0], zs[1], zs[13]) }; // Decompose j_prime = j + 2^130 - t_P using 13 ten-bit lookups. // We can reuse the canon_bitshift_130 logic here. let (j_prime, z13_j_prime) = self.canon_bitshift_130(layouter.namespace(|| "j_prime = j + 2^130 - t_P"), j)?; /* Assign y canonicity gate in the following configuration: | A_5 | A_6 | A_7 | A_8 | A_9 | ---------------------------------------------- | y | lsb | k_0 | k_2 | k_3 | | j | z1_j| z13_j | j_prime | z13_j_prime | where z1_j = k_1. */ layouter.assign_region( || "y canonicity", |mut region| { self.q_y_canon.enable(&mut region, 0)?; // Offset 0 let lsb = { let offset = 0; // Copy y. copy(&mut region, || "copy y", self.advices[5], offset, &y)?; // Witness LSB. let lsb = { let cell = region.assign_advice( || "witness LSB", self.advices[6], offset, || lsb.ok_or(Error::SynthesisError), )?; CellValue::new(cell, lsb) }; // Witness k_0. copy(&mut region, || "copy k_0", self.advices[7], offset, &k_0)?; // Copy k_2. copy(&mut region, || "copy k_2", self.advices[8], offset, &k_2)?; // Witness k_3. region.assign_advice( || "witness k_3", self.advices[9], offset, || k_3.ok_or(Error::SynthesisError), )?; lsb }; // Offset 1 { let offset = 1; // Copy j. copy(&mut region, || "copy j", self.advices[5], offset, &j)?; // Copy z1_j. copy(&mut region, || "copy z1_j", self.advices[6], offset, &z1_j)?; // Copy z13_j. copy( &mut region, || "copy z13_j", self.advices[7], offset, &z13_j, )?; // Copy j_prime. copy( &mut region, || "copy j_prime", self.advices[8], offset, &j_prime, )?; // Copy z13_j_prime. copy( &mut region, || "copy z13_j_prime", self.advices[9], offset, &z13_j_prime, )?; } Ok(lsb) }, ) } fn assign_gate( &self, mut layouter: impl Layouter, gate_cells: GateCells, ) -> Result<(), Error> { // Columns used for MessagePiece gates. let col_l = self.advices[6]; let col_m = self.advices[7]; let col_r = self.advices[8]; let col_z = self.advices[9]; // | A_6 | A_7 | A_8 | q_notecommit_b | // ------------------------------------ // | b | b_0 | b_1 | 1 | // | | b_2 | b_3 | 0 | let b_1 = layouter.assign_region( || "NoteCommit MessagePiece b", |mut region| { self.q_notecommit_b.enable(&mut region, 0)?; copy(&mut region, || "b", col_l, 0, &gate_cells.b)?; copy(&mut region, || "b_0", col_m, 0, &gate_cells.b_0)?; let b_1 = { let cell = region.assign_advice( || "b_1", col_r, 0, || gate_cells.b_1.ok_or(Error::SynthesisError), )?; CellValue::new(cell, gate_cells.b_1) }; copy(&mut region, || "b_2", col_m, 1, &gate_cells.b_2)?; copy(&mut region, || "b_3", col_r, 1, &gate_cells.b_3)?; Ok(b_1) }, )?; // | A_6 | A_7 | A_8 | q_notecommit_d | // ------------------------------------ // | d | d_0 | d_1 | 1 | // | | d_2 | d_3 | 0 | let d_0 = layouter.assign_region( || "NoteCommit MessagePiece d", |mut region| { self.q_notecommit_d.enable(&mut region, 0)?; copy(&mut region, || "d", col_l, 0, &gate_cells.d)?; let d_0 = { let cell = region.assign_advice( || "d_0", col_m, 0, || gate_cells.d_0.ok_or(Error::SynthesisError), )?; CellValue::new(cell, gate_cells.d_0) }; copy(&mut region, || "d_1", col_r, 0, &gate_cells.d_1)?; copy(&mut region, || "d_2", col_m, 1, &gate_cells.d_2)?; copy(&mut region, || "d_3 = z1_d", col_r, 1, &gate_cells.z1_d)?; Ok(d_0) }, )?; // | A_6 | A_7 | A_8 | q_notecommit_e | // ------------------------------------ // | e | e_0 | e_1 | 1 | layouter.assign_region( || "NoteCommit MessagePiece e", |mut region| { self.q_notecommit_e.enable(&mut region, 0)?; copy(&mut region, || "e", col_l, 0, &gate_cells.e)?; copy(&mut region, || "e_0", col_m, 0, &gate_cells.e_0)?; copy(&mut region, || "e_1", col_r, 0, &gate_cells.e_1)?; Ok(()) }, )?; // | A_6 | A_7 | q_notecommit_g | // ------------------------------ // | g | g_0 | 1 | // | g_1 | g_2 | 0 | let g_0 = layouter.assign_region( || "NoteCommit MessagePiece g", |mut region| { self.q_notecommit_g.enable(&mut region, 0)?; copy(&mut region, || "g", col_l, 0, &gate_cells.g)?; let g_0 = { let cell = region.assign_advice( || "g_0", col_m, 0, || gate_cells.g_0.ok_or(Error::SynthesisError), )?; CellValue::new(cell, gate_cells.g_0) }; copy(&mut region, || "g_1", col_l, 1, &gate_cells.g_1)?; copy(&mut region, || "g_2 = z1_g", col_m, 1, &gate_cells.z1_g)?; Ok(g_0) }, )?; // | A_6 | A_7 | A_8 | q_notecommit_h | // ------------------------------------ // | h | h_0 | h_1 | 1 | let h_1 = layouter.assign_region( || "NoteCommit MessagePiece h", |mut region| { self.q_notecommit_h.enable(&mut region, 0)?; copy(&mut region, || "h", col_l, 0, &gate_cells.h)?; copy(&mut region, || "h_0", col_m, 0, &gate_cells.h_0)?; let h_1 = { let cell = region.assign_advice( || "h_1", col_r, 0, || gate_cells.h_1.ok_or(Error::SynthesisError), )?; CellValue::new(cell, gate_cells.h_1) }; Ok(h_1) }, )?; // | A_6 | A_7 | A_8 | A_9 | q_notecommit_g_d | // ----------------------------------------------------------- // | x(g_d) | b_0 | a | z13_a | 1 | // | | b_1 | a_prime | z13_a_prime | 0 | layouter.assign_region( || "NoteCommit input g_d", |mut region| { copy(&mut region, || "gd_x", col_l, 0, &gate_cells.gd_x)?; copy(&mut region, || "b_0", col_m, 0, &gate_cells.b_0)?; copy(&mut region, || "b_1", col_m, 1, &b_1)?; copy(&mut region, || "a", col_r, 0, &gate_cells.a)?; copy(&mut region, || "a_prime", col_r, 1, &gate_cells.a_prime)?; copy(&mut region, || "z13_a", col_z, 0, &gate_cells.z13_a)?; copy( &mut region, || "z13_a_prime", col_z, 1, &gate_cells.z13_a_prime, )?; self.q_notecommit_g_d.enable(&mut region, 0) }, )?; // | A_6 | A_7 | A_8 | A_9 | q_notecommit_pk_d | // ------------------------------------------------------------------- // | x(pk_d) | b_3 | c | z13_c | 1 | // | | d_0 | b3_c_prime | z14_b3_c_prime | 0 | layouter.assign_region( || "NoteCommit input pk_d", |mut region| { copy(&mut region, || "pkd_x", col_l, 0, &gate_cells.pkd_x)?; copy(&mut region, || "b_3", col_m, 0, &gate_cells.b_3)?; copy(&mut region, || "d_0", col_m, 1, &d_0)?; copy(&mut region, || "c", col_r, 0, &gate_cells.c)?; copy( &mut region, || "b3_c_prime", col_r, 1, &gate_cells.b3_c_prime, )?; copy(&mut region, || "z13_c", col_z, 0, &gate_cells.z13_c)?; copy( &mut region, || "z14_b3_c_prime", col_z, 1, &gate_cells.z14_b3_c_prime, )?; self.q_notecommit_pk_d.enable(&mut region, 0) }, )?; // | value | d_2 | d_3 | e_0 | layouter.assign_region( || "NoteCommit input value", |mut region| { copy(&mut region, || "value", col_l, 0, &gate_cells.value)?; copy(&mut region, || "d_2", col_m, 0, &gate_cells.d_2)?; copy(&mut region, || "d3 = z1_d", col_r, 0, &gate_cells.z1_d)?; copy(&mut region, || "e_0", col_z, 0, &gate_cells.e_0)?; self.q_notecommit_value.enable(&mut region, 0) }, )?; // | A_6 | A_7 | A_8 | A_9 | q_notecommit_rho | // -------------------------------------------------------------- // | rho | e_1 | f | z13_f | 1 | // | | g_0 | e1_f_prime | z14_e1_f_prime | 0 | layouter.assign_region( || "NoteCommit input rho", |mut region| { copy(&mut region, || "rho", col_l, 0, &gate_cells.rho)?; copy(&mut region, || "e_1", col_m, 0, &gate_cells.e_1)?; copy(&mut region, || "g_0", col_m, 1, &g_0)?; copy(&mut region, || "f", col_r, 0, &gate_cells.f)?; copy( &mut region, || "e1_f_prime", col_r, 1, &gate_cells.e1_f_prime, )?; copy(&mut region, || "z13_f", col_z, 0, &gate_cells.z13_f)?; copy( &mut region, || "z14_e1_f_prime", col_z, 1, &gate_cells.z14_e1_f_prime, )?; self.q_notecommit_rho.enable(&mut region, 0) }, )?; // | A_6 | A_7 | A_8 | A_9 | q_notecommit_psi | // ---------------------------------------------------------------- // | psi | g_1 | g_2 | z13_g | 1 | // | h_0 | h_1 | g1_g2_prime | z13_g1_g2_prime | 0 | layouter.assign_region( || "NoteCommit input psi", |mut region| { copy(&mut region, || "psi", col_l, 0, &gate_cells.psi)?; copy(&mut region, || "h_0", col_l, 1, &gate_cells.h_0)?; copy(&mut region, || "g_1", col_m, 0, &gate_cells.g_1)?; copy(&mut region, || "h_1", col_m, 1, &h_1)?; copy(&mut region, || "g_2 = z1_g", col_r, 0, &gate_cells.z1_g)?; copy( &mut region, || "g1_g2_prime", col_r, 1, &gate_cells.g1_g2_prime, )?; copy(&mut region, || "z13_g", col_z, 0, &gate_cells.z13_g)?; copy( &mut region, || "z13_g1_g2_prime", col_z, 1, &gate_cells.z13_g1_g2_prime, )?; self.q_notecommit_psi.enable(&mut region, 0) }, ) } } struct GateCells { a: CellValue, b: CellValue, b_0: CellValue, b_1: Option, b_2: CellValue, b_3: CellValue, c: CellValue, d: CellValue, d_0: Option, d_1: CellValue, d_2: CellValue, z1_d: CellValue, e: CellValue, e_0: CellValue, e_1: CellValue, f: CellValue, g: CellValue, g_0: Option, g_1: CellValue, z1_g: CellValue, h: CellValue, h_0: CellValue, h_1: Option, gd_x: CellValue, pkd_x: CellValue, value: CellValue, rho: CellValue, psi: CellValue, a_prime: CellValue, b3_c_prime: CellValue, e1_f_prime: CellValue, g1_g2_prime: CellValue, z13_a_prime: CellValue, z14_b3_c_prime: CellValue, z14_e1_f_prime: CellValue, z13_g1_g2_prime: CellValue, z13_a: CellValue, z13_c: CellValue, z13_f: CellValue, z13_g: CellValue, } #[cfg(test)] mod tests { use super::NoteCommitConfig; use crate::{ circuit::gadget::{ ecc::{ chip::{EccChip, EccConfig}, NonIdentityPoint, }, sinsemilla::chip::SinsemillaChip, utilities::{ lookup_range_check::LookupRangeCheckConfig, CellValue, UtilitiesInstructions, }, }, constants::{L_ORCHARD_BASE, L_VALUE, NOTE_COMMITMENT_PERSONALIZATION, T_Q}, primitives::sinsemilla::CommitDomain, }; use ff::{Field, PrimeField, PrimeFieldBits}; use group::Curve; use halo2::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, plonk::{Circuit, ConstraintSystem, Error}, }; use pasta_curves::{ arithmetic::{CurveAffine, FieldExt}, pallas, }; use rand::{rngs::OsRng, RngCore}; use std::convert::TryInto; #[test] fn note_commit() { #[derive(Default)] struct MyCircuit { gd_x: Option, gd_y_lsb: Option, pkd_x: Option, pkd_y_lsb: Option, rho: Option, psi: Option, } impl UtilitiesInstructions for MyCircuit { type Var = CellValue; } impl Circuit for MyCircuit { type Config = (NoteCommitConfig, EccConfig); type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { Self::default() } fn configure(meta: &mut ConstraintSystem) -> 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. let constants = meta.fixed_column(); meta.enable_constant(constants); for advice in advices.iter() { meta.enable_equality((*advice).into()); } let table_idx = meta.lookup_table_column(); let lookup = ( table_idx, meta.lookup_table_column(), meta.lookup_table_column(), ); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), meta.fixed_column(), meta.fixed_column(), meta.fixed_column(), meta.fixed_column(), meta.fixed_column(), meta.fixed_column(), ]; let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx); let sinsemilla_config = SinsemillaChip::configure( meta, advices[..5].try_into().unwrap(), advices[2], lagrange_coeffs[0], lookup, range_check.clone(), ); let note_commit_config = NoteCommitConfig::configure(meta, advices, sinsemilla_config); let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check); (note_commit_config, ecc_config) } fn synthesize( &self, config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { let (note_commit_config, ecc_config) = config; // Load the Sinsemilla generator lookup table used by the whole circuit. SinsemillaChip::load(note_commit_config.sinsemilla_config.clone(), &mut layouter)?; // Construct a Sinsemilla chip let sinsemilla_chip = SinsemillaChip::construct(note_commit_config.sinsemilla_config.clone()); // Construct an ECC chip let ecc_chip = EccChip::construct(ecc_config); // Witness g_d let g_d = { let g_d = self.gd_x.zip(self.gd_y_lsb).map(|(x, y_lsb)| { // Calculate y = (x^3 + 5).sqrt() let mut y = (x.square() * x + pallas::Affine::b()).sqrt().unwrap(); if bool::from(y.is_odd() ^ y_lsb.is_odd()) { y = -y; } pallas::Affine::from_xy(x, y).unwrap() }); NonIdentityPoint::new( ecc_chip.clone(), layouter.namespace(|| "witness g_d"), g_d, )? }; // Witness pk_d let pk_d = { let pk_d = self.pkd_x.zip(self.pkd_y_lsb).map(|(x, y_lsb)| { // Calculate y = (x^3 + 5).sqrt() let mut y = (x.square() * x + pallas::Affine::b()).sqrt().unwrap(); if bool::from(y.is_odd() ^ y_lsb.is_odd()) { y = -y; } pallas::Affine::from_xy(x, y).unwrap() }); NonIdentityPoint::new( ecc_chip.clone(), layouter.namespace(|| "witness pk_d"), pk_d, )? }; // Witness a random non-negative u64 note value // A note value cannot be negative. let value = { let mut rng = OsRng; pallas::Base::from_u64(rng.next_u64()) }; let value_var = { self.load_private( layouter.namespace(|| "witness value"), note_commit_config.advices[0], Some(value), )? }; // Witness rho let rho = self.load_private( layouter.namespace(|| "witness rho"), note_commit_config.advices[0], self.rho, )?; // Witness psi let psi = self.load_private( layouter.namespace(|| "witness psi"), note_commit_config.advices[0], self.psi, )?; let rcm = pallas::Scalar::rand(); let cm = note_commit_config.assign_region( layouter.namespace(|| "Hash NoteCommit pieces"), sinsemilla_chip, ecc_chip.clone(), g_d.inner(), pk_d.inner(), value_var, rho, psi, Some(rcm), )?; let expected_cm = { let domain = CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); // Hash g★_d || pk★_d || i2lebsp_{64}(v) || rho || psi let lsb = |y_lsb: pallas::Base| y_lsb == pallas::Base::one(); let point = domain .commit( std::iter::empty() .chain( self.gd_x .unwrap() .to_le_bits() .iter() .by_val() .take(L_ORCHARD_BASE), ) .chain(Some(lsb(self.gd_y_lsb.unwrap()))) .chain( self.pkd_x .unwrap() .to_le_bits() .iter() .by_val() .take(L_ORCHARD_BASE), ) .chain(Some(lsb(self.pkd_y_lsb.unwrap()))) .chain(value.to_le_bits().iter().by_val().take(L_VALUE)) .chain( self.rho .unwrap() .to_le_bits() .iter() .by_val() .take(L_ORCHARD_BASE), ) .chain( self.psi .unwrap() .to_le_bits() .iter() .by_val() .take(L_ORCHARD_BASE), ), &rcm, ) .unwrap() .to_affine(); NonIdentityPoint::new( ecc_chip, layouter.namespace(|| "witness cm"), Some(point), )? }; cm.constrain_equal(layouter.namespace(|| "cm == expected cm"), &expected_cm) } } let two_pow_254 = pallas::Base::from_u128(1 << 127).square(); // Test different values of `ak`, `nk` let circuits = [ // `gd_x` = -1, `pkd_x` = -1 (these have to be x-coordinates of curve points) // `rho` = 0, `psi` = 0 MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::one()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::one()), rho: Some(pallas::Base::zero()), psi: Some(pallas::Base::zero()), }, // `rho` = T_Q - 1, `psi` = T_Q - 1 MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::zero()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::zero()), rho: Some(pallas::Base::from_u128(T_Q - 1)), psi: Some(pallas::Base::from_u128(T_Q - 1)), }, // `rho` = T_Q, `psi` = T_Q MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::one()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::zero()), rho: Some(pallas::Base::from_u128(T_Q)), psi: Some(pallas::Base::from_u128(T_Q)), }, // `rho` = 2^127 - 1, `psi` = 2^127 - 1 MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::zero()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::one()), rho: Some(pallas::Base::from_u128((1 << 127) - 1)), psi: Some(pallas::Base::from_u128((1 << 127) - 1)), }, // `rho` = 2^127, `psi` = 2^127 MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::zero()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::zero()), rho: Some(pallas::Base::from_u128(1 << 127)), psi: Some(pallas::Base::from_u128(1 << 127)), }, // `rho` = 2^254 - 1, `psi` = 2^254 - 1 MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::one()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::one()), rho: Some(two_pow_254 - pallas::Base::one()), psi: Some(two_pow_254 - pallas::Base::one()), }, // `rho` = 2^254, `psi` = 2^254 MyCircuit { gd_x: Some(-pallas::Base::one()), gd_y_lsb: Some(pallas::Base::one()), pkd_x: Some(-pallas::Base::one()), pkd_y_lsb: Some(pallas::Base::zero()), rho: Some(two_pow_254), psi: Some(two_pow_254), }, ]; for circuit in circuits.iter() { let prover = MockProver::::run(11, circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())); } } }