diff --git a/taiga_halo2/Cargo.toml b/taiga_halo2/Cargo.toml index 59503543..ad375da4 100644 --- a/taiga_halo2/Cargo.toml +++ b/taiga_halo2/Cargo.toml @@ -8,6 +8,7 @@ rand = "0.8" lazy_static = "1.4" blake2b_simd = "1.0" pasta_curves = "0.5.1" +blake2s_simd = "1" ff = "0.13" group = "0.13" halo2_gadgets = { git = "https://github.com/heliaxdev/halo2", branch = "taiga", features = ["test-dependencies"] } diff --git a/taiga_halo2/benches/action_proof.rs b/taiga_halo2/benches/action_proof.rs index 436d4f8d..28a6fe7b 100644 --- a/taiga_halo2/benches/action_proof.rs +++ b/taiga_halo2/benches/action_proof.rs @@ -66,8 +66,8 @@ fn bench_action_proof(name: &str, c: &mut Criterion) { } }; let input_merkle_path = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); - let rcv = pallas::Scalar::random(&mut rng); - ActionInfo::new(input_note, input_merkle_path, output_note, rcv) + let rseed = RandomSeed::random(&mut rng); + ActionInfo::new(input_note, input_merkle_path, output_note, rseed) }; let (action, action_circuit) = action_info.build(); let params = SETUP_PARAMS_MAP.get(&ACTION_CIRCUIT_PARAMS_SIZE).unwrap(); diff --git a/taiga_halo2/benches/vp_proof.rs b/taiga_halo2/benches/vp_proof.rs index 7672c210..61de2863 100644 --- a/taiga_halo2/benches/vp_proof.rs +++ b/taiga_halo2/benches/vp_proof.rs @@ -7,7 +7,7 @@ use rand::rngs::OsRng; use rand::Rng; use taiga_halo2::{ circuit::{vp_circuit::ValidityPredicateCircuit, vp_examples::TrivialValidityPredicateCircuit}, - constant::{NUM_NOTE, SETUP_PARAMS_MAP}, + constant::{NUM_NOTE, SETUP_PARAMS_MAP, VP_CIRCUIT_PARAMS_SIZE}, note::{Note, NoteType, RandomSeed}, nullifier::{Nullifier, NullifierKeyContainer}, proof::Proof, @@ -71,7 +71,7 @@ fn bench_vp_proof(name: &str, c: &mut Criterion) { output_notes.try_into().unwrap(), ) }; - let params = SETUP_PARAMS_MAP.get(&12).unwrap(); + let params = SETUP_PARAMS_MAP.get(&VP_CIRCUIT_PARAMS_SIZE).unwrap(); let empty_circuit: TrivialValidityPredicateCircuit = Default::default(); let vk = keygen_vk(params, &empty_circuit).expect("keygen_vk should not fail"); let pk = keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail"); diff --git a/taiga_halo2/src/action.rs b/taiga_halo2/src/action.rs index 77fadff6..8e10e021 100644 --- a/taiga_halo2/src/action.rs +++ b/taiga_halo2/src/action.rs @@ -1,11 +1,12 @@ use crate::{ circuit::action_circuit::ActionCircuit, + constant::{PRF_EXPAND_INPUT_VP_CM_R, PRF_EXPAND_OUTPUT_VP_CM_R}, merkle_tree::{MerklePath, Node}, - note::{InputNoteProvingInfo, Note, OutputNoteProvingInfo}, + note::{InputNoteProvingInfo, Note, OutputNoteProvingInfo, RandomSeed}, nullifier::Nullifier, value_commitment::ValueCommitment, + vp_commitment::ValidityPredicateCommitment, }; -use halo2_proofs::arithmetic::Field; use pasta_curves::pallas; use rand::RngCore; @@ -28,10 +29,14 @@ pub struct ActionInstance { pub anchor: pallas::Base, /// The nullifier of input note. pub nf: Nullifier, - /// The commitment of the output note. + /// The commitment to the output note. pub cm_x: pallas::Base, - /// net value commitment + /// The commitment to net value pub cv_net: ValueCommitment, + /// The commitment to input note application(static) vp + pub input_vp_commitment: ValidityPredicateCommitment, + /// The commitment to output note application(static) vp + pub output_vp_commitment: ValidityPredicateCommitment, } /// The information to build ActionInstance and ActionCircuit. @@ -40,17 +45,24 @@ pub struct ActionInfo { input_note: Note, input_merkle_path: MerklePath, output_note: Note, - rcv: pallas::Scalar, + // rseed is to generate the randomness of the value commitment and vp commitments + rseed: RandomSeed, } impl ActionInstance { pub fn to_instance(&self) -> Vec { + let input_vp_commitment = self.input_vp_commitment.to_public_inputs(); + let output_vp_commitment = self.output_vp_commitment.to_public_inputs(); vec![ self.nf.inner(), self.anchor, self.cm_x, self.cv_net.get_x(), self.cv_net.get_y(), + input_vp_commitment[0], + input_vp_commitment[1], + output_vp_commitment[0], + output_vp_commitment[1], ] } } @@ -63,6 +75,8 @@ impl BorshSerialize for ActionInstance { writer.write_all(&self.nf.to_bytes())?; writer.write_all(&self.cm_x.to_repr())?; writer.write_all(&self.cv_net.to_bytes())?; + writer.write_all(&self.input_vp_commitment.to_bytes())?; + writer.write_all(&self.output_vp_commitment.to_bytes())?; Ok(()) } } @@ -84,12 +98,20 @@ impl BorshDeserialize for ActionInstance { let cv_net_bytes = <[u8; 32]>::deserialize_reader(reader)?; let cv_net = Option::from(ValueCommitment::from_bytes(cv_net_bytes)) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "cv_net not in field"))?; + let input_vp_commitment_bytes = <[u8; 32]>::deserialize_reader(reader)?; + let input_vp_commitment = + ValidityPredicateCommitment::from_bytes(input_vp_commitment_bytes); + let output_vp_commitment_bytes = <[u8; 32]>::deserialize_reader(reader)?; + let output_vp_commitment = + ValidityPredicateCommitment::from_bytes(output_vp_commitment_bytes); Ok(ActionInstance { anchor, nf, cm_x, cv_net, + input_vp_commitment, + output_vp_commitment, }) } } @@ -99,13 +121,13 @@ impl ActionInfo { input_note: Note, input_merkle_path: MerklePath, output_note: Note, - rcv: pallas::Scalar, + rseed: RandomSeed, ) -> Self { Self { input_note, input_merkle_path, output_note, - rcv, + rseed, } } @@ -114,17 +136,28 @@ impl ActionInfo { output: OutputNoteProvingInfo, mut rng: R, ) -> Self { - let rcv = pallas::Scalar::random(&mut rng); + let rseed = RandomSeed::random(&mut rng); Self { input_note: input.note, input_merkle_path: input.merkle_path, output_note: output.note, - rcv, + rseed, } } + // Get the randomness of value commitment pub fn get_rcv(&self) -> pallas::Scalar { - self.rcv + self.rseed.get_rcv() + } + + // Get the randomness of input note application vp commitment + pub fn get_input_vp_com_r(&self) -> pallas::Base { + self.rseed.get_vp_cm_r(PRF_EXPAND_INPUT_VP_CM_R) + } + + // Get the randomness of output note application vp commitment + pub fn get_output_vp_com_r(&self) -> pallas::Base { + self.rseed.get_vp_cm_r(PRF_EXPAND_OUTPUT_VP_CM_R) } pub fn build(&self) -> (ActionInstance, ActionCircuit) { @@ -140,19 +173,33 @@ impl ActionInfo { self.input_merkle_path.root(cm_node).inner() }; - let cv_net = ValueCommitment::new(&self.input_note, &self.output_note, &self.rcv); + let rcv = self.get_rcv(); + let cv_net = ValueCommitment::new(&self.input_note, &self.output_note, &rcv); + + let input_vp_cm_r = self.get_input_vp_com_r(); + let input_vp_commitment = + ValidityPredicateCommitment::commit(&self.input_note.get_app_vk(), &input_vp_cm_r); + + let output_vp_cm_r = self.get_output_vp_com_r(); + let output_vp_commitment = + ValidityPredicateCommitment::commit(&self.output_note.get_app_vk(), &output_vp_cm_r); + let action = ActionInstance { nf, cm_x, anchor, cv_net, + input_vp_commitment, + output_vp_commitment, }; let action_circuit = ActionCircuit { input_note: self.input_note, merkle_path: self.input_merkle_path.get_path().try_into().unwrap(), output_note: self.output_note, - rcv: self.rcv, + rcv, + input_vp_cm_r, + output_vp_cm_r, }; (action, action_circuit) @@ -165,15 +212,14 @@ pub mod tests { use crate::constant::TAIGA_COMMITMENT_TREE_DEPTH; use crate::merkle_tree::MerklePath; use crate::note::tests::{random_input_note, random_output_note}; - use halo2_proofs::arithmetic::Field; - use pasta_curves::pallas; + use crate::note::RandomSeed; use rand::RngCore; pub fn random_action_info(mut rng: R) -> ActionInfo { let input_note = random_input_note(&mut rng); let output_note = random_output_note(&mut rng, input_note.get_nf().unwrap()); let input_merkle_path = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); - let rcv = pallas::Scalar::random(&mut rng); - ActionInfo::new(input_note, input_merkle_path, output_note, rcv) + let rseed = RandomSeed::random(&mut rng); + ActionInfo::new(input_note, input_merkle_path, output_note, rseed) } } diff --git a/taiga_halo2/src/circuit/action_circuit.rs b/taiga_halo2/src/circuit/action_circuit.rs index 3f733306..37bf5692 100644 --- a/taiga_halo2/src/circuit/action_circuit.rs +++ b/taiga_halo2/src/circuit/action_circuit.rs @@ -1,4 +1,5 @@ -use crate::circuit::gadgets::add::AddChip; +use crate::circuit::blake2s::{vp_commitment_gadget, Blake2sChip, Blake2sConfig}; +use crate::circuit::gadgets::{add::AddChip, assign_free_advice}; use crate::circuit::hash_to_curve::HashToCurveConfig; use crate::circuit::integrity::{check_input_note, check_output_note, compute_value_commitment}; use crate::circuit::merkle_circuit::{ @@ -7,16 +8,17 @@ use crate::circuit::merkle_circuit::{ use crate::circuit::note_circuit::{NoteChip, NoteCommitmentChip, NoteConfig}; use crate::constant::{ NoteCommitmentDomain, NoteCommitmentHashDomain, TaigaFixedBases, - ACTION_ANCHOR_PUBLIC_INPUT_ROW_IDX, ACTION_NET_VALUE_CM_X_PUBLIC_INPUT_ROW_IDX, - ACTION_NET_VALUE_CM_Y_PUBLIC_INPUT_ROW_IDX, ACTION_NF_PUBLIC_INPUT_ROW_IDX, - ACTION_OUTPUT_CM_PUBLIC_INPUT_ROW_IDX, TAIGA_COMMITMENT_TREE_DEPTH, + ACTION_ANCHOR_PUBLIC_INPUT_ROW_IDX, ACTION_INPUT_VP_CM_1_ROW_IDX, ACTION_INPUT_VP_CM_2_ROW_IDX, + ACTION_NET_VALUE_CM_X_PUBLIC_INPUT_ROW_IDX, ACTION_NET_VALUE_CM_Y_PUBLIC_INPUT_ROW_IDX, + ACTION_NF_PUBLIC_INPUT_ROW_IDX, ACTION_OUTPUT_CM_PUBLIC_INPUT_ROW_IDX, + ACTION_OUTPUT_VP_CM_1_ROW_IDX, ACTION_OUTPUT_VP_CM_2_ROW_IDX, TAIGA_COMMITMENT_TREE_DEPTH, }; use crate::merkle_tree::LR; use crate::note::Note; use halo2_gadgets::{ecc::chip::EccChip, sinsemilla::chip::SinsemillaChip}; use halo2_proofs::{ - circuit::{floor_planner, Layouter}, + circuit::{floor_planner, Layouter, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Constraints, Error, Instance, Selector}, poly::Rotation, }; @@ -30,6 +32,7 @@ pub struct ActionConfig { merkle_config: MerklePoseidonConfig, merkle_path_selector: Selector, hash_to_curve_config: HashToCurveConfig, + blake2s_config: Blake2sConfig, } /// The Action circuit. @@ -43,6 +46,10 @@ pub struct ActionCircuit { pub output_note: Note, /// random scalar for net value commitment pub rcv: pallas::Scalar, + /// The randomness for input note application vp commitment + pub input_vp_cm_r: pallas::Base, + /// The randomness for output note application vp commitment + pub output_vp_cm_r: pallas::Base, } impl Circuit for ActionCircuit { @@ -101,6 +108,8 @@ impl Circuit for ActionCircuit { let hash_to_curve_config = HashToCurveConfig::configure(meta, advices, note_config.poseidon_config.clone()); + let blake2s_config = Blake2sConfig::configure(meta, advices); + Self::Config { instances, advices, @@ -108,6 +117,7 @@ impl Circuit for ActionCircuit { merkle_config, merkle_path_selector, hash_to_curve_config, + blake2s_config, } } @@ -139,6 +149,9 @@ impl Circuit for ActionCircuit { // Construct a merkle chip let merkle_chip = MerklePoseidonChip::construct(config.merkle_config); + // Construct a blake2s chip + let blake2s_chip = Blake2sChip::construct(config.blake2s_config); + // Input note // Check the input note commitment let input_note_variables = check_input_note( @@ -162,8 +175,6 @@ impl Circuit for ActionCircuit { &self.merkle_path, )?; - // TODO: user send address VP commitment and application VP commitment - // Output note let output_note_vars = check_output_note( layouter.namespace(|| "check output note"), @@ -178,10 +189,6 @@ impl Circuit for ActionCircuit { ACTION_OUTPUT_CM_PUBLIC_INPUT_ROW_IDX, )?; - // TODO: application VP commitment - - // TODO: add note verifiable encryption - // compute and public net value commitment(input_value_commitment - output_value_commitment) let cv_net = compute_value_commitment( layouter.namespace(|| "net value commitment"), @@ -231,6 +238,52 @@ impl Circuit for ActionCircuit { }, )?; + // Input note application VP commitment + let input_vp_cm_r = assign_free_advice( + layouter.namespace(|| "witness input_vp_cm_r"), + config.advices[0], + Value::known(self.input_vp_cm_r), + )?; + let input_vp_commitment = vp_commitment_gadget( + &mut layouter, + &blake2s_chip, + input_note_variables.note_variables.app_vk.clone(), + input_vp_cm_r, + )?; + layouter.constrain_instance( + input_vp_commitment[0].cell(), + config.instances, + ACTION_INPUT_VP_CM_1_ROW_IDX, + )?; + layouter.constrain_instance( + input_vp_commitment[1].cell(), + config.instances, + ACTION_INPUT_VP_CM_2_ROW_IDX, + )?; + + // Output note application VP commitment + let output_vp_cm_r = assign_free_advice( + layouter.namespace(|| "witness output_vp_cm_r"), + config.advices[0], + Value::known(self.output_vp_cm_r), + )?; + let output_vp_commitment = vp_commitment_gadget( + &mut layouter, + &blake2s_chip, + output_note_vars.note_variables.app_vk.clone(), + output_vp_cm_r, + )?; + layouter.constrain_instance( + output_vp_commitment[0].cell(), + config.instances, + ACTION_OUTPUT_VP_CM_1_ROW_IDX, + )?; + layouter.constrain_instance( + output_vp_commitment[1].cell(), + config.instances, + ACTION_OUTPUT_VP_CM_2_ROW_IDX, + )?; + Ok(()) } } diff --git a/taiga_halo2/src/circuit/blake2s.rs b/taiga_halo2/src/circuit/blake2s.rs new file mode 100644 index 00000000..91d7a5f0 --- /dev/null +++ b/taiga_halo2/src/circuit/blake2s.rs @@ -0,0 +1,1198 @@ +use super::gadgets::assign_free_advice; +use crate::circuit::gadgets::assign_free_constant; +use crate::constant::{ + VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_1, VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_2, + VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_1, VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_2, + VP_COMMITMENT_PERSONALIZATION, +}; +use crate::vp_commitment::ValidityPredicateCommitment; +use byteorder::{ByteOrder, LittleEndian}; +use group::ff::PrimeField; +use halo2_gadgets::utilities::bool_check; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Instance, Selector, VirtualCells, + }, + poly::Rotation, +}; +use std::{convert::TryInto, marker::PhantomData}; + +pub fn vp_commitment_gadget( + layouter: &mut impl Layouter, + blake2s_chip: &Blake2sChip, + vp: AssignedCell, + rcm: AssignedCell, +) -> Result<[AssignedCell; 2], Error> { + let hash = blake2s_chip.process(layouter, &[vp, rcm], VP_COMMITMENT_PERSONALIZATION)?; + blake2s_chip.encode_result(layouter, &hash) +} + +pub fn publicize_default_dynamic_vp_commitments( + layouter: &mut impl Layouter, + advice: Column, + instances: Column, +) -> Result<(), Error> { + let vp_cm_fields: [F; 2] = ValidityPredicateCommitment::default().to_public_inputs(); + let vp_cm_1 = assign_free_advice( + layouter.namespace(|| "vp_cm 1"), + advice, + Value::known(vp_cm_fields[0]), + )?; + let vp_cm_2 = assign_free_advice( + layouter.namespace(|| "vp_cm 2"), + advice, + Value::known(vp_cm_fields[1]), + )?; + + layouter.constrain_instance(vp_cm_1.cell(), instances, VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_1)?; + layouter.constrain_instance(vp_cm_2.cell(), instances, VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_2)?; + layouter.constrain_instance(vp_cm_1.cell(), instances, VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_1)?; + layouter.constrain_instance(vp_cm_2.cell(), instances, VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_2)?; + + Ok(()) +} + +// | BLAKE2s | +// --------------+------------------+ +// Bits in word | w = 32 | +// Rounds in F | r = 10 | +// Block bytes | bb = 64 | +// Hash bytes | 1 <= nn <= 32 | +// Key bytes | 0 <= kk <= 32 | +// Input bytes | 0 <= ll < 2**64 | +// --------------+------------------+ +// G Rotation | (R1, R2, R3, R4) | +// constants = | (16, 12, 8, 7) | +// --------------+------------------+ + +// BLAKE2 CONSTANTS +// ---------------- + +// Initialisation Vector (IV) +const IV: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +// The SIGMA constant in Blake2s is a 10x16 array that defines the message permutations in the algorithm. Each of the 10 rows corresponds to a round of the hashing process, and each of the 16 elements in the row determines the message block order. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +// G Rotation constants +const R1: usize = 16; +const R2: usize = 12; +const R3: usize = 8; +const R4: usize = 7; + +const ROUNDS: usize = 10; + +// --------------- + +#[derive(Clone, Debug)] +pub struct Blake2sChip { + config: Blake2sConfig, + _marker: PhantomData, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Blake2sConfig { + pub advices: [Column; 10], + pub s_field_decompose: Selector, + pub s_word_decompose: Selector, + pub s_byte_decompose: Selector, + pub s_byte_xor: Selector, + pub s_word_add: Selector, + pub s_result_encode: Selector, + _marker: PhantomData, +} + +// One blockword has 4 bytes(32bits). +#[derive(Clone, Debug)] +pub struct Blake2sWord { + word: AssignedCell, + bits: [AssignedCell; 32], +} + +// One byte has 8 bits. +#[derive(Clone, Debug)] +struct Blake2sByte { + byte: AssignedCell, + bits: [AssignedCell; 8], +} + +impl Blake2sByte { + pub fn get_byte(&self) -> AssignedCell { + self.byte.clone() + } + + pub fn get_bits(&self) -> &[AssignedCell; 8] { + &self.bits + } + + pub fn from_u8( + value: Value, + mut layouter: impl Layouter, + config: &Blake2sConfig, + ) -> Result { + layouter.assign_region( + || "decompose bytes to bits", + |mut region| { + config.s_byte_decompose.enable(&mut region, 0)?; + let mut byte = value; + let mut bits = Vec::with_capacity(8); + for i in 0..8 { + let bit = byte.map(|b| F::from((b & 1) as u64)); + let bit_var = region.assign_advice(|| "bit", config.advices[i], 0, || bit)?; + bits.push(bit_var); + byte = byte.map(|b| b >> 1); + } + let byte = region.assign_advice( + || "byte", + config.advices[0], + 1, + || value.map(|v| F::from(v as u64)), + )?; + Ok(Self { + byte, + bits: bits.try_into().unwrap(), + }) + }, + ) + } + + pub fn from_constant_u8( + value: u8, + layouter: &mut impl Layouter, + config: &Blake2sConfig, + ) -> Result { + layouter.assign_region( + || "decompose bytes to bits", + |mut region| { + config.s_byte_decompose.enable(&mut region, 0)?; + let mut byte = value; + let mut bits = Vec::with_capacity(8); + for i in 0..8 { + let bit = byte & 1; + let bit_var = region.assign_advice_from_constant( + || "bit", + config.advices[i], + 0, + F::from(bit as u64), + )?; + bits.push(bit_var); + byte >>= 1; + } + let byte = region.assign_advice_from_constant( + || "byte", + config.advices[0], + 1, + F::from(value as u64), + )?; + Ok(Self { + byte, + bits: bits.try_into().unwrap(), + }) + }, + ) + } +} + +impl Blake2sConfig { + pub fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 10], + ) -> Blake2sConfig { + let s_field_decompose = meta.selector(); + let s_word_decompose = meta.selector(); + let s_byte_decompose = meta.selector(); + let s_byte_xor = meta.selector(); + let s_word_add = meta.selector(); + let s_result_encode = meta.selector(); + + meta.create_gate("decompose field to words", |meta| { + let field_element = meta.query_advice(advices[0], Rotation::next()); + let word_1 = meta.query_advice(advices[0], Rotation::cur()); + let word_2 = meta.query_advice(advices[1], Rotation::cur()); + let word_3 = meta.query_advice(advices[2], Rotation::cur()); + let word_4 = meta.query_advice(advices[3], Rotation::cur()); + let word_5 = meta.query_advice(advices[4], Rotation::cur()); + let word_6 = meta.query_advice(advices[5], Rotation::cur()); + let word_7 = meta.query_advice(advices[6], Rotation::cur()); + let word_8 = meta.query_advice(advices[7], Rotation::cur()); + let s_field_decompose = meta.query_selector(s_field_decompose); + + vec![ + s_field_decompose + * (word_1 + + word_2 * F::from(1 << 32) + + word_3 * F::from_u128(1 << 64) + + word_4 * F::from_u128(1 << 96) + + word_5 * F::from_u128(1 << 64).square() + + word_6 * F::from_u128(1 << 80).square() + + word_7 * F::from_u128(1 << 96).square() + + word_8 * F::from_u128(1 << 112).square() + - field_element), + ] + }); + + meta.create_gate("decompose word to bytes", |meta| { + let word = meta.query_advice(advices[0], Rotation::next()); + let byte_1 = meta.query_advice(advices[0], Rotation::cur()); + let byte_2 = meta.query_advice(advices[1], Rotation::cur()); + let byte_3 = meta.query_advice(advices[2], Rotation::cur()); + let byte_4 = meta.query_advice(advices[3], Rotation::cur()); + let s_word_decompose = meta.query_selector(s_word_decompose); + + vec![ + s_word_decompose + * (byte_1 + + byte_2 * F::from(1 << 8) + + byte_3 * F::from(1 << 16) + + byte_4 * F::from(1 << 24) + - word), + ] + }); + + meta.create_gate("decompose byte to bits", |meta| { + let byte = meta.query_advice(advices[0], Rotation::next()); + let bit_1 = meta.query_advice(advices[0], Rotation::cur()); + let bit_2 = meta.query_advice(advices[1], Rotation::cur()); + let bit_3 = meta.query_advice(advices[2], Rotation::cur()); + let bit_4 = meta.query_advice(advices[3], Rotation::cur()); + let bit_5 = meta.query_advice(advices[4], Rotation::cur()); + let bit_6 = meta.query_advice(advices[5], Rotation::cur()); + let bit_7 = meta.query_advice(advices[6], Rotation::cur()); + let bit_8 = meta.query_advice(advices[7], Rotation::cur()); + let s_byte_decompose = meta.query_selector(s_byte_decompose); + + vec![ + s_byte_decompose + * (bit_1 + + bit_2 * F::from(1 << 1) + + bit_3 * F::from(1 << 2) + + bit_4 * F::from(1 << 3) + + bit_5 * F::from(1 << 4) + + bit_6 * F::from(1 << 5) + + bit_7 * F::from(1 << 6) + + bit_8 * F::from(1 << 7) + - byte), + ] + }); + + meta.create_gate("byte xor", |meta| { + let s_byte_xor = meta.query_selector(s_byte_xor); + let bit_xor = |idx: usize, meta: &mut VirtualCells| { + let lhs_bit = meta.query_advice(advices[idx], Rotation::prev()); + let rhs_bit = meta.query_advice(advices[idx], Rotation::cur()); + let out_bit = meta.query_advice(advices[idx], Rotation::next()); + lhs_bit.clone() + rhs_bit.clone() - lhs_bit * rhs_bit * F::from(2) - out_bit + }; + + Constraints::with_selector( + s_byte_xor, + std::iter::empty() + .chain((0..8).map(|idx| bit_xor(idx, meta))) + .collect::>(), + ) + }); + + meta.create_gate("word add", |meta| { + let s_word_add = meta.query_selector(s_word_add); + let lhs = meta.query_advice(advices[0], Rotation::cur()); + let rhs = meta.query_advice(advices[1], Rotation::cur()); + let out = meta.query_advice(advices[0], Rotation::next()); + let carry = meta.query_advice(advices[1], Rotation::next()); + let equal = lhs + rhs - carry.clone() * F::from(1 << 32) - out; + + Constraints::with_selector( + s_word_add, + [ + ("carry bool check", bool_check(carry)), + ("equal check", equal), + ], + ) + }); + + meta.create_gate("encode four words to one field", |meta| { + let field_element = meta.query_advice(advices[0], Rotation::next()); + let word_1 = meta.query_advice(advices[0], Rotation::cur()); + let word_2 = meta.query_advice(advices[1], Rotation::cur()); + let word_3 = meta.query_advice(advices[2], Rotation::cur()); + let word_4 = meta.query_advice(advices[3], Rotation::cur()); + let s_result_encode = meta.query_selector(s_result_encode); + + vec![ + s_result_encode + * (word_1 + + word_2 * F::from(1 << 32) + + word_3 * F::from_u128(1 << 64) + + word_4 * F::from_u128(1 << 96) + - field_element), + ] + }); + + Blake2sConfig { + advices, + s_field_decompose, + s_word_decompose, + s_byte_decompose, + s_byte_xor, + s_word_add, + s_result_encode, + _marker: PhantomData, + } + } +} + +impl Blake2sChip { + pub fn construct(config: Blake2sConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn process( + &self, + layouter: &mut impl Layouter, + inputs: &[AssignedCell], + personalization: &[u8], + ) -> Result>, Error> { + assert_eq!(personalization.len(), 8); + assert!(inputs.len() % 2 == 0); + + // Init + let mut h = vec![ + Blake2sWord::from_constant_u32(IV[0] ^ 0x01010000 ^ 32, layouter, self)?, + Blake2sWord::from_constant_u32(IV[1], layouter, self)?, + Blake2sWord::from_constant_u32(IV[2], layouter, self)?, + Blake2sWord::from_constant_u32(IV[3], layouter, self)?, + Blake2sWord::from_constant_u32(IV[4], layouter, self)?, + Blake2sWord::from_constant_u32(IV[5], layouter, self)?, + Blake2sWord::from_constant_u32( + IV[6] ^ LittleEndian::read_u32(&personalization[0..4]), + layouter, + self, + )?, + Blake2sWord::from_constant_u32( + IV[7] ^ LittleEndian::read_u32(&personalization[4..8]), + layouter, + self, + )?, + ]; + + // Handle message: convert field message to blocks. + let mut blocks = vec![]; + for block in inputs.chunks(2) { + let mut cur_block = Vec::with_capacity(16); + for field in block.iter() { + let mut words = self.field_decompose(layouter, field)?; + cur_block.append(&mut words); + } + blocks.push(cur_block); + } + + if blocks.is_empty() { + let zero_padding_block = (0..16) + .map(|_| Blake2sWord::from_constant_u32(0, layouter, self).unwrap()) + .collect(); + blocks.push(zero_padding_block); + } + + let block_len = blocks.len(); + + for (i, block) in blocks[0..(block_len - 1)].iter().enumerate() { + self.compress(layouter, &mut h, block, (i as u64 + 1) * 64, false)?; + } + + // Compress(Final block) + self.compress( + layouter, + &mut h, + &blocks[block_len - 1], + (block_len as u64) * 64, + true, + )?; + + Ok(h) + } + + // Encode the eight words to two field elements + pub fn encode_result( + &self, + layouter: &mut impl Layouter, + ret: &Vec>, + ) -> Result<[AssignedCell; 2], Error> { + let mut fields = vec![]; + assert_eq!(ret.len(), 8); + for words in ret.chunks(4) { + let field = layouter.assign_region( + || "encode four words to one field", + |mut region| { + self.config.s_result_encode.enable(&mut region, 0)?; + for (i, word) in words.iter().enumerate() { + word.get_word().copy_advice( + || "word", + &mut region, + self.config.advices[i], + 0, + )?; + } + let word_values: Value> = + words.iter().map(|word| word.get_word().value()).collect(); + let field_value = word_values.map(|words| { + words + .into_iter() + .rev() + .fold(F::ZERO, |acc, byte| acc * F::from(1 << 32) + byte) + }); + region.assign_advice( + || "result field", + self.config.advices[0], + 1, + || field_value, + ) + }, + )?; + fields.push(field); + } + assert_eq!(fields.len(), 2); + Ok(fields.try_into().unwrap()) + } + + // Compression function F takes as an argument the state vector "h", + // message block vector "m" (last block is padded with zeros to full + // block size, if required), 2w-bit offset counter "t", and final block + // indicator flag "f". Local vector v[0..15] is used in processing. F + // returns a new state vector. The number of rounds, "r", is 12 for + // BLAKE2b and 10 for BLAKE2s. Rounds are numbered from 0 to r - 1. + + // FUNCTION F( h[0..7], m[0..15], t, f ) + // | + // | // Initialize local work vector v[0..15] + // | v[0..7] := h[0..7] // First half from state. + // | v[8..15] := IV[0..7] // Second half from IV. + // | + // | v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. + // | v[13] := v[13] ^ (t >> w) // High word. + // | + // | IF f = TRUE THEN // last block flag? + // | | v[14] := v[14] ^ 0xFF..FF // Invert all bits. + // | END IF. + // | + // | // Cryptographic mixing + // | FOR i = 0 TO r - 1 DO // Ten or twelve rounds. + // | | + // | | // Message word selection permutation for this round. + // | | s[0..15] := SIGMA[i mod 10][0..15] + // | | + // | | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) + // | | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) + // | | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) + // | | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) + // | | + // | | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) + // | | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) + // | | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) + // | | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) + // | | + // | END FOR + // | + // | FOR i = 0 TO 7 DO // XOR the two halves. + // | | h[i] := h[i] ^ v[i] ^ v[i + 8] + // | END FOR. + // | + // | RETURN h[0..7] // New state. + // | + // END FUNCTION. + fn compress( + &self, + layouter: &mut impl Layouter, + h: &mut [Blake2sWord], // current state + m: &[Blake2sWord], // current block + t: u64, // offset counter + f: bool, // final flag + ) -> Result<(), Error> { + let mut v = Vec::with_capacity(16); + v.extend_from_slice(h); + for iv in IV[0..4].iter() { + let word = Blake2sWord::from_constant_u32(*iv, layouter, self)?; + v.push(word); + } + // v[12] := v[12] ^ (t mod 2**w) + let v_12 = Blake2sWord::from_constant_u32(IV[4] ^ (t as u32), layouter, self)?; + v.push(v_12); + + // v[13] := v[13] ^ (t >> w) + let v_13 = Blake2sWord::from_constant_u32(IV[5] ^ ((t >> 32) as u32), layouter, self)?; + v.push(v_13); + + // IF f = TRUE THEN // last block flag? + // | v[14] := v[14] ^ 0xFF..FF // Invert all bits. + // END IF. + let v_14 = if f { + Blake2sWord::from_constant_u32(IV[6] ^ u32::max_value(), layouter, self)? + } else { + Blake2sWord::from_constant_u32(IV[6], layouter, self)? + }; + v.push(v_14); + + // v_15 + let v_15 = Blake2sWord::from_constant_u32(IV[7], layouter, self)?; + v.push(v_15); + assert_eq!(v.len(), 16); + + for i in 0..ROUNDS { + let s = SIGMA[i % ROUNDS]; + self.g( + layouter.namespace(|| "mixing 1"), + &mut v, + (0, 4, 8, 12), + &m[s[0]], + &m[s[1]], + )?; + self.g( + layouter.namespace(|| "mixing 2"), + &mut v, + (1, 5, 9, 13), + &m[s[2]], + &m[s[3]], + )?; + self.g( + layouter.namespace(|| "mixing 3"), + &mut v, + (2, 6, 10, 14), + &m[s[4]], + &m[s[5]], + )?; + self.g( + layouter.namespace(|| "mixing 4"), + &mut v, + (3, 7, 11, 15), + &m[s[6]], + &m[s[7]], + )?; + + self.g( + layouter.namespace(|| "mixing 5"), + &mut v, + (0, 5, 10, 15), + &m[s[8]], + &m[s[9]], + )?; + self.g( + layouter.namespace(|| "mixing 6"), + &mut v, + (1, 6, 11, 12), + &m[s[10]], + &m[s[11]], + )?; + self.g( + layouter.namespace(|| "mixing 7"), + &mut v, + (2, 7, 8, 13), + &m[s[12]], + &m[s[13]], + )?; + self.g( + layouter.namespace(|| "mixing 8"), + &mut v, + (3, 4, 9, 14), + &m[s[14]], + &m[s[15]], + )?; + } + + // Finalize the state + for i in 0..8 { + let h_i_bits = self.word_xor( + layouter.namespace(|| "final first xor"), + h[i].get_bits(), + v[i].get_bits(), + )?; + let h_i_bits = self.word_xor( + layouter.namespace(|| "final second xor"), + &h_i_bits, + v[i + 8].get_bits(), + )?; + h[i] = Blake2sWord::from_bits( + self, + layouter.namespace(|| "construct word from bits"), + h_i_bits, + )?; + } + + Ok(()) + } + + // The G primitive function mixes two input words, "x" and "y", into + // four words indexed by "a", "b", "c", and "d" in the working vector + // v[0..15]. The full modified vector is returned. The rotation + // constants + // FUNCTION G( v[0..15], a, b, c, d, x, y ) + // | + // | v[a] := (v[a] + v[b] + x) mod 2**w + // | v[d] := (v[d] ^ v[a]) >>> R1 + // | v[c] := (v[c] + v[d]) mod 2**w + // | v[b] := (v[b] ^ v[c]) >>> R2 + // | v[a] := (v[a] + v[b] + y) mod 2**w + // | v[d] := (v[d] ^ v[a]) >>> R3 + // | v[c] := (v[c] + v[d]) mod 2**w + // | v[b] := (v[b] ^ v[c]) >>> R4 + // | + // | RETURN v[0..15] + // | + // END FUNCTION. + fn g( + &self, + mut layouter: impl Layouter, + v: &mut [Blake2sWord], + (a, b, c, d): (usize, usize, usize, usize), + x: &Blake2sWord, + y: &Blake2sWord, + ) -> Result<(), Error> { + // v[a] := (v[a] + v[b] + x) mod 2**w + v[a] = { + let sum_a_b = self.add_mod_u32( + layouter.namespace(|| "add_mod_u32"), + v[a].get_word(), + v[b].get_word(), + )?; + let sum_a_b_x = + self.add_mod_u32(layouter.namespace(|| "add_mod_u32"), &sum_a_b, x.get_word())?; + Blake2sWord::from_word(self, layouter.namespace(|| "from word"), sum_a_b_x)? + }; + + // v[d] := (v[d] ^ v[a]) >>> R1 + v[d] = { + let d_xor_a = self.word_xor( + layouter.namespace(|| "xor"), + v[d].get_bits(), + v[a].get_bits(), + )?; + let bits = Blake2sWord::word_rotate(&d_xor_a, R1); + Blake2sWord::from_bits(self, layouter.namespace(|| "from bits"), bits)? + }; + + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = { + let sum = self.add_mod_u32( + layouter.namespace(|| "add_mod_u32"), + v[c].get_word(), + v[d].get_word(), + )?; + Blake2sWord::from_word(self, layouter.namespace(|| "from word"), sum)? + }; + + // v[b] := (v[b] ^ v[c]) >>> R2 + v[b] = { + let b_xor_c = self.word_xor( + layouter.namespace(|| "xor"), + v[b].get_bits(), + v[c].get_bits(), + )?; + let bits = Blake2sWord::word_rotate(&b_xor_c, R2); + Blake2sWord::from_bits(self, layouter.namespace(|| "from bits"), bits)? + }; + + // v[a] := (v[a] + v[b] + y) mod 2**w + v[a] = { + let sum_a_b = self.add_mod_u32( + layouter.namespace(|| "add_mod_u32"), + v[a].get_word(), + v[b].get_word(), + )?; + let sum_a_b_y = + self.add_mod_u32(layouter.namespace(|| "add_mod_u32"), &sum_a_b, y.get_word())?; + Blake2sWord::from_word(self, layouter.namespace(|| "from word"), sum_a_b_y)? + }; + + // v[d] := (v[d] ^ v[a]) >>> R3 + v[d] = { + let d_xor_a = self.word_xor( + layouter.namespace(|| "xor"), + v[d].get_bits(), + v[a].get_bits(), + )?; + let bits = Blake2sWord::word_rotate(&d_xor_a, R3); + Blake2sWord::from_bits(self, layouter.namespace(|| "from bits"), bits)? + }; + + // v[c] := (v[c] + v[d]) mod 2**w + v[c] = { + let sum = self.add_mod_u32( + layouter.namespace(|| "add_mod_u32"), + v[c].get_word(), + v[d].get_word(), + )?; + Blake2sWord::from_word(self, layouter.namespace(|| "from word"), sum)? + }; + + // v[b] := (v[b] ^ v[c]) >>> R4 + v[b] = { + let b_xor_c = self.word_xor( + layouter.namespace(|| "xor"), + v[b].get_bits(), + v[c].get_bits(), + )?; + let bits = Blake2sWord::word_rotate(&b_xor_c, R4); + Blake2sWord::from_bits(self, layouter.namespace(|| "from bits"), bits)? + }; + + Ok(()) + } + + // Decompose a field to words + fn field_decompose( + &self, + layouter: &mut impl Layouter, + field: &AssignedCell, + ) -> Result>, Error> { + // the decomposition from bytes to bits + let mut bits = vec![]; + let mut bytes = vec![]; + for i in 0..32 { + let byte_value = field.value().map(|f| f.to_repr().as_ref()[i]); + let byte = + Blake2sByte::from_u8(byte_value, layouter.namespace(|| "from_u8"), &self.config)?; + bits.append(&mut byte.get_bits().to_vec()); + bytes.push(byte.get_byte()); + } + + // Check the decomposition from words to bytes + let mut words = vec![]; + for bytes in bytes.chunks(4) { + let word = { + let byte_values: Value> = bytes.iter().map(|byte| byte.value()).collect(); + let word_value = byte_values.map(|bytes| { + bytes + .into_iter() + .rev() + .fold(F::ZERO, |acc, byte| acc * F::from(1 << 8) + byte) + }); + assign_free_advice( + layouter.namespace(|| "assign word"), + self.config.advices[8], + word_value, + )? + }; + self.word_decompose(layouter.namespace(|| "word decompose"), bytes, &word)?; + words.push(word); + } + + // check the decomposition from field to words + layouter.assign_region( + || "decompose field to words", + |mut region| { + self.config.s_field_decompose.enable(&mut region, 0)?; + for (i, word) in words.iter().enumerate() { + word.copy_advice(|| "word", &mut region, self.config.advices[i], 0)?; + } + field.copy_advice(|| "field", &mut region, self.config.advices[0], 1)?; + Ok(()) + }, + )?; + + let res = bits + .chunks(32) + .zip(words) + .map(|(bits, word)| Blake2sWord { + word, + bits: bits.to_vec().try_into().unwrap(), + }) + .collect::>(); + + Ok(res) + } + + // decompose a word to four bytes + fn word_decompose( + &self, + mut layouter: impl Layouter, + bytes: &[AssignedCell], + word: &AssignedCell, + ) -> Result<(), Error> { + assert_eq!(bytes.len(), 4); + layouter.assign_region( + || "decompose word to bytes", + |mut region| { + self.config.s_word_decompose.enable(&mut region, 0)?; + for (i, byte) in bytes.iter().enumerate() { + byte.copy_advice(|| "byte", &mut region, self.config.advices[i], 0)?; + } + word.copy_advice(|| "word", &mut region, self.config.advices[0], 1)?; + Ok(()) + }, + ) + } + + // decompose from a byte to eight bits + fn byte_decompose( + &self, + mut layouter: impl Layouter, + bits: &[AssignedCell], + byte: &AssignedCell, + ) -> Result<(), Error> { + assert_eq!(bits.len(), 8); + layouter.assign_region( + || "decompose byte to bits", + |mut region| { + self.config.s_byte_decompose.enable(&mut region, 0)?; + for (i, bit) in bits.iter().enumerate() { + bit.copy_advice(|| "bit", &mut region, self.config.advices[i], 0)?; + } + byte.copy_advice(|| "byte", &mut region, self.config.advices[0], 1)?; + Ok(()) + }, + ) + } + + fn byte_xor( + &self, + mut layouter: impl Layouter, + x: &[AssignedCell], + y: &[AssignedCell], + ) -> Result>, Error> { + assert_eq!(x.len(), 8); + assert_eq!(y.len(), 8); + layouter.assign_region( + || "byte xor", + |mut region| { + self.config.s_byte_xor.enable(&mut region, 1)?; + let xor = |x: &F, y: &F| -> F { + F::from(((x.is_odd()) ^ (y.is_odd())).unwrap_u8() as u64) + }; + let mut byte_ret = Vec::with_capacity(8); + for i in 0..8 { + x[i].copy_advice(|| "xor bit x", &mut region, self.config.advices[i], 0)?; + y[i].copy_advice(|| "xor bit y", &mut region, self.config.advices[i], 1)?; + let result_bits = x[i] + .value() + .zip(y[i].value()) + .map(|(x_bit, y_bit)| xor(x_bit, y_bit)); + let ret = region.assign_advice( + || "xor bit result", + self.config.advices[i], + 2, + || result_bits, + )?; + byte_ret.push(ret); + } + + Ok(byte_ret) + }, + ) + } + + fn word_xor( + &self, + mut layouter: impl Layouter, + x: &[AssignedCell], + y: &[AssignedCell], + ) -> Result>, Error> { + assert_eq!(x.len(), 32); + assert_eq!(y.len(), 32); + let mut bits = Vec::with_capacity(32); + for (x_byte, y_byte) in x.chunks(8).zip(y.chunks(8)) { + let mut ret = self.byte_xor(layouter.namespace(|| "byte xor"), x_byte, y_byte)?; + bits.append(&mut ret); + } + + Ok(bits) + } + + fn add_mod_u32( + &self, + mut layouter: impl Layouter, + // x and y must be a word variable + x: &AssignedCell, + y: &AssignedCell, + ) -> Result, Error> { + layouter.assign_region( + || "decompose bytes to bits", + |mut region| { + self.config.s_word_add.enable(&mut region, 0)?; + x.copy_advice(|| "word_add x", &mut region, self.config.advices[0], 0)?; + y.copy_advice(|| "word_add y", &mut region, self.config.advices[1], 0)?; + let sum = x.value().zip(y.value()).map(|(&x, &y)| { + let sum = x + y; + let carry = F::from(sum.to_repr().as_ref()[4] as u64); + let ret = sum - carry * F::from(1 << 32); + (ret, carry) + }); + let ret = region.assign_advice( + || "word_add ret", + self.config.advices[0], + 1, + || sum.map(|sum| sum.0), + )?; + region.assign_advice( + || "word_add carry", + self.config.advices[1], + 1, + || sum.map(|sum| sum.1), + )?; + Ok(ret) + }, + ) + } +} + +impl Blake2sWord { + pub fn from_constant_u32( + value: u32, + layouter: &mut impl Layouter, + chip: &Blake2sChip, + ) -> Result { + let mut bytes = Vec::with_capacity(4); + let mut word_bits = Vec::with_capacity(32); + let mut tmp = value; + for _ in 0..4 { + let input_byte = tmp as u8; + let byte = Blake2sByte::from_constant_u8(input_byte, layouter, &chip.config)?; + bytes.push(byte.get_byte()); + word_bits.append(&mut byte.get_bits().to_vec()); + tmp >>= 8; + } + let word = assign_free_constant( + layouter.namespace(|| "constant word"), + chip.config.advices[0], + F::from(value as u64), + )?; + chip.word_decompose(layouter.namespace(|| "word decompose"), &bytes, &word)?; + Ok(Self { + word, + bits: word_bits.try_into().unwrap(), + }) + } + + pub fn word_rotate(bits: &Vec>, by: usize) -> Vec> { + assert!(bits.len() == 32); + let by = by % 32; + bits.iter() + .skip(by) + .chain(bits.iter()) + .take(32) + .cloned() + .collect() + } + + pub fn shift( + &self, + by: usize, + mut layouter: impl Layouter, + advice: Column, + ) -> Result>, Error> { + let by = by % 32; + let padding_zero = assign_free_constant(layouter.namespace(|| "zero"), advice, F::from(0))?; + let old_bits = self.get_bits(); + Ok(old_bits + .iter() + .skip(by) + .chain(Some(&padding_zero).into_iter().cycle()) + .take(32) + .cloned() + .collect()) + } + + pub fn get_bits(&self) -> &[AssignedCell; 32] { + &self.bits + } + + pub fn get_word(&self) -> &AssignedCell { + &self.word + } + + pub fn from_bits( + chip: &Blake2sChip, + mut layouter: impl Layouter, + bits: Vec>, + ) -> Result { + assert!(bits.len() == 32); + let mut bytes = Vec::with_capacity(4); + for bits in bits.chunks(8) { + let bit_values: Value> = bits.iter().map(|bit| bit.value()).collect(); + let byte_value = bit_values.map(|bits| { + bits.into_iter() + .rev() + .fold(F::ZERO, |acc, bit| acc * F::from(2) + bit) + }); + let byte = assign_free_advice( + layouter.namespace(|| "assign byte"), + chip.config.advices[8], + byte_value, + )?; + chip.byte_decompose(layouter.namespace(|| "byte decompose"), bits, &byte)?; + bytes.push(byte); + } + let word = { + let byte_values: Value> = bytes.iter().map(|byte| byte.value()).collect(); + let word_value = byte_values.map(|bytes| { + bytes + .into_iter() + .rev() + .fold(F::ZERO, |acc, byte| acc * F::from(1 << 8) + byte) + }); + assign_free_advice( + layouter.namespace(|| "assign word"), + chip.config.advices[8], + word_value, + )? + }; + chip.word_decompose(layouter.namespace(|| "word decompose"), &bytes, &word)?; + Ok(Self { + word, + bits: bits.try_into().unwrap(), + }) + } + + pub fn from_word( + chip: &Blake2sChip, + mut layouter: impl Layouter, + word: AssignedCell, + ) -> Result { + let mut bytes = Vec::with_capacity(4); + let mut bits = Vec::with_capacity(32); + for i in 0..4 { + let byte_value = word.value().map(|v| v.to_repr().as_ref()[i]); + let byte = + Blake2sByte::from_u8(byte_value, layouter.namespace(|| "from_u8"), &chip.config)?; + bits.append(&mut byte.get_bits().to_vec()); + bytes.push(byte.get_byte()); + } + + chip.word_decompose(layouter.namespace(|| "word decompose"), &bytes, &word)?; + Ok(Self { + word, + bits: bits.try_into().unwrap(), + }) + } +} + +#[test] +fn test_blake2s_circuit() { + use crate::{ + circuit::gadgets::assign_free_advice, constant::VP_COMMITMENT_PERSONALIZATION, + vp_commitment::ValidityPredicateCommitment, + }; + use halo2_proofs::{ + circuit::{floor_planner, Layouter, Value}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use pasta_curves::pallas; + + #[derive(Default)] + struct MyCircuit {} + + impl Circuit for MyCircuit { + type Config = Blake2sConfig; + type FloorPlanner = floor_planner::V1; + + 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(), + ]; + + for advice in advices.iter() { + meta.enable_equality(*advice); + } + + let constants = meta.fixed_column(); + meta.enable_constant(constants); + Blake2sConfig::configure(meta, advices) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let vp = pallas::Base::one(); + let rcm = pallas::Base::one(); + let vp_var = assign_free_advice( + layouter.namespace(|| "message one"), + config.advices[0], + Value::known(vp), + )?; + let rcm_var = assign_free_advice( + layouter.namespace(|| "message two"), + config.advices[0], + Value::known(rcm), + )?; + + let blake2s_chip = Blake2sChip::construct(config); + let words_result = blake2s_chip.process( + &mut layouter, + &[vp_var, rcm_var], + VP_COMMITMENT_PERSONALIZATION, + )?; + + let expect_ret = ValidityPredicateCommitment::commit(&vp, &rcm); + let expect_words_result: Vec = expect_ret + .to_bytes() + .chunks(4) + .map(LittleEndian::read_u32) + .collect(); + + for (word, expect_word) in words_result.iter().zip(expect_words_result.into_iter()) { + let expect_word_var = assign_free_advice( + layouter.namespace(|| "expected words"), + config.advices[0], + Value::known(pallas::Base::from(expect_word as u64)), + )?; + layouter.assign_region( + || "constrain result", + |mut region| { + region.constrain_equal(word.get_word().cell(), expect_word_var.cell()) + }, + )?; + } + + let expect_field_ret: [pallas::Base; 2] = expect_ret.to_public_inputs(); + let field_ret = blake2s_chip.encode_result(&mut layouter, &words_result)?; + + for (field, expect_field) in field_ret.iter().zip(expect_field_ret.into_iter()) { + let expect_field_var = assign_free_advice( + layouter.namespace(|| "expected field"), + config.advices[0], + Value::known(expect_field), + )?; + layouter.assign_region( + || "constrain result", + |mut region| region.constrain_equal(field.cell(), expect_field_var.cell()), + )?; + } + + Ok(()) + } + } + + let circuit = MyCircuit {}; + + let prover = MockProver::run(14, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); +} diff --git a/taiga_halo2/src/circuit/gadgets.rs b/taiga_halo2/src/circuit/gadgets.rs index 8ae58d8d..fd798a60 100644 --- a/taiga_halo2/src/circuit/gadgets.rs +++ b/taiga_halo2/src/circuit/gadgets.rs @@ -6,6 +6,7 @@ use halo2_proofs::{ pub mod add; pub mod conditional_equal; +pub mod conditional_select; pub mod extended_or_relation; pub mod mul; pub mod poseidon_hash; diff --git a/taiga_halo2/src/circuit/gadgets/conditional_select.rs b/taiga_halo2/src/circuit/gadgets/conditional_select.rs new file mode 100644 index 00000000..e4bedc3e --- /dev/null +++ b/taiga_halo2/src/circuit/gadgets/conditional_select.rs @@ -0,0 +1,72 @@ +/// Constrain flag * (lhs - rhs) = 0 +use halo2_proofs::{ + circuit::{AssignedCell, Region}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; + +use pasta_curves::pallas; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ConditionalSelectConfig { + q_conditional_select: Selector, + advice: [Column; 2], +} + +impl ConditionalSelectConfig { + #[allow(clippy::too_many_arguments)] + pub fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 2], + ) -> Self { + let config = Self { + q_conditional_select: meta.selector(), + advice, + }; + + config.create_gate(meta); + + config + } + + fn create_gate(&self, meta: &mut ConstraintSystem) { + meta.create_gate("conditional select", |meta| { + let q_conditional_select = meta.query_selector(self.q_conditional_select); + + let flag = meta.query_advice(self.advice[0], Rotation::cur()); + let ret = meta.query_advice(self.advice[0], Rotation::next()); + let lhs = meta.query_advice(self.advice[1], Rotation::cur()); + let rhs = meta.query_advice(self.advice[1], Rotation::next()); + let poly = + flag.clone() * lhs + (Expression::Constant(pallas::Base::one()) - flag) * rhs - ret; + + Constraints::with_selector( + q_conditional_select, + [("flag * lhs + flag * rhs = ret", poly)], + ) + }); + } + + pub fn assign_region( + &self, + flag: &AssignedCell, + lhs: &AssignedCell, + rhs: &AssignedCell, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result, Error> { + // Enable `q_conditional_select` selector + self.q_conditional_select.enable(region, offset)?; + + flag.copy_advice(|| "flag", region, self.advice[0], offset)?; + let ret_value = flag + .value() + .zip(lhs.value()) + .zip(rhs.value()) + .map(|((flag, &lhs), &rhs)| flag * lhs + (pallas::Base::one() - flag) * rhs); + + lhs.copy_advice(|| "lhs", region, self.advice[1], offset)?; + rhs.copy_advice(|| "rhs", region, self.advice[1], offset + 1)?; + region.assign_advice(|| "ret", self.advice[0], offset + 1, || ret_value) + } +} diff --git a/taiga_halo2/src/circuit/mod.rs b/taiga_halo2/src/circuit/mod.rs index ed2a07f7..9e791a94 100644 --- a/taiga_halo2/src/circuit/mod.rs +++ b/taiga_halo2/src/circuit/mod.rs @@ -5,6 +5,7 @@ pub mod merkle_circuit; pub mod note_circuit; #[macro_use] pub mod vp_circuit; +pub mod blake2s; pub mod curve; pub mod hash_to_curve; pub mod note_encryption_circuit; diff --git a/taiga_halo2/src/circuit/vp_circuit.rs b/taiga_halo2/src/circuit/vp_circuit.rs index 7241baaa..106fb294 100644 --- a/taiga_halo2/src/circuit/vp_circuit.rs +++ b/taiga_halo2/src/circuit/vp_circuit.rs @@ -1,10 +1,13 @@ use crate::circuit::vamp_ir_utils::{get_circuit_assignments, parse, VariableAssignmentError}; use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, + blake2s::Blake2sConfig, gadgets::{ add::{AddChip, AddConfig}, assign_free_advice, conditional_equal::ConditionalEqualConfig, + conditional_select::ConditionalSelectConfig, extended_or_relation::ExtendedOrRelationConfig, mul::{MulChip, MulConfig}, sub::{SubChip, SubConfig}, @@ -308,10 +311,12 @@ pub struct ValidityPredicateConfig { pub get_is_input_note_flag_config: GetIsInputNoteFlagConfig, pub get_owned_note_variable_config: GetOwnedNoteVariableConfig, pub conditional_equal_config: ConditionalEqualConfig, + pub conditional_select_config: ConditionalSelectConfig, pub extended_or_relation_config: ExtendedOrRelationConfig, pub add_config: AddConfig, pub sub_config: SubConfig, pub mul_config: MulConfig, + pub blake2s_config: Blake2sConfig, } impl ValidityPredicateConfig { @@ -349,6 +354,8 @@ impl ValidityPredicateConfig { let conditional_equal_config = ConditionalEqualConfig::configure(meta, [advices[0], advices[1], advices[2]]); + let conditional_select_config = + ConditionalSelectConfig::configure(meta, [advices[0], advices[1]]); let add_config = note_conifg.add_config.clone(); let sub_config = SubChip::configure(meta, [advices[0], advices[1]]); @@ -356,7 +363,7 @@ impl ValidityPredicateConfig { let extended_or_relation_config = ExtendedOrRelationConfig::configure(meta, [advices[0], advices[1], advices[2]]); - + let blake2s_config = Blake2sConfig::configure(meta, advices); Self { note_conifg, advices, @@ -364,10 +371,12 @@ impl ValidityPredicateConfig { get_is_input_note_flag_config, get_owned_note_variable_config, conditional_equal_config, + conditional_select_config, extended_or_relation_config, add_config, sub_config, mul_config, + blake2s_config, } } } @@ -468,12 +477,20 @@ pub trait ValidityPredicateCircuit: Circuit + ValidityPredicateVer // `get_input_notes` and `get_output_notes` will be used in `basic_constraints` to get the basic note info. // Add custom constraints on basic note variables and user-defined variables. + // It should at least contain the default vp commitment fn custom_constraints( &self, - _config: ValidityPredicateConfig, - mut _layouter: impl Layouter, + config: ValidityPredicateConfig, + mut layouter: impl Layouter, _basic_variables: BasicValidityPredicateVariables, ) -> Result<(), Error> { + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -826,7 +843,7 @@ macro_rules! vp_circuit_impl { impl ValidityPredicateVerifyingInfo for $name { fn get_verifying_info(&self) -> VPVerifyingInfo { let mut rng = OsRng; - let params = SETUP_PARAMS_MAP.get(&12).unwrap(); + let params = SETUP_PARAMS_MAP.get(&15).unwrap(); let vk = keygen_vk(params, self).expect("keygen_vk should not fail"); let pk = keygen_pk(params, vk.clone(), self).expect("keygen_pk should not fail"); let public_inputs = self.get_public_inputs(&mut rng); @@ -846,7 +863,7 @@ macro_rules! vp_circuit_impl { } fn get_vp_vk(&self) -> ValidityPredicateVerifyingKey { - let params = SETUP_PARAMS_MAP.get(&12).unwrap(); + let params = SETUP_PARAMS_MAP.get(&15).unwrap(); let vk = keygen_vk(params, self).expect("keygen_vk should not fail"); ValidityPredicateVerifyingKey::from_vk(vk) } diff --git a/taiga_halo2/src/circuit/vp_examples.rs b/taiga_halo2/src/circuit/vp_examples.rs index 1246a5f6..036ec4e0 100644 --- a/taiga_halo2/src/circuit/vp_examples.rs +++ b/taiga_halo2/src/circuit/vp_examples.rs @@ -6,6 +6,7 @@ use crate::{ constant::{NUM_NOTE, SETUP_PARAMS_MAP}, note::{Note, RandomSeed}, proof::Proof, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_proofs::plonk::{keygen_pk, keygen_vk}; @@ -111,6 +112,10 @@ impl ValidityPredicateCircuit for TrivialValidityPredicateCircuit { fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), &RandomSeed::random(&mut rng), @@ -153,6 +158,7 @@ pub mod tests { #[test] fn test_halo2_trivial_vp_circuit() { use crate::circuit::vp_circuit::ValidityPredicateCircuit; + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use halo2_proofs::dev::MockProver; use rand::rngs::OsRng; @@ -160,8 +166,12 @@ pub mod tests { let circuit = random_trivial_vp_circuit(&mut rng); let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } } diff --git a/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs b/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs index b19ccc76..03500720 100644 --- a/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs @@ -6,6 +6,7 @@ /// use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ assign_free_advice, target_note_variable::{get_is_input_note_flag, get_owned_note_variable}, @@ -19,6 +20,7 @@ use crate::{ note::{Note, RandomSeed}, nullifier::{Nullifier, NullifierKeyContainer}, proof::Proof, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_proofs::{ @@ -105,6 +107,13 @@ impl ValidityPredicateCircuit for CascadeIntentValidityPredicateCircuit { }, )?; + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -118,6 +127,10 @@ impl ValidityPredicateCircuit for CascadeIntentValidityPredicateCircuit { fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), &RandomSeed::random(&mut rng), @@ -156,6 +169,7 @@ pub fn create_intent_note( #[test] fn test_halo2_cascade_intent_vp_circuit() { + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::note::tests::{random_input_note, random_output_note}; use halo2_proofs::arithmetic::Field; use halo2_proofs::dev::MockProver; @@ -183,7 +197,11 @@ fn test_halo2_cascade_intent_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } diff --git a/taiga_halo2/src/circuit/vp_examples/field_addition.rs b/taiga_halo2/src/circuit/vp_examples/field_addition.rs index b220f5ec..99805ae0 100644 --- a/taiga_halo2/src/circuit/vp_examples/field_addition.rs +++ b/taiga_halo2/src/circuit/vp_examples/field_addition.rs @@ -1,5 +1,6 @@ use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ add::{AddChip, AddInstructions}, assign_free_advice, @@ -12,6 +13,7 @@ use crate::{ constant::{NUM_NOTE, SETUP_PARAMS_MAP, VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_BEGIN_IDX}, note::{Note, RandomSeed}, proof::Proof, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_proofs::{ @@ -57,13 +59,20 @@ impl ValidityPredicateCircuit for FieldAdditionValidityPredicateCircuit { let c = add_chip.add(layouter.namespace(|| "a + b = c"), &a, &b)?; - // Public c + // Publicize c layouter.constrain_instance( c.cell(), config.instances, VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_BEGIN_IDX, )?; + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -77,6 +86,10 @@ impl ValidityPredicateCircuit for FieldAdditionValidityPredicateCircuit { fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); public_inputs.push(self.a + self.b); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), @@ -95,6 +108,7 @@ vp_circuit_impl!(FieldAdditionValidityPredicateCircuit); #[test] fn test_halo2_addition_vp_circuit() { + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::note::tests::{random_input_note, random_output_note}; use halo2_proofs::arithmetic::Field; use halo2_proofs::dev::MockProver; @@ -120,7 +134,11 @@ fn test_halo2_addition_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } diff --git a/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs b/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs index 63556c09..2aab9fb0 100644 --- a/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs @@ -4,6 +4,7 @@ /// use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ assign_free_advice, assign_free_constant, poseidon_hash::poseidon_hash_gadget, @@ -20,6 +21,7 @@ use crate::{ nullifier::{Nullifier, NullifierKeyContainer}, proof::Proof, utils::poseidon_hash_n, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_proofs::{ @@ -227,6 +229,13 @@ impl ValidityPredicateCircuit for OrRelationIntentValidityPredicateCircuit { }, )?; + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -240,6 +249,10 @@ impl ValidityPredicateCircuit for OrRelationIntentValidityPredicateCircuit { fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), &RandomSeed::random(&mut rng), @@ -283,6 +296,7 @@ pub fn create_intent_note( #[test] fn test_halo2_or_relation_intent_vp_circuit() { + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::{ circuit::vp_examples::token::COMPRESSED_TOKEN_VK, note::tests::random_output_note, nullifier::tests::random_nullifier, @@ -334,7 +348,11 @@ fn test_halo2_or_relation_intent_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } diff --git a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs index 9d8118ac..435adf90 100644 --- a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs @@ -4,6 +4,7 @@ /// use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ assign_free_advice, assign_free_constant, mul::{MulChip, MulInstructions}, @@ -22,6 +23,7 @@ use crate::{ nullifier::{Nullifier, NullifierKeyContainer}, proof::Proof, utils::poseidon_hash_n, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_proofs::{ @@ -380,6 +382,13 @@ impl ValidityPredicateCircuit for PartialFulfillmentIntentValidityPredicateCircu } } + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -393,6 +402,10 @@ impl ValidityPredicateCircuit for PartialFulfillmentIntentValidityPredicateCircu fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), &RandomSeed::random(&mut rng), @@ -436,6 +449,7 @@ pub fn create_intent_note( #[test] fn test_halo2_partial_fulfillment_intent_vp_circuit() { + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::{circuit::vp_examples::token::COMPRESSED_TOKEN_VK, note::tests::random_input_note}; use halo2_proofs::arithmetic::Field; use halo2_proofs::dev::MockProver; @@ -478,8 +492,12 @@ fn test_halo2_partial_fulfillment_intent_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } @@ -513,9 +531,12 @@ fn test_halo2_partial_fulfillment_intent_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]) - .unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } @@ -538,9 +559,12 @@ fn test_halo2_partial_fulfillment_intent_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]) - .unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } } diff --git a/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs b/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs index 6fe7f15b..09617c99 100644 --- a/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs +++ b/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs @@ -1,5 +1,6 @@ use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ add::AddChip, assign_free_advice, poseidon_hash::poseidon_hash_gadget, target_note_variable::get_owned_note_variable, @@ -16,6 +17,7 @@ use crate::{ note_encryption::{NoteCiphertext, NotePlaintext, SecretKey}, proof::Proof, utils::mod_r_p, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use group::Group; @@ -212,6 +214,13 @@ impl ValidityPredicateCircuit for ReceiverValidityPredicateCircuit { &mut message, )?; + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -225,6 +234,10 @@ impl ValidityPredicateCircuit for ReceiverValidityPredicateCircuit { fn get_public_inputs(&self, rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); let custom_public_input_padding = ValidityPredicatePublicInputs::get_custom_public_input_padding( public_inputs.len(), @@ -270,6 +283,7 @@ vp_circuit_impl!(ReceiverValidityPredicateCircuit); #[test] fn test_halo2_receiver_vp_circuit() { + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::{ note::tests::{random_input_note, random_output_note}, utils::poseidon_hash_n, @@ -314,8 +328,12 @@ fn test_halo2_receiver_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); let de_cipher = public_inputs.decrypt(rcv_sk).unwrap(); diff --git a/taiga_halo2/src/circuit/vp_examples/signature_verification.rs b/taiga_halo2/src/circuit/vp_examples/signature_verification.rs index 620628b9..1a0f3fc3 100644 --- a/taiga_halo2/src/circuit/vp_examples/signature_verification.rs +++ b/taiga_halo2/src/circuit/vp_examples/signature_verification.rs @@ -1,5 +1,6 @@ use crate::{ circuit::{ + blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ assign_free_advice, poseidon_hash::poseidon_hash_gadget, target_note_variable::get_owned_note_variable, @@ -13,6 +14,7 @@ use crate::{ note::{Note, RandomSeed}, proof::Proof, utils::{mod_r_p, poseidon_hash_n}, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_gadgets::ecc::{chip::EccChip, FixedPoint, NonIdentityPoint, ScalarFixed, ScalarVar}; @@ -247,6 +249,13 @@ impl ValidityPredicateCircuit for SignatureVerificationValidityPredicateCircuit s_g.constrain_equal(layouter.namespace(|| "s*G = R + Hash(r||P||m)*P"), &rhs)?; + // Publicize the dynamic vp commitments with default value + publicize_default_dynamic_vp_commitments( + &mut layouter, + config.advices[0], + config.instances, + )?; + Ok(()) } @@ -260,6 +269,10 @@ impl ValidityPredicateCircuit for SignatureVerificationValidityPredicateCircuit fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); + public_inputs.extend(default_vp_cm); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), &RandomSeed::random(&mut rng), @@ -280,6 +293,7 @@ fn test_halo2_sig_verification_vp_circuit() { use crate::circuit::vp_examples::{ receiver_vp::COMPRESSED_RECEIVER_VK, token::TokenAuthorization, }; + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::note::tests::{random_input_note, random_output_note}; use halo2_proofs::dev::MockProver; use rand::rngs::OsRng; @@ -308,7 +322,11 @@ fn test_halo2_sig_verification_vp_circuit() { }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } diff --git a/taiga_halo2/src/circuit/vp_examples/token.rs b/taiga_halo2/src/circuit/vp_examples/token.rs index b57f05c3..95be8221 100644 --- a/taiga_halo2/src/circuit/vp_examples/token.rs +++ b/taiga_halo2/src/circuit/vp_examples/token.rs @@ -1,8 +1,10 @@ use crate::{ circuit::{ + blake2s::{vp_commitment_gadget, Blake2sChip}, gadgets::{ - assign_free_advice, assign_free_constant, poseidon_hash::poseidon_hash_gadget, - target_note_variable::get_owned_note_variable, + assign_free_advice, assign_free_constant, + poseidon_hash::poseidon_hash_gadget, + target_note_variable::{get_is_input_note_flag, get_owned_note_variable}, }, vp_circuit::{ BasicValidityPredicateVariables, VPVerifyingInfo, ValidityPredicateCircuit, @@ -13,11 +15,16 @@ use crate::{ SignatureVerificationValidityPredicateCircuit, COMPRESSED_TOKEN_AUTH_VK, }, }, - constant::{NUM_NOTE, SETUP_PARAMS_MAP}, + constant::{ + NUM_NOTE, PRF_EXPAND_DYNAMIC_VP_1_CM_R, SETUP_PARAMS_MAP, VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_1, + VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_2, VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_1, + VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_2, + }, merkle_tree::MerklePath, note::{InputNoteProvingInfo, Note, OutputNoteProvingInfo, RandomSeed}, proof::Proof, utils::poseidon_hash_n, + vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use ff::Field; @@ -62,6 +69,8 @@ pub struct TokenValidityPredicateCircuit { // The auth goes to app_data_dynamic and defines how to consume and create the note. pub auth: TokenAuthorization, pub receiver_vp_vk: pallas::Base, + // rseed is to generate the randomness for vp commitment + pub rseed: RandomSeed, } #[derive(Clone, Debug, Copy)] @@ -88,6 +97,7 @@ impl Default for TokenValidityPredicateCircuit { token_name: "Token_name".to_string(), auth: TokenAuthorization::default(), receiver_vp_vk: pallas::Base::zero(), + rseed: RandomSeed::default(), } } } @@ -157,7 +167,12 @@ impl ValidityPredicateCircuit for TokenValidityPredicateCircuit { let encoded_app_data_dynamic = poseidon_hash_gadget( config.note_conifg.poseidon_config, layouter.namespace(|| "app_data_dynamic encoding"), - [pk.inner().x(), pk.inner().y(), auth_vp_vk, receiver_vp_vk], + [ + pk.inner().x(), + pk.inner().y(), + auth_vp_vk.clone(), + receiver_vp_vk.clone(), + ], )?; layouter.assign_region( @@ -184,8 +199,76 @@ impl ValidityPredicateCircuit for TokenValidityPredicateCircuit { |mut region| region.constrain_equal(is_merkle_checked.cell(), constant_one.cell()), )?; - // TODO: add the sender(authorization method included) vp commitment if it's an input note; - // Add the receiver(note encryption constraints included) vp commitment if it's an output note. + // VP Commitment + // Commt the sender(authorization method included) vp if it's an input note; + // Commit the receiver(note encryption constraints included) vp if it's an output note. + let first_dynamic_vp = { + let is_input_note = get_is_input_note_flag( + config.get_is_input_note_flag_config, + layouter.namespace(|| "get is_input_note_flag"), + &owned_note_pub_id, + &basic_variables.get_input_note_nfs(), + &basic_variables.get_output_note_cms(), + )?; + layouter.assign_region( + || "conditional select: ", + |mut region| { + config.conditional_select_config.assign_region( + &is_input_note, + &auth_vp_vk, + &receiver_vp_vk, + 0, + &mut region, + ) + }, + )? + }; + + // Construct a blake2s chip + let blake2s_chip = Blake2sChip::construct(config.blake2s_config); + let vp_cm_r = assign_free_advice( + layouter.namespace(|| "vp_cm_r"), + config.advices[0], + Value::known(self.rseed.get_vp_cm_r(PRF_EXPAND_DYNAMIC_VP_1_CM_R)), + )?; + let first_dynamic_vp_cm = + vp_commitment_gadget(&mut layouter, &blake2s_chip, first_dynamic_vp, vp_cm_r)?; + + layouter.constrain_instance( + first_dynamic_vp_cm[0].cell(), + config.instances, + VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_1, + )?; + layouter.constrain_instance( + first_dynamic_vp_cm[1].cell(), + config.instances, + VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_2, + )?; + + // Publicize the second dynamic vp commitment with default value + let vp_cm_fields: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + let vp_cm_1 = assign_free_advice( + layouter.namespace(|| "vp_cm 1"), + config.advices[0], + Value::known(vp_cm_fields[0]), + )?; + let vp_cm_2 = assign_free_advice( + layouter.namespace(|| "vp_cm 2"), + config.advices[0], + Value::known(vp_cm_fields[1]), + )?; + + layouter.constrain_instance( + vp_cm_1.cell(), + config.instances, + VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_1, + )?; + layouter.constrain_instance( + vp_cm_2.cell(), + config.instances, + VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_2, + )?; Ok(()) } @@ -200,6 +283,22 @@ impl ValidityPredicateCircuit for TokenValidityPredicateCircuit { fn get_public_inputs(&self, mut rng: impl RngCore) -> ValidityPredicatePublicInputs { let mut public_inputs = self.get_mandatory_public_inputs(); + let dynamic_vp = if self.owned_note_pub_id == self.output_notes[0].commitment().get_x() + || self.owned_note_pub_id == self.output_notes[1].commitment().get_x() + { + self.receiver_vp_vk + } else { + self.auth.vk + }; + + let vp_com_r = self.rseed.get_vp_cm_r(PRF_EXPAND_DYNAMIC_VP_1_CM_R); + let vp_com: [pallas::Base; 2] = + ValidityPredicateCommitment::commit(&dynamic_vp, &vp_com_r).to_public_inputs(); + + public_inputs.extend(vp_com); + let default_vp_cm: [pallas::Base; 2] = + ValidityPredicateCommitment::default().to_public_inputs(); + public_inputs.extend(default_vp_cm); let padding = ValidityPredicatePublicInputs::get_public_input_padding( public_inputs.len(), &RandomSeed::random(&mut rng), @@ -264,6 +363,7 @@ pub fn generate_input_token_note_proving_info( token_name, auth, receiver_vp_vk: *COMPRESSED_RECEIVER_VK, + rseed: RandomSeed::random(&mut rng), }; // token auth VP @@ -303,6 +403,7 @@ pub fn generate_output_token_note_proving_info( token_name, auth, receiver_vp_vk: *COMPRESSED_RECEIVER_VK, + rseed: RandomSeed::random(&mut rng), }; // receiver VP @@ -322,6 +423,7 @@ pub fn generate_output_token_note_proving_info( #[test] fn test_halo2_token_vp_circuit() { + use crate::constant::VP_CIRCUIT_PARAMS_SIZE; use crate::note::tests::{random_input_note, random_output_note}; use halo2_proofs::dev::MockProver; use rand::rngs::OsRng; @@ -345,12 +447,17 @@ fn test_halo2_token_vp_circuit() { token_name, auth, receiver_vp_vk: *COMPRESSED_RECEIVER_VK, + rseed: RandomSeed::random(&mut rng), } }; let public_inputs = circuit.get_public_inputs(&mut rng); - let prover = - MockProver::::run(12, &circuit, vec![public_inputs.to_vec()]).unwrap(); + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); assert_eq!(prover.verify(), Ok(())); } diff --git a/taiga_halo2/src/constant.rs b/taiga_halo2/src/constant.rs index 4f3d24c5..1b8280d9 100644 --- a/taiga_halo2/src/constant.rs +++ b/taiga_halo2/src/constant.rs @@ -24,10 +24,17 @@ pub const NOTE_COMMITMENT_PERSONALIZATION: &str = "Taiga-NoteCommit"; pub const TRANSACTION_BINDING_HASH_PERSONALIZATION: &[u8; 16] = b"TxBindingSigHash"; +pub const VP_COMMITMENT_PERSONALIZATION: &[u8; 8] = b"VPCommit"; + pub const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"Taiga_ExpandSeed"; pub const PRF_EXPAND_PSI: u8 = 0; pub const PRF_EXPAND_RCM: u8 = 1; pub const PRF_EXPAND_PUBLIC_INPUT_PADDING: u8 = 2; +pub const PRF_EXPAND_VCM_R: u8 = 3; +pub const PRF_EXPAND_INPUT_VP_CM_R: u8 = 4; +pub const PRF_EXPAND_OUTPUT_VP_CM_R: u8 = 5; +pub const PRF_EXPAND_DYNAMIC_VP_1_CM_R: u8 = 6; +pub const PRF_EXPAND_DYNAMIC_VP_2_CM_R: u8 = 7; /// Commitment merkle tree depth pub const TAIGA_COMMITMENT_TREE_DEPTH: usize = 32; @@ -42,6 +49,10 @@ pub const ACTION_ANCHOR_PUBLIC_INPUT_ROW_IDX: usize = 1; pub const ACTION_OUTPUT_CM_PUBLIC_INPUT_ROW_IDX: usize = 2; pub const ACTION_NET_VALUE_CM_X_PUBLIC_INPUT_ROW_IDX: usize = 3; pub const ACTION_NET_VALUE_CM_Y_PUBLIC_INPUT_ROW_IDX: usize = 4; +pub const ACTION_INPUT_VP_CM_1_ROW_IDX: usize = 5; +pub const ACTION_INPUT_VP_CM_2_ROW_IDX: usize = 6; +pub const ACTION_OUTPUT_VP_CM_1_ROW_IDX: usize = 7; +pub const ACTION_OUTPUT_VP_CM_2_ROW_IDX: usize = 8; pub const POSEIDON_TO_CURVE_INPUT_LEN: usize = 3; pub const CURVE_ID: &str = "pallas"; @@ -50,8 +61,8 @@ pub const VALUE_BASE_DOMAIN_POSTFIX: &str = "Taiga-NoteType"; pub const VP_CIRCUIT_PUBLIC_INPUT_NUM: usize = VP_CIRCUIT_MANDATORY_PUBLIC_INPUT_NUM + VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_NUM + VP_CIRCUIT_NOTE_ENCRYPTION_PUBLIC_INPUT_NUM; -pub const VP_CIRCUIT_MANDATORY_PUBLIC_INPUT_NUM: usize = 5; -pub const VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_NUM: usize = 6; +pub const VP_CIRCUIT_MANDATORY_PUBLIC_INPUT_NUM: usize = 9; +pub const VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_NUM: usize = 2; pub const VP_CIRCUIT_NOTE_ENCRYPTION_PUBLIC_INPUT_NUM: usize = NOTE_ENCRYPTION_CIPHERTEXT_NUM + 2; // ciphertext(12) + public_key(2) pub const VP_CIRCUIT_NULLIFIER_ONE_PUBLIC_INPUT_IDX: usize = 0; @@ -59,6 +70,10 @@ pub const VP_CIRCUIT_OUTPUT_CM_ONE_PUBLIC_INPUT_IDX: usize = 1; pub const VP_CIRCUIT_NULLIFIER_TWO_PUBLIC_INPUT_IDX: usize = 2; pub const VP_CIRCUIT_OUTPUT_CM_TWO_PUBLIC_INPUT_IDX: usize = 3; pub const VP_CIRCUIT_OWNED_NOTE_PUB_ID_PUBLIC_INPUT_IDX: usize = 4; +pub const VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_1: usize = 5; +pub const VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_2: usize = 6; +pub const VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_1: usize = 7; +pub const VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_2: usize = 8; pub const VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_BEGIN_IDX: usize = VP_CIRCUIT_MANDATORY_PUBLIC_INPUT_NUM; pub const VP_CIRCUIT_NOTE_ENCRYPTION_PUBLIC_INPUT_BEGIN_IDX: usize = VP_CIRCUIT_MANDATORY_PUBLIC_INPUT_NUM + VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_NUM; @@ -96,16 +111,16 @@ lazy_static! { }; } -pub const CIRCUIT_PARAMS_SIZE_12: u32 = 12; -pub const ACTION_CIRCUIT_PARAMS_SIZE: u32 = 12; -pub const VP_CIRCUIT_PARAMS_SIZE: u32 = 12; +pub const PARAMS_SIZE: u32 = 15; +pub const ACTION_CIRCUIT_PARAMS_SIZE: u32 = PARAMS_SIZE; +pub const VP_CIRCUIT_PARAMS_SIZE: u32 = PARAMS_SIZE; // Setup params map lazy_static! { pub static ref SETUP_PARAMS_MAP: HashMap> = { let mut m = HashMap::new(); #[allow(clippy::single_element_loop)] - for circuit_size in [CIRCUIT_PARAMS_SIZE_12] { + for circuit_size in [PARAMS_SIZE] { let params = Params::new(circuit_size); m.insert(circuit_size, params); } @@ -6097,3 +6112,5 @@ fn r_u_z_generate() { } println!("]"); } + +pub const MAX_DYNAMIC_VP_NUM: usize = 2; diff --git a/taiga_halo2/src/lib.rs b/taiga_halo2/src/lib.rs index 223b2158..2164730c 100644 --- a/taiga_halo2/src/lib.rs +++ b/taiga_halo2/src/lib.rs @@ -17,4 +17,5 @@ pub mod transaction; pub mod transparent_ptx; pub mod utils; pub mod value_commitment; +pub mod vp_commitment; pub mod vp_vk; diff --git a/taiga_halo2/src/note.rs b/taiga_halo2/src/note.rs index 0f39c53e..489a7990 100644 --- a/taiga_halo2/src/note.rs +++ b/taiga_halo2/src/note.rs @@ -6,7 +6,7 @@ use crate::{ constant::{ BASE_BITS_NUM, NOTE_COMMIT_DOMAIN, NUM_NOTE, POSEIDON_TO_CURVE_INPUT_LEN, PRF_EXPAND_PERSONALIZATION, PRF_EXPAND_PSI, PRF_EXPAND_PUBLIC_INPUT_PADDING, - PRF_EXPAND_RCM, + PRF_EXPAND_RCM, PRF_EXPAND_VCM_R, }, merkle_tree::MerklePath, nullifier::{Nullifier, NullifierKeyContainer}, @@ -497,6 +497,28 @@ impl RandomSeed { }) .collect() } + + pub fn get_rcv(&self) -> pallas::Scalar { + let mut h = Blake2bParams::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + h.update(&[PRF_EXPAND_VCM_R]); + h.update(&self.0); + let bytes = *h.finalize().as_array(); + pallas::Scalar::from_uniform_bytes(&bytes) + } + + pub fn get_vp_cm_r(&self, tag: u8) -> pallas::Base { + let mut h = Blake2bParams::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + h.update(&[tag]); + h.update(&self.0); + let bytes = *h.finalize().as_array(); + pallas::Base::from_uniform_bytes(&bytes) + } } impl InputNoteProvingInfo { diff --git a/taiga_halo2/src/shielded_ptx.rs b/taiga_halo2/src/shielded_ptx.rs index d78bd1a8..4b8e5b17 100644 --- a/taiga_halo2/src/shielded_ptx.rs +++ b/taiga_halo2/src/shielded_ptx.rs @@ -1,8 +1,8 @@ use crate::action::{ActionInfo, ActionInstance}; use crate::circuit::vp_circuit::{VPVerifyingInfo, ValidityPredicate}; use crate::constant::{ - ACTION_CIRCUIT_PARAMS_SIZE, ACTION_PROVING_KEY, ACTION_VERIFYING_KEY, NUM_NOTE, - SETUP_PARAMS_MAP, + ACTION_CIRCUIT_PARAMS_SIZE, ACTION_PROVING_KEY, ACTION_VERIFYING_KEY, MAX_DYNAMIC_VP_NUM, + NUM_NOTE, SETUP_PARAMS_MAP, }; use crate::error::TransactionError; use crate::executable::Executable; @@ -339,6 +339,8 @@ impl NoteVPVerifyingInfoSet { app_vp_verifying_info: VPVerifyingInfo, app_dynamic_vp_verifying_info: Vec, ) -> Self { + assert!(app_dynamic_vp_verifying_info.len() <= MAX_DYNAMIC_VP_NUM); + Self { app_vp_verifying_info, app_dynamic_vp_verifying_info, @@ -349,6 +351,8 @@ impl NoteVPVerifyingInfoSet { application_vp: Box, dynamic_vps: Vec>, ) -> Self { + assert!(dynamic_vps.len() <= MAX_DYNAMIC_VP_NUM); + let app_vp_verifying_info = application_vp.get_verifying_info(); let app_dynamic_vp_verifying_info = dynamic_vps diff --git a/taiga_halo2/src/vp_commitment.rs b/taiga_halo2/src/vp_commitment.rs new file mode 100644 index 00000000..d96e66eb --- /dev/null +++ b/taiga_halo2/src/vp_commitment.rs @@ -0,0 +1,44 @@ +use crate::constant::VP_COMMITMENT_PERSONALIZATION; +use blake2s_simd::Params; +use byteorder::{ByteOrder, LittleEndian}; +use ff::PrimeField; +#[cfg(feature = "serde")] +use serde; + +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ValidityPredicateCommitment([u8; 32]); + +impl ValidityPredicateCommitment { + pub fn commit(vp: &F, rcm: &F) -> Self { + let hash = Params::new() + .hash_length(32) + .personal(VP_COMMITMENT_PERSONALIZATION) + .to_state() + .update(vp.to_repr().as_ref()) + .update(rcm.to_repr().as_ref()) + .finalize(); + Self(hash.as_bytes().try_into().unwrap()) + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) + } + + pub fn from_public_inputs(public_inputs: &[F; 2]) -> Self { + let mut bytes: [u8; 32] = [0; 32]; + bytes[0..16].copy_from_slice(&public_inputs[0].to_repr().as_ref()[0..16]); + bytes[16..].copy_from_slice(&public_inputs[1].to_repr().as_ref()[0..16]); + Self(bytes) + } + + pub fn to_public_inputs(&self) -> [F; 2] { + let low = F::from_u128(LittleEndian::read_u128(&self.0[0..16])); + let high = F::from_u128(LittleEndian::read_u128(&self.0[16..])); + [low, high] + } +}