diff --git a/ckb-contracts/capsule.toml b/ckb-contracts/capsule.toml index fb466f8..7b76223 100644 --- a/ckb-contracts/capsule.toml +++ b/ckb-contracts/capsule.toml @@ -2,7 +2,7 @@ version = "0.4.5 cdad051" deployment = "deployment.toml" [rust] -docker_image = "secbit/ckb-zkp-capsule:2021-02-17" +docker_image = "secbit/ckb-zkp-capsule:2021-05-30" [[contracts]] name = "universal_plonk_verifier" diff --git a/ckb-contracts/contracts/universal_plonk_verifier/Cargo.toml b/ckb-contracts/contracts/universal_plonk_verifier/Cargo.toml index 7e147f0..d7c4de9 100644 --- a/ckb-contracts/contracts/universal_plonk_verifier/Cargo.toml +++ b/ckb-contracts/contracts/universal_plonk_verifier/Cargo.toml @@ -12,6 +12,6 @@ ark-poly-commit = { version = "0.2", default-features = false } blake2 = { version = "0.9", default-features = false } [dependencies.zkp-plonk] -git = "https://github.com/sunhuachuang/ckb-zkp" -branch = "dev" +git = "https://github.com/sec-bit/ckb-zkp" +branch = "master" default-features = false diff --git a/ckb-contracts/docker/Dockerfile b/ckb-contracts/docker/Dockerfile index 9a9477c..95c564b 100644 --- a/ckb-contracts/docker/Dockerfile +++ b/ckb-contracts/docker/Dockerfile @@ -1,14 +1,15 @@ -FROM nervos/ckb-riscv-gnu-toolchain@sha256:7b168b4b109a0f741078a71b7c4dddaf1d283a5244608f7851f5714fbad273ba +FROM nervos/ckb-riscv-gnu-toolchain:bionic-20191209 # Install Rust -RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-02-17 -y +RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-05-30 -y ENV PATH=/root/.cargo/bin:$PATH # Install RISC-V target RUN rustup target add riscv64imac-unknown-none-elf # Install CKB binary patcher RUN cargo install --git https://github.com/xxuejie/ckb-binary-patcher.git --rev 930f0b468a8f426ebb759d9da735ebaa1e2f98ba # Install CKB debugger -RUN git clone https://github.com/xxuejie/ckb-standalone-debugger.git \ - && cd ckb-standalone-debugger && git checkout 7c62220552fb90de0e6cd30cb6f95bf1bdcce18f && cd - \ +RUN git clone https://github.com/sunhuachuang/ckb-standalone-debugger.git \ + && cd ckb-standalone-debugger \ + && cd - \ && cargo install --path ckb-standalone-debugger/bins \ && rm -r ckb-standalone-debugger diff --git a/plonk/src/composer/abstract_hash.rs b/plonk/src/composer/abstract_hash.rs new file mode 100644 index 0000000..d43161f --- /dev/null +++ b/plonk/src/composer/abstract_hash.rs @@ -0,0 +1,17 @@ +use ark_ff::PrimeField; + +use crate::composer::{Composer, Variable}; + +use crate::Vec; + +pub trait AbstractHashOutput: Clone { + fn get_variables(&self) -> Vec; + + fn get_variable_values(&self) -> Vec; +} + +pub trait AbstractHash { + type Output: AbstractHashOutput; + + fn hash_enforce(composer: &mut Composer, params: &[&Self::Output]) -> Self::Output; +} diff --git a/plonk/src/composer/boolean.rs b/plonk/src/composer/boolean.rs new file mode 100644 index 0000000..95478dd --- /dev/null +++ b/plonk/src/composer/boolean.rs @@ -0,0 +1,23 @@ +use crate::composer::{Composer, Field, Variable}; + +impl Composer { + pub fn boolean_gate(&mut self, a: Variable, pi: F) { + self.permutation.insert_gate(a, a, a, Variable(0), self.n); + + self.w_0.push(a); + self.w_1.push(a); + self.w_2.push(a); + self.w_3.push(Variable(0)); + self.pi.push(pi); + + self.q_0.push(F::zero()); + self.q_1.push(F::zero()); + self.q_2.push(-F::one()); + self.q_3.push(F::zero()); + self.q_m.push(F::one()); + self.q_c.push(F::zero()); + self.q_arith.push(F::one()); + + self.n += 1; + } +} diff --git a/plonk/src/composer/logic.rs b/plonk/src/composer/logic.rs new file mode 100644 index 0000000..f6a3d69 --- /dev/null +++ b/plonk/src/composer/logic.rs @@ -0,0 +1,283 @@ +use ark_ff::BitIteratorBE; +use ark_ff::PrimeField; + +use super::permutation::Wire; +use crate::composer::{Composer, Variable}; + +impl Composer { + // Performs a logical AND or XOR op between the inputs provided for the + // specified + /// number of bits. + /// + /// Each logic gate adds `(num_bits / 2) + 1` gates to the circuit to + /// perform the whole operation. + /// + /// ## Selector + /// - is_xor_gate = 1 -> Performs XOR between the first `num_bits` for `a` + /// and `b`. + /// - is_xor_gate = 0 -> Performs AND between the first `num_bits` for `a` + /// and `b`. + /// + /// ## Panics + /// This function will panic if the num_bits specified is not even `num_bits + /// % 2 != 0`. + fn logic_gate( + &mut self, + a: Variable, + b: Variable, + num_bits: usize, + is_xor_gate: bool, + ) -> Variable { + // Since we work on base4, we need to guarantee that we have an even + // number of bits representing the greatest input. + assert_eq!(num_bits & 1, 0); + // We will have exactly `num_bits / 2` quads (quaternary digits) + // representing both numbers. + let num_quads = num_bits >> 1; + // Allocate accumulators for gate construction. + let mut left_accumulator = F::zero(); + let mut right_accumulator = F::zero(); + let mut out_accumulator = F::zero(); + let mut left_quad: u8; + let mut right_quad: u8; + // Get vars as bits and reverse them to get the Little Endian repr. + let a_bit_iter = BitIteratorBE::new(self.assignment[&a].into_repr()); + let a_bits: Vec<_> = a_bit_iter.skip(256 - num_bits).collect(); + let b_bit_iter = BitIteratorBE::new(self.assignment[&b].into_repr()); + let b_bits: Vec<_> = b_bit_iter.skip(256 - num_bits).collect(); + // XXX Doc this + assert!(a_bits.len() >= num_bits); + assert!(b_bits.len() >= num_bits); + + // If we take a look to the program memory structure of the ref. impl. + // * +-----+-----+-----+-----+ + // * | A | B | C | D | + // * +-----+-----+-----+-----+ + // * | 0 | 0 | w1 | 0 | + // * | a1 | b1 | w2 | c1 | + // * | a2 | b2 | w3 | c2 | + // * | : | : | : | : | + // * | an | bn | --- | cn | + // * +-----+-----+-----+-----+ + // We need to have w_4, w_l and w_r pointing to one gate ahead of w_o. + // We increase the gate idx and assign w_4, w_l and w_r to `zero`. + // Now we can add the first row as: `| 0 | 0 | -- | 0 |`. + // Note that `w_1` will be set on the first loop iteration. + self.permutation.add_to_map(Variable(0), Wire::W0(self.n)); + self.permutation.add_to_map(Variable(0), Wire::W1(self.n)); + self.permutation.add_to_map(Variable(0), Wire::W3(self.n)); + self.w_0.push(Variable(0)); + self.w_1.push(Variable(0)); + self.w_3.push(Variable(0)); + // Increase the gate index so we can add the following rows in the + // correct order. + self.n += 1; + + // Start generating accumulator rows and adding them to the circuit. + // Note that we will do this process exactly `num_bits / 2` counting + // that the first step above was done correctly to obtain the + // right format the the first row. This means that we will need + // to pad the end of the memory program once we've built it. + // As we can see in the last row structure: `| an | bn | --- | cn |`. + for i in 0..num_quads { + // On each round, we will commit every accumulator step. To do so, + // we first need to get the ith quads of `a` and `b` and then + // compute `out_quad`(logical OP result) and + // `prod_quad`(intermediate prod result). + + // Here we compute each quad by taking the most significant bit + // multiplying it by two and adding to it the less significant + // bit to form the quad with a ternary value encapsulated in an `u8` + // in Big Endian form. + left_quad = { + let idx = i << 1; + ((a_bits[idx] as u8) << 1) + (a_bits[idx + 1] as u8) + }; + right_quad = { + let idx = i << 1; + ((b_bits[idx] as u8) << 1) + (b_bits[idx + 1] as u8) + }; + let left_quad_fr = F::from(left_quad as u64); + let right_quad_fr = F::from(right_quad as u64); + // The `out_quad` is the result of the bitwise ops `&` or `^` + // between the left and right quads. The op is decided + // with a boolean flag set as input of the function. + let out_quad_fr = match is_xor_gate { + true => F::from((left_quad ^ right_quad) as u64), + false => F::from((left_quad & right_quad) as u64), + }; + // We also need to allocate a helper item which is the result + // of the product between the left and right quads. + // This param is identified as `w` in the program memory and + // is needed to prevent the degree of our quotient polynomial from + // blowing up + let prod_quad_fr = F::from((left_quad * right_quad) as u64); + + // Now that we've computed this round results, we need to apply the + // logic transition constraint that will check the following: + // a - 4 . a ϵ [0, 1, 2, 3] + // i + 1 i + // + // + // + // + // b - 4 . b ϵ [0, 1, 2, 3] + // i + 1 i + // + // + // + // + // / \ / + // \ c - 4 . c = | a - 4 . a | (& OR ^) | b + // - 4 . b | i + 1 i \ i + 1 i / + // \ i + 1 i / + // + let prev_left_accum = left_accumulator; + let prev_right_accum = right_accumulator; + let prev_out_accum = out_accumulator; + // We also need to add the computed quad fr_s to the circuit + // representing a logic gate. To do so, we just mul by 4 + // the previous accomulated result and we add to it + // the new computed quad. + // With this technique we're basically accumulating the quads and + // adding them to get back to the starting value, at the + // i-th iteration. i + // === + // \ j + // x = / q . 4 + // i === (bits/2 - j) + // j = 0 + // + left_accumulator *= F::from(4u64); + left_accumulator += left_quad_fr; + right_accumulator *= F::from(4u64); + right_accumulator += right_quad_fr; + out_accumulator *= F::from(4u64); + out_accumulator += out_quad_fr; + // Apply logic transition constraints. + assert!(left_accumulator - (prev_left_accum * F::from(4u64)) < F::from(4u64)); + assert!(right_accumulator - (prev_right_accum * F::from(4u64)) < F::from(4u64)); + assert!(out_accumulator - (prev_out_accum * F::from(4u64)) < F::from(4u64)); + + // Get variables pointing to the previous accumulated values. + let var_a = self.alloc_and_assign(left_accumulator); + let var_b = self.alloc_and_assign(right_accumulator); + let var_c = self.alloc_and_assign(prod_quad_fr); + let var_4 = self.alloc_and_assign(out_accumulator); + // Add the variables to the variable map linking them to it's + // corresponding gate index. + // + // Note that by doing this, we are basically setting the wire_coeffs + // of the wire polynomials, but we still need to link the + // selector_poly coefficients in order to be able to + // have complete gates. + // + // Also note that here we're setting left, right and fourth + // variables to the actual gate, meanwhile we set out to + // the previous gate. + self.permutation.add_to_map(var_a, Wire::W0(self.n)); + self.permutation.add_to_map(var_b, Wire::W1(self.n)); + self.permutation.add_to_map(var_4, Wire::W3(self.n)); + self.permutation.add_to_map(var_c, Wire::W2(self.n - 1)); + // Push the variables to it's actual wire vector storage + self.w_0.push(var_a); + self.w_1.push(var_b); + self.w_2.push(var_c); + self.w_3.push(var_4); + // Update the gate index + self.n += 1; + } + + // We have one missing value for the last row of the program memory + // which is `w_o` since the rest of wires are pointing one gate + // ahead. To fix this, we simply pad with a 0 so the last row of + // the program memory will look like this: + // | an | bn | --- | cn | + self.permutation + .add_to_map(Variable(0), Wire::W2(self.n - 1)); + self.w_2.push(Variable(0)); + + // Now the wire values are set for each gate, indexed and mapped in the + // `variable_map` inside of the `Permutation` struct. + // Now we just need to extend the selector polynomials with the + // appropriate coefficients to form complete logic gates. + for _ in 0..num_quads { + self.q_m.push(F::zero()); + self.q_0.push(F::zero()); + self.q_1.push(F::zero()); + self.q_arith.push(F::zero()); + self.q_2.push(F::zero()); + self.q_3.push(F::zero()); + + match is_xor_gate { + true => { + self.q_c.push(-F::one()); + } + false => { + self.q_c.push(F::one()); + } + }; + } + // For the last gate, `q_c` and `q_logic` we use no-op values (Zero). + self.q_m.push(F::zero()); + self.q_0.push(F::zero()); + self.q_1.push(F::zero()); + self.q_arith.push(F::zero()); + self.q_2.push(F::zero()); + self.q_3.push(F::zero()); + + self.q_c.push(F::zero()); + //self.q_logic.push(F::zero()); + + // Now we need to assert that the sum of accumulated values + // matches the original values provided to the fn. + // Note that we're only considering the quads that are included + // in the range 0..num_bits. So, when actually executed, we're checking + // that x & ((1 << num_bits +1) -1) == [0..num_quads] + // accumulated sums of x. + // + // We could also check that the last gates wire coefficients match the + // original values introduced in the function taking into account the + // bitnum specified on the fn call parameters. + // This can be done with an `assert_equal` constraint gate or simply + // by taking the values behind the n'th variables of `w_l` & `w_r` and + // checking that they're equal to the original ones behind the variables + // sent through the function parameters. + + // assert_eq!( + // self.assignment[&a] & (F::from(2u64).pow(&[(num_bits) as u64, 0, 0, 0]) - F::one()), + // self.assignment[&self.w_0[self.n - 1]] + // ); + // assert_eq!( + // self.assignment[&b] & (F::from(2u64).pow(&[(num_bits) as u64, 0, 0, 0]) - F::one()), + // self.assignment[&self.w_1[self.n - 1]] + // ); + + // Once the inputs are checked against the accumulated additions, + // we can safely return the resulting variable of the gate computation + // which is stored on the last program memory row and in the column that + // `w_3` is holding. + self.w_3[self.w_3.len() - 1] + } + + /// Adds a logical XOR gate that performs the XOR between two values for the + /// specified first `num_bits` returning a `Variable` holding the result. + /// + /// # Panics + /// + /// If the `num_bits` specified in the fn params is odd. + pub fn xor_gate(&mut self, a: Variable, b: Variable, num_bits: usize) -> Variable { + self.logic_gate(a, b, num_bits, true) + } + + /// Adds a logical AND gate that performs the bitwise AND between two values + /// for the specified first `num_bits` returning a `Variable` holding the + /// result. + /// + /// # Panics + /// + /// If the `num_bits` specified in the fn params is odd. + pub fn and_gate(&mut self, a: Variable, b: Variable, num_bits: usize) -> Variable { + self.logic_gate(a, b, num_bits, false) + } +} diff --git a/plonk/src/composer/merkletree/cbmt.rs b/plonk/src/composer/merkletree/cbmt.rs new file mode 100644 index 0000000..96b2c45 --- /dev/null +++ b/plonk/src/composer/merkletree/cbmt.rs @@ -0,0 +1,339 @@ +//! Complete Binary Merkle Tree, this implementation inspired by [Nervos CBMT]. +//! +//! [Nervos CBMT]: https://github.com/nervosnetwork/merkle-tree + +use core::marker::PhantomData; + +#[cfg(not(feature = "std"))] +use alloc::collections::VecDeque; + +#[cfg(feature = "std")] +use std::collections::VecDeque; + +use crate::Vec; + +pub trait Merge { + type Item; + fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item; +} + +pub struct MerkleTree { + nodes: Vec, + merge: PhantomData, +} + +impl MerkleTree +where + T: Ord + Default + Clone, + M: Merge, +{ + /// `leaf_index`: The index of leaves + pub fn build_proof(&self, leaf_index: &u32) -> Option> { + if self.nodes.is_empty() { + return None; + } + + let leaves_count = ((self.nodes.len() >> 1) + 1) as u32; + let index = leaves_count + leaf_index - 1; + + if index >= (leaves_count << 1) - 1 { + return None; + } + + let mut lemmas = Vec::new(); + + if index == 0 { + return Some(MerkleProof { + index, + lemmas, + merge: PhantomData, + }); + } + + let mut new_index = index; + + loop { + let sibling = new_index.sibling(); + lemmas.push(self.nodes[sibling as usize].clone()); + + let parent = new_index.parent(); + if parent != 0 { + new_index = parent; + } else { + break; + } + } + + Some(MerkleProof { + index, + lemmas, + merge: PhantomData, + }) + } + + pub fn root(&self) -> T { + if self.nodes.is_empty() { + T::default() + } else { + self.nodes[0].clone() + } + } + + pub fn nodes(&self) -> &Vec { + &self.nodes + } +} + +pub struct MerkleProof { + index: u32, + lemmas: Vec, + merge: PhantomData, +} + +impl MerkleProof +where + T: Ord + Default + Clone, + M: Merge, +{ + pub fn new(index: u32, lemmas: Vec) -> Self { + Self { + index, + lemmas, + merge: PhantomData, + } + } + + pub fn root(&self, leaf: &T) -> Option { + if self.index == 0 && self.lemmas.len() != 0 { + return None; + } + + let mut parent = leaf.clone(); + let mut index = self.index; + let mut lemmas_iter = self.lemmas.iter(); + + loop { + if let Some(sibling) = lemmas_iter.next() { + parent = if index.is_left() { + M::merge(&parent, &sibling) + } else { + M::merge(&sibling, &parent) + }; + index = index.parent(); + } else { + break; + } + } + + Some(parent) + } + + pub fn verify(&self, root: &T, leaf: &T) -> bool { + match self.root(leaf) { + Some(r) => &r == root, + _ => false, + } + } + + pub fn index(&self) -> &u32 { + &self.index + } + + pub fn lemmas(&self) -> &[T] { + &self.lemmas + } +} + +#[derive(Default)] +pub struct CBMT { + data_type: PhantomData, + merge: PhantomData, +} + +impl CBMT +where + T: Ord + Default + Clone, + M: Merge, +{ + pub fn build_merkle_root(leaves: &[T]) -> T { + if leaves.is_empty() { + return T::default(); + } + + let mut queue = VecDeque::with_capacity((leaves.len() + 1) >> 1); + + let mut iter = leaves.rchunks_exact(2); + while let Some([leaf1, leaf2]) = iter.next() { + queue.push_back(M::merge(leaf1, leaf2)) + } + if let [leaf] = iter.remainder() { + queue.push_front(leaf.clone()) + } + + while queue.len() > 1 { + let right = queue.pop_front().unwrap(); + let left = queue.pop_front().unwrap(); + queue.push_back(M::merge(&left, &right)); + } + + queue.pop_front().unwrap() + } + + pub fn build_merkle_tree(leaves: Vec) -> MerkleTree { + let len = leaves.len(); + if len > 0 { + let mut nodes = vec![T::default(); len - 1]; + nodes.extend(leaves); + + (0..len - 1) + .rev() + .for_each(|i| nodes[i] = M::merge(&nodes[(i << 1) + 1], &nodes[(i << 1) + 2])); + + MerkleTree { + nodes, + merge: PhantomData, + } + } else { + MerkleTree { + nodes: vec![], + merge: PhantomData, + } + } + } + + pub fn build_merkle_proof(leaves: &[T], index: &u32) -> Option> { + Self::build_merkle_tree(leaves.to_vec()).build_proof(index) + } +} + +pub trait TreeIndex: Clone { + fn sibling(&self) -> Self; + fn parent(&self) -> Self; + fn is_left(&self) -> bool; + fn is_root(&self) -> bool; +} + +macro_rules! impl_tree_index { + ($t: ty) => { + impl TreeIndex for $t { + fn sibling(&self) -> $t { + if *self == 0 { + 0 + } else { + ((self + 1) ^ 1) - 1 + } + } + + fn parent(&self) -> $t { + if *self == 0 { + 0 + } else { + (self - 1) >> 1 + } + } + + fn is_left(&self) -> bool { + self & 1 == 1 + } + + fn is_root(&self) -> bool { + *self == 0 + } + } + }; +} + +impl_tree_index!(u32); +impl_tree_index!(usize); + +#[cfg(test)] +mod tests { + use super::*; + + struct MergeI32 {} + + impl Merge for MergeI32 { + type Item = i32; + fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item { + right.wrapping_sub(*left) + } + } + + type CBMTI32 = CBMT; + type CBMTI32Proof = MerkleProof; + + #[test] + fn build_cbmt_empty() { + let leaves = vec![]; + let tree = CBMTI32::build_merkle_tree(leaves); + assert!(tree.nodes().is_empty()); + assert_eq!(tree.root(), i32::default()); + } + + #[test] + fn build_cbmt_one() { + let leaves = vec![1i32]; + let tree = CBMTI32::build_merkle_tree(leaves); + assert_eq!(&vec![1], tree.nodes()); + } + + #[test] + fn build_cbmt_two() { + let leaves = vec![1i32, 2]; + let tree = CBMTI32::build_merkle_tree(leaves); + assert_eq!(&vec![1, 1, 2], tree.nodes()); + } + + #[test] + fn build_cbmt_five() { + let leaves = vec![2i32, 3, 5, 7, 11]; + let tree = CBMTI32::build_merkle_tree(leaves); + assert_eq!(&vec![4, -2, 2, 4, 2, 3, 5, 7, 11], tree.nodes()); + } + + #[test] + fn build_cbmt_root_directly() { + let leaves = vec![2i32, 3, 5, 7, 11]; + assert_eq!(4, CBMTI32::build_merkle_root(&leaves)); + } + + #[test] + fn rebuild_cbmt_proof() { + let leaves = vec![2i32, 3, 5, 7, 11]; + let tree = CBMTI32::build_merkle_tree(leaves); + let root = tree.root(); + + // build proof + let proof = tree.build_proof(&3).unwrap(); + let lemmas = proof.lemmas(); + let index = proof.index(); + + // rebuild proof + let needed_leaf = tree.nodes()[*index as usize].clone(); + + let rebuild_proof = CBMTI32Proof::new(*index, lemmas.to_vec()); + assert_eq!(rebuild_proof.verify(&root, &needed_leaf), true); + assert_eq!(root, rebuild_proof.root(&needed_leaf).unwrap()); + } + + #[test] + fn build_cbmt_proof() { + let leaves = vec![2i32, 3, 5, 7, 11, 13]; + let leaf_index = 5u32; + let proof_leaf = leaves[leaf_index as usize].clone(); + + let proof = CBMTI32::build_merkle_proof(&leaves, &leaf_index).unwrap(); + + assert_eq!(vec![11, 2, 1], proof.lemmas); + assert_eq!(Some(1), proof.root(&proof_leaf)); + + // merkle proof for single leaf + let leaves = vec![2i32]; + let leaf_index = 0u32; + let proof_leaf = leaves[leaf_index as usize].clone(); + + let proof = CBMTI32::build_merkle_proof(&leaves, &leaf_index).unwrap(); + assert!(proof.lemmas.is_empty()); + assert_eq!(Some(2), proof.root(&proof_leaf)); + } +} diff --git a/plonk/src/composer/merkletree/cbmt_constraints.rs b/plonk/src/composer/merkletree/cbmt_constraints.rs new file mode 100644 index 0000000..beab332 --- /dev/null +++ b/plonk/src/composer/merkletree/cbmt_constraints.rs @@ -0,0 +1,200 @@ +//! Complete Binary Merkle Tree Proof gadgets. + +use ark_ff::PrimeField; + +use crate::composer::{ + abstract_hash::{AbstractHash, AbstractHashOutput}, + Composer, Variable, +}; +use crate::Vec; + +use super::cbmt::TreeIndex; + +impl Composer { + pub fn merkletree_mermbership>( + &mut self, + root: H::Output, + leaf: H::Output, + index: I, + lemmas: Vec, + pi: F, + ) { + let mut parent = leaf.clone(); + let mut index = index.clone(); + let mut lemmas_iter = lemmas.iter(); + + loop { + if let Some(sibling) = lemmas_iter.next() { + let _parent_variable_vec = parent.get_variables(); + let parent_value_vec = parent.get_variable_values(); + let _sibling_variable_vec = sibling.get_variables(); + let sibling_value_vec = sibling.get_variable_values(); + let is_left_value = if index.is_left() { F::one() } else { F::zero() }; + + let _is_left_variable = self.alloc_and_assign(is_left_value); + + let input_value = if index.is_left() { + parent_value_vec.clone() + } else { + sibling_value_vec.clone() + }; + + let mut input_variable_vec: Vec = + Vec::with_capacity(input_value.len() as usize); + for j in 0..input_value.len() as usize { + let input_variable = self.alloc_and_assign(input_value[j]); + input_variable_vec.push(input_variable); + } + + // for j in 0..parent_variable_vec.len() as usize { + // // parent_variable_vec.len = 256; sibling_variable_vec.len = 8 + // if j >= sibling_variable_vec.len() { + // break; + // } + // // "is_left*(left[{}][{}]-right[{}][{}])=(input[{}]-right[{}][{}])" + // let left = parent_variable_vec[j] - sibling_variable_vec[j]; + // let right = input_variable_vec[j] - sibling_variable_vec[j]; + // is_left_variable * left = right; + // } + parent = if index.is_left() { + H::hash_enforce(self, &[&parent, sibling]) + } else { + H::hash_enforce(self, &[sibling, &parent]) + }; + index = index.parent(); + } else { + break; + } + } + + let pre = parent + .get_variables() + .iter() + .zip(root.get_variables().into_iter()) + .map(|(i, l)| (*i, l)) + .collect::>(); + + for (i, j) in pre.iter() { + let v = self.get_value(j); + self.constrain_to_constant(*i, v, pi); + } + } +} + +#[cfg(test)] +mod tests { + use ark_bls12_381::{Bls12_381, Fr}; + use ark_ff::{One, Zero}; + use ark_poly_commit::marlin_pc::MarlinKZG10; + use ark_std::test_rng; + use blake2::Blake2s; + + use crate::composer::Composer; + use crate::*; + + type PC = MarlinKZG10>; + type PlonkInst = Plonk; + + use super::super::super::abstract_hash::AbstractHashOutput; + use super::super::cbmt::*; + use super::*; + + pub fn ks() -> [Fr; 4] { + [ + Fr::one(), + Fr::from(7_u64), + Fr::from(13_u64), + Fr::from(17_u64), + ] + } + + struct MergeHashMock; + + #[derive(Clone)] + struct HashMockOutput(Variable, Fr); + + impl AbstractHashOutput for HashMockOutput { + fn get_variables(&self) -> Vec { + vec![self.0] + } + + fn get_variable_values(&self) -> Vec { + vec![self.1] + } + } + + struct HashMock; + + impl AbstractHash for HashMock { + type Output = HashMockOutput; + + fn hash_enforce(composer: &mut Composer, params: &[&Self::Output]) -> Self::Output { + let xor_res = composer.xor_gate( + params[0].get_variables()[0], + params[1].get_variables()[0], + 64, + ); + HashMockOutput(xor_res, composer.assignment[&xor_res]) + } + } + + impl Merge for MergeHashMock { + type Item = u64; + + fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item { + left ^ right + } + } + + type CBMTMOCK = CBMT; + + #[test] + fn test_merkle_tree_mock() { + let rng = &mut test_rng(); + + // compose + let mut cs = Composer::new(); + + // test 10 elements merkle tree. + let leaves = vec![1u64, 2u64, 3u64, 4u64, 5u64, 6u64, 7u64]; + + let tree = CBMTMOCK::build_merkle_tree(leaves.clone()); + let root = tree.root(); + + let n_root = cs.alloc_and_assign(Fr::from(root)); + let root_output = HashMockOutput(n_root, Fr::from(root)); + + for (i, leaf) in leaves.iter().enumerate() { + let proof = tree.build_proof(&(i as u32)).unwrap(); + assert!(proof.verify(&root, leaf)); + + let n_leaf = cs.alloc_and_assign(Fr::from(*leaf)); + let leaf_output = HashMockOutput(n_leaf, Fr::from(*leaf)); + + let lemmas = proof + .lemmas() + .iter() + .map(|v| { + let lemma = cs.alloc_and_assign(Fr::from(*v)); + HashMockOutput(lemma, Fr::from(*v)) + }) + .collect(); + + cs.merkletree_mermbership::( + root_output.clone(), + leaf_output, + *proof.index(), + lemmas, + Fr::zero(), + ); + } + + let ks = ks(); + println!("size of the circuit: {}", cs.size()); + let srs = PlonkInst::setup(1024, rng).unwrap(); + let (pk, vk) = PlonkInst::keygen(&srs, &cs, ks).unwrap(); + let proof = PlonkInst::prove(&pk, &cs, rng).unwrap(); + let result = PlonkInst::verify(&vk, cs.public_inputs(), proof).unwrap(); + assert!(result); + } +} diff --git a/plonk/src/composer/merkletree/mod.rs b/plonk/src/composer/merkletree/mod.rs new file mode 100644 index 0000000..56d73b8 --- /dev/null +++ b/plonk/src/composer/merkletree/mod.rs @@ -0,0 +1,2 @@ +pub mod cbmt; +pub mod cbmt_constraints; diff --git a/plonk/src/composer/mod.rs b/plonk/src/composer/mod.rs index 52ad608..94a0ae0 100644 --- a/plonk/src/composer/mod.rs +++ b/plonk/src/composer/mod.rs @@ -7,6 +7,12 @@ mod permutation; use permutation::Permutation; mod arithmetic; +mod boolean; +mod logic; +mod range; + +pub mod abstract_hash; +pub mod merkletree; mod synthesize; pub use synthesize::{Error, Selectors, Witnesses}; @@ -36,7 +42,7 @@ pub struct Composer { null_var: Variable, permutation: Permutation, - assignment: Map, + pub(crate) assignment: Map, } impl Composer { @@ -78,6 +84,10 @@ impl Composer { var } + + pub fn get_value(&self, v: &Variable) -> F { + self.assignment.get(v).cloned().unwrap_or(F::zero()) + } } #[cfg(test)] @@ -91,9 +101,66 @@ mod tests { use super::*; + fn circuit() -> Composer { + let mut cs = Composer::new(); + let one = Fr::one(); + let two = one + one; + let three = two + one; + let four = two + two; + let six = two + four; + let var_one = cs.alloc_and_assign(one); + let var_two = cs.alloc_and_assign(two); + let var_three = cs.alloc_and_assign(three); + let var_four = cs.alloc_and_assign(four); + let var_six = cs.alloc_and_assign(six); + cs.create_add_gate( + (var_one, one), + (var_two, one), + var_three, + None, + Fr::zero(), + Fr::zero(), + ); + cs.create_add_gate( + (var_one, one), + (var_three, one), + var_four, + None, + Fr::zero(), + Fr::zero(), + ); + cs.create_mul_gate( + var_two, + var_two, + var_four, + None, + Fr::one(), + Fr::zero(), + Fr::zero(), + ); + cs.create_mul_gate(var_one, var_two, var_six, None, two, two, Fr::zero()); + cs.constrain_to_constant(var_six, six, Fr::zero()); + + let var_zero = cs.alloc_and_assign(Fr::zero()); + cs.boolean_gate(var_zero, Fr::zero()); + cs.boolean_gate(var_one, Fr::zero()); + // cs.boolean_gate(var_two, Fr::zero()); // error: when not boolean. + + cs.range_gate(var_zero, 2, Fr::zero()); // 0 in [0, 4) + cs.range_gate(var_one, 2, Fr::zero()); // 1 in [0, 4) + cs.range_gate(var_two, 2, Fr::zero()); // 2 in [0, 4) + cs.range_gate(var_three, 2, Fr::zero()); // 3 in [0, 4) + + // cs.range_gate(var_four, 2, Fr::zero()); // error: four not in [0, 4) + // cs.range_gate(var_six, 3, Fr::zero()); //error: 3 is not even number. + cs.range_gate(var_six, 4, Fr::zero()); // six in [0, 16) + + cs + } + #[test] fn compose() { - let cs = crate::tests::circuit(); + let cs = circuit(); let ks = [ Fr::one(), Fr::from(7_u64), diff --git a/plonk/src/composer/permutation.rs b/plonk/src/composer/permutation.rs index 180e223..5cdf8a7 100644 --- a/plonk/src/composer/permutation.rs +++ b/plonk/src/composer/permutation.rs @@ -11,9 +11,13 @@ use crate::Map; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum Wire { + /// Left wire of n'th gate. W0(usize), + /// Right wire of n'th gate. W1(usize), + /// Output wire of n'th gate. W2(usize), + /// Fourth wire of n'th gate. W3(usize), } @@ -52,7 +56,7 @@ impl Permutation { self.add_to_map(w_3, Wire::W3(index)); } - fn add_to_map(&mut self, var: Variable, wire: Wire) { + pub fn add_to_map(&mut self, var: Variable, wire: Wire) { let wires = self.variable_map.get_mut(&var).unwrap(); wires.push(wire); } @@ -133,8 +137,7 @@ mod tests { let domain_n = GeneralEvaluationDomain::::new(cs.size()).unwrap(); let roots: Vec<_> = domain_n.elements().collect(); - let (sigma_0, sigma_1, sigma_2, sigma_3) = - cs.permutation.compute_sigmas(domain_n, &ks); + let (sigma_0, sigma_1, sigma_2, sigma_3) = cs.permutation.compute_sigmas(domain_n, &ks); let (id_0, id_1, id_2, id_3) = { let id_0: Vec<_> = cfg_iter!(roots).map(|r| ks[0] * r).collect(); diff --git a/plonk/src/composer/range.rs b/plonk/src/composer/range.rs new file mode 100644 index 0000000..ee16d05 --- /dev/null +++ b/plonk/src/composer/range.rs @@ -0,0 +1,111 @@ +use ark_ff::BitIteratorBE; +use ark_ff::PrimeField; + +use super::permutation::Wire; +use crate::composer::{Composer, Variable}; + +impl Composer { + /// a in [0, 2.pow(num_bits)) + pub fn range_gate(&mut self, a: Variable, num_bits: usize, pi: F) { + let add_wire = |composer: &mut Composer, i: usize, variable: Variable| { + // Since four quads can fit into one gate, the gate index does + // not change for every four wires + let gate_index = composer.size() + (i / 4); + + let wire_data = match i % 4 { + 0 => { + composer.w_3.push(variable); + Wire::W3(gate_index) + } + 1 => { + composer.w_2.push(variable); + Wire::W2(gate_index) + } + 2 => { + composer.w_1.push(variable); + Wire::W1(gate_index) + } + 3 => { + composer.w_0.push(variable); + Wire::W0(gate_index) + } + _ => unreachable!(), + }; + + composer.permutation.add_to_map(variable, wire_data); + }; + + assert!(num_bits % 2 == 0); + + let value = self.assignment[&a]; + let mut bits: Vec<_> = BitIteratorBE::new(value.into_repr()).collect(); + bits.reverse(); + + let mut num_gates = num_bits >> 3; + + if num_bits % 8 != 0 { + num_gates += 1; + } + + let num_quads = num_gates * 4; + + let pad = 1 + (((num_quads << 1) - num_bits) >> 1); + + let used_gates = num_gates + 1; + + let mut accumulators: Vec = Vec::new(); + let mut accumulator = F::zero(); + let four = F::from(4u64); + + for i in 0..pad { + add_wire(self, i, Variable(0)); + } + + for i in pad..=num_quads { + // Convert each pair of bits to quads + let bit_index = (num_quads - i) << 1; + let q_0 = bits[bit_index] as u64; + let q_1 = bits[bit_index + 1] as u64; + let quad = q_0 + (2 * q_1); + + // Compute the next accumulator term + accumulator = four * accumulator; + accumulator += F::from(quad); + + let accumulator_var = self.alloc_and_assign(accumulator); + accumulators.push(accumulator_var); + + add_wire(self, i, accumulator_var); + } + + let zeros = vec![F::zero(); used_gates]; + + self.q_0.extend(zeros.iter()); + self.q_1.extend(zeros.iter()); + self.q_2.extend(zeros.iter()); + self.q_3.extend(zeros.iter()); + self.q_m.extend(zeros.iter()); + self.q_c.extend(zeros.iter()); + self.q_arith.extend(zeros.iter()); + + self.pi.push(pi); + + self.n += used_gates; + + self.w_0.push(Variable(0)); + self.w_1.push(Variable(0)); + self.w_2.push(Variable(0)); + + let last_accumulator = accumulators.len() - 1; + self.assert_equal(accumulators[last_accumulator], a); + accumulators[last_accumulator] = a; + } + + /// a > b + pub fn greater_gate(&mut self, _a: Variable, _b: Variable, _num_bits: usize, _pi: F) { + todo!() + // x = (a-1) - b + + // x in 0 ~ 2.pow(num_bits) + } +} diff --git a/plonk/src/lib.rs b/plonk/src/lib.rs index 7f21878..5eb75b7 100644 --- a/plonk/src/lib.rs +++ b/plonk/src/lib.rs @@ -32,7 +32,7 @@ mod data_structures; pub use crate::data_structures::*; mod composer; -pub use crate::composer::Composer; +pub use crate::composer::*; mod ahp; use ahp::{AHPForPLONK, EvaluationsProvider}; @@ -355,6 +355,31 @@ mod tests { cs.create_mul_gate(var_one, var_two, var_six, None, two, two, Fr::zero()); cs.constrain_to_constant(var_six, six, Fr::zero()); + let var_zero = cs.alloc_and_assign(Fr::zero()); + cs.boolean_gate(var_zero, Fr::zero()); + cs.boolean_gate(var_one, Fr::zero()); + // cs.boolean_gate(var_two, Fr::zero()); // error: when not boolean. + + cs.range_gate(var_zero, 2, Fr::zero()); // 0 in [0, 4) + cs.range_gate(var_one, 2, Fr::zero()); // 1 in [0, 4) + cs.range_gate(var_two, 2, Fr::zero()); // 2 in [0, 4) + cs.range_gate(var_three, 2, Fr::zero()); // 3 in [0, 4) + + // cs.range_gate(var_four, 2, Fr::zero()); // error: four not in [0, 4) + // cs.range_gate(var_six, 3, Fr::zero()); //error: 3 is not even number. + cs.range_gate(var_six, 4, Fr::zero()); // six in [0, 16) + + // logic + let witness_a = cs.alloc_and_assign(Fr::from(500u64)); + let witness_b = cs.alloc_and_assign(Fr::from(357u64)); + let xor_res = cs.xor_gate(witness_a, witness_b, 10); + cs.constrain_to_constant(xor_res, Fr::from(500u64 ^ 357u64), Fr::zero()); + + let witness_a2 = cs.alloc_and_assign(Fr::from(469u64)); + let witness_b2 = cs.alloc_and_assign(Fr::from(321u64)); + let xor_res = cs.and_gate(witness_a2, witness_b2, 10); + cs.constrain_to_constant(xor_res, Fr::from(469u64 & 321u64), Fr::zero()); + cs } @@ -367,7 +392,7 @@ mod tests { let ks = ks(); println!("size of the circuit: {}", cs.size()); - let srs = PlonkInst::setup(8, rng)?; + let srs = PlonkInst::setup(64, rng)?; let (pk, vk) = PlonkInst::keygen(&srs, &cs, ks)?; let proof = PlonkInst::prove(&pk, &cs, rng)?; let result = PlonkInst::verify(&vk, cs.public_inputs(), proof)?;