├── .gitignore ├── src ├── lib.rs ├── circuit.rs └── verkle.rs ├── README.md ├── .github └── workflows │ └── rust.yml └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(int_log)] 2 | pub mod verkle; 3 | pub mod circuit; 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://github.com/nikkolasg/halo2-circuits/actions/workflows/rust.yml/badge.svg) 2 | # Verkle Trees & Halo2 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | name: Test Suite 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly 18 | override: true 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: test 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ecc-circuits" 3 | version = "0.1.0" 4 | authors = ["nalos"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | orchard = { git = "https://github.com/nikkolasg/orchard", branch = "local" } 9 | halo2 = "=0.1.0-beta.1" 10 | group = "0.11" 11 | ff = "0.11" 12 | pasta_curves = "0.2.1" 13 | rand = "0.8" 14 | thiserror = "1.0" 15 | blake2b_simd = { version = "0.5", default-features = false } 16 | 17 | [patch.crates-io] 18 | zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "35e75420657599fdc701cb45704878eb3fa2e59a" } 19 | incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "b7bd6246122a6e9ace8edb51553fbf5228906cbb" } 20 | -------------------------------------------------------------------------------- /src/circuit.rs: -------------------------------------------------------------------------------- 1 | use group::{Curve, Group}; 2 | use halo2::arithmetic::FieldExt; 3 | use halo2::circuit::{Chip, Layouter, SimpleFloorPlanner}; 4 | use halo2::dev::MockProver; 5 | use halo2::pasta::Fp; 6 | use halo2::plonk::{Advice, Circuit, Column, ConstraintSystem, Error}; 7 | use orchard::circuit::gadget::ecc::chip::{EccChip, EccConfig}; 8 | use orchard::circuit::gadget::ecc::NonIdentityPoint; 9 | use orchard::circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig; 10 | use orchard::circuit::gadget::utilities::UtilitiesInstructions; 11 | //use pasta_curves::{arithmetic::CurveAffine, pallas}; 12 | use pasta_curves::pallas; 13 | 14 | #[derive(Debug, Clone)] 15 | struct PedersenChip { 16 | config: PedersenConfig, 17 | ecc: EccChip, 18 | } 19 | 20 | /*impl UtilitiesInstructions for PedersenChip {*/ 21 | //type Var = CellValue; 22 | //} 23 | 24 | #[derive(Debug, Clone)] 25 | struct PedersenConfig { 26 | col1: Column, 27 | col2: Column, 28 | col3: Column, 29 | ecc_config: EccConfig, 30 | } 31 | 32 | impl Chip for PedersenChip { 33 | type Config = PedersenConfig; 34 | type Loaded = (); 35 | 36 | fn config(&self) -> &Self::Config { 37 | &self.config 38 | } 39 | 40 | fn loaded(&self) -> &Self::Loaded { 41 | &() 42 | } 43 | } 44 | 45 | impl PedersenChip { 46 | fn new(p: PedersenConfig) -> PedersenChip { 47 | PedersenChip { 48 | ecc: EccChip::construct(p.ecc_config.clone()), 49 | config: p, 50 | } 51 | } 52 | 53 | fn configure(meta: &mut ConstraintSystem) -> PedersenConfig { 54 | let advices = [ 55 | meta.advice_column(), 56 | meta.advice_column(), 57 | meta.advice_column(), 58 | meta.advice_column(), 59 | meta.advice_column(), 60 | meta.advice_column(), 61 | meta.advice_column(), 62 | meta.advice_column(), 63 | meta.advice_column(), 64 | meta.advice_column(), 65 | ]; 66 | let lookup_table = meta.lookup_table_column(); 67 | let lagrange_coeffs = [ 68 | meta.fixed_column(), 69 | meta.fixed_column(), 70 | meta.fixed_column(), 71 | meta.fixed_column(), 72 | meta.fixed_column(), 73 | meta.fixed_column(), 74 | meta.fixed_column(), 75 | meta.fixed_column(), 76 | ]; 77 | // Shared fixed column for loading constants 78 | let constants = meta.fixed_column(); 79 | meta.enable_constant(constants); 80 | 81 | let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table); 82 | let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check); 83 | let col1 = meta.advice_column(); 84 | meta.enable_equality(col1.into()); 85 | let col2 = meta.advice_column(); 86 | meta.enable_equality(col2.into()); 87 | let col3 = meta.advice_column(); 88 | meta.enable_equality(col3.into()); 89 | 90 | PedersenConfig { 91 | col1, 92 | col2, 93 | col3, 94 | ecc_config, 95 | } 96 | } 97 | } 98 | 99 | // pallas::Base = Fp 100 | // pallas::Scalar = Fq 101 | // vesta::Base = Fq 102 | // vesta::Scalar = Fp 103 | // we construct circuit with elements of the circuit be pallas::Base = Fp so the 104 | // curve points we're gonna use are on pallas (Fp, Fp) -> |E(Fp)| = Fq 105 | // HOWEVER: given halo2/orchard doesn't support variable base multiplication, 106 | // and they use the right field size they only currently allow for a Fp * G 107 | // multiplication 108 | #[derive(Default)] 109 | struct PedersenCircuit { 110 | pub e: Option, 111 | pub f: Option, 112 | pub g: Option, 113 | } 114 | // We have the circuit over C::Base that means the arithmetic circuit has 115 | // elements in C::Base. That means proof will contain points in curve 116 | // C2 with C2::Scalar == C::Base. In our case C = Pallas therefore C2 = Vesta 117 | // The input to the circuit are C::Base elements so to perform group 118 | // arithmetic in the circuit, we have to use points in C2. 119 | impl Circuit for PedersenCircuit { 120 | type Config = PedersenConfig; 121 | type FloorPlanner = SimpleFloorPlanner; 122 | //type Config = EccConfig; 123 | fn configure(cs: &mut ConstraintSystem) -> Self::Config { 124 | PedersenChip::configure(cs) 125 | } 126 | fn without_witnesses(&self) -> Self { 127 | Self::default() 128 | } 129 | fn synthesize( 130 | &self, 131 | config: Self::Config, 132 | mut layouter: impl Layouter, 133 | ) -> Result<(), Error> { 134 | let chip = PedersenChip::new(config); 135 | // allocate inputs for the point addition 136 | // [e] + [f] = [g] 137 | //let _ = chip.ecc.load_private( 138 | //layouter.namespace(|| "witness2"), 139 | //chip.config.col2, 140 | //Some(self.inputs.f), 141 | //)?; 142 | //let _ = chip.ecc.load_private( 143 | //layouter.namespace(|| "witness3"), 144 | //chip.config.col3, 145 | //Some(self.inputs.g), 146 | //)?; 147 | // 148 | // 149 | let scalar_val = pallas::Base::rand(); 150 | let e = chip.ecc.load_private( 151 | layouter.namespace(|| "witness1"), 152 | chip.ecc.config().advices[0], 153 | Some(scalar_val), 154 | )?; 155 | let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); 156 | let p_cell = 157 | NonIdentityPoint::new(chip.ecc.clone(), layouter.namespace(|| "b1"), Some(p_val))?; 158 | let (_, _) = p_cell.mul(layouter.namespace(|| "[e]B"), &e)?; 159 | Ok(()) 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use super::*; 166 | #[test] 167 | fn test_circuit_pedersen() { 168 | let circuit: PedersenCircuit = PedersenCircuit { 169 | e: Some(pallas::Base::from(10)), 170 | f: Some(pallas::Base::from(20)), 171 | g: Some(pallas::Base::from(30)), 172 | }; 173 | let prover = MockProver::run(12, &circuit, vec![]).unwrap(); 174 | // XXX THIS FAILS ! 175 | //assert!(prover.verify().is_ok()); 176 | 177 | /*circuit.inputs.e = ::ScalarExt::from(59);*/ 178 | //let prover = MockProver::run(12, &circuit, vec![]).unwrap(); 179 | /*assert!(prover.verify().is_err());*/ 180 | } 181 | 182 | #[test] 183 | fn pasta_arithmetic() { 184 | let one = Fp::one(); 185 | let zero = Fp::zero(); 186 | assert!(one != zero); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/verkle.rs: -------------------------------------------------------------------------------- 1 | //use halo2::pasta::pasta::{EqAffine, Point, Scalar,Fp}; 2 | use halo2::poly::{ 3 | commitment::{Blind, Params as CParams}, 4 | Polynomial, 5 | }; 6 | use group::Curve; 7 | use halo2::{ 8 | transcript::{Blake2bRead, Blake2bWrite, Challenge255, Transcript,EncodedChallenge, TranscriptWrite}, 9 | arithmetic::{eval_polynomial,CurveAffine}, 10 | poly::{multiopen::{self,ProverQuery,VerifierQuery as VQ}, EvaluationDomain, Coeff}, 11 | }; 12 | use thiserror::Error; 13 | use group::ff::Field; 14 | use std::{fmt::{ Formatter,Debug},io}; 15 | 16 | pub type Poly = Polynomial<::ScalarExt, Coeff>; 17 | pub type Domain = EvaluationDomain; 18 | 19 | pub struct Params { 20 | n: usize, // total length of data 21 | layers: usize, 22 | arity: usize, 23 | k: usize, // 2^k = arity 24 | domain: Domain, 25 | params: CParams, 26 | } 27 | 28 | impl Params 29 | where 30 | C: CurveAffine, 31 | { 32 | pub fn new(len: usize, arity: usize) -> Result { 33 | if !len.is_power_of_two() { 34 | return Err(Error::NotPowerOfTwo); 35 | } 36 | let logn = len.log2() as usize; 37 | 38 | // logb(x) = logc(x) / logc(b) 39 | // so log_arity(n) = log2(n) / log_2(arity) 40 | let layers = logn / arity.log2() as usize; 41 | //let layers = logn / arity as usize; 42 | let k = arity.log2(); 43 | let domain = EvaluationDomain::new(1, k); 44 | let params = CParams::new(k); 45 | println!("len {} arity {} logn {} layers {} k {}",len,arity,logn,layers,k); 46 | Ok(Self { 47 | k: k as usize, 48 | layers, 49 | n: len, 50 | arity, 51 | domain, 52 | params, 53 | }) 54 | } 55 | 56 | pub fn poly(&self, data: Vec) -> Result, Error> { 57 | if data.len() != self.arity { 58 | return Err(Error::InvalidSize); 59 | } 60 | 61 | // TODO hack halo2 to use lagrange form for everything 62 | Ok(self.domain.lagrange_to_coeff(self.domain.lagrange_from_vec(data))) 63 | } 64 | 65 | // commitment use a NULL blind factor -> i.e. it is NOT bliding at the moment, 66 | // just for simplicity of PoC 67 | pub fn commitment(&self, p: &Poly) -> C::CurveExt { 68 | let r = Blind(C::ScalarExt::zero()); 69 | self.params.commit(p, r) 70 | } 71 | 72 | pub fn build_tree(&self, mut data: Vec) -> Result, Error> { 73 | let params = &self; 74 | if data.len() % params.arity != 0 { 75 | return Err(Error::InvalidSize); 76 | } 77 | 78 | let mut layer_nodes = vec![]; 79 | let mut leaf_step = true; 80 | let mut layer = params.layers; 81 | while layer > 0 { 82 | // this assumes a perfect balanced tree 83 | let n_nodes = params.arity.pow((layer-1) as u32); 84 | println!("Build: layer {} (over {}) -> how many nodes {}",layer,params.layers,n_nodes); 85 | layer_nodes = match (0..n_nodes).map(|_| { 86 | if leaf_step { 87 | println!("Draining {} nodes from {} data nodes",params.arity,data.len()); 88 | Node::new_leaf(¶ms, data.drain(0..params.arity).collect::>()) 89 | } else { 90 | println!("Draining {} vnodes from {} vnodes",params.arity,layer_nodes.len()); 91 | Node::new_inode(¶ms, layer_nodes.drain(0..params.arity).collect::>()) 92 | } 93 | }) 94 | .collect::,_>>() { 95 | Ok(nodes) => nodes, 96 | Err(e) => return Err(e), 97 | }; 98 | leaf_step = leaf_step & false; 99 | layer -= 1; 100 | if layer_nodes.len() == 1 { 101 | break 102 | } 103 | } 104 | Ok(layer_nodes.pop().unwrap()) 105 | } 106 | 107 | 108 | 109 | // we assume no more than 2^64 values being committed... 110 | pub fn openings(&self, root: &Node, positions: &[u64]) -> io::Result> { 111 | let params = &self; 112 | if positions.len() == 0{ 113 | panic!("wow"); 114 | } 115 | // find the right node for each position, and keep accumulating the 116 | // polynomials and commitments along the way 117 | // TODO: better access for the prover might be better, like random 118 | // access to all verkle node leaves. In the end it's only a logarithmic 119 | // number of basic arithmetic, it shouldn't make a huge difference 120 | let (prover_queries,verifier_queries) :(Vec>,Vec>) = 121 | positions.iter() 122 | .map(|pos| 123 | prover_recurse(params, *pos,root,true) 124 | ).flatten().unzip(); 125 | //).flatten().collect::>(); 126 | //println!("from positions {:?} -> Queries {:?}",positions, queries); 127 | // TODO see if needs to write other stuff.. 128 | let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); 129 | multiopen::create_proof(¶ms.params, &mut transcript, prover_queries)?; 130 | Ok(Proof { 131 | transcript: transcript.finalize(), 132 | queries: verifier_queries, 133 | }) 134 | } 135 | pub fn verify_proof(&self,mut proof: Proof, root_commit: &C, positions :&[u64]) -> Result<(),Error> { 136 | let params = &self; 137 | let length = params.layers - 1; 138 | 139 | // whole reason of that loop before is because I can't drain inside the 140 | // iterator otherwise there is no owner of the data when creating the 141 | // verifier query which takes a reference. Anyway to do better than this ? 142 | let pqueries = positions.iter().map(|_| { 143 | let mut q = proof.queries.drain(0..length).collect::>(); 144 | q[0].0 = root_commit.clone(); // the parent is the root 145 | q 146 | }).collect::>(); 147 | 148 | let queries = positions.iter().enumerate().map(|(i,pos)| { 149 | // all the positions in the path from root to leaf 150 | let path_pos = verifier_recurse(params.arity, *pos); 151 | //let mut pqueries = proof.queries.drain(0..length).collect::>(); 152 | //pqueries[0].0 = root_commit.clone(); 153 | // check the hash consistency 154 | let consistent_hashes = (1..length).all(|j| { 155 | // the hash of commitment shoud be the value evaluated the layer 156 | // above 157 | let exp = point_to_scalar(&pqueries[i][j].0); 158 | let got = pqueries[i][j-1].1; 159 | exp == got 160 | }); 161 | if !consistent_hashes { 162 | return Err(Error::InvalidProof("consistency check failed".to_string())); 163 | } 164 | // associate the positions and the queries given by prover 165 | // we put the root commitment ourself 166 | Ok(path_pos.into_iter().zip(pqueries[i].iter()) 167 | .map(|(pos,query)| VQ::new_commitment(&query.0,C::ScalarExt::from(pos),query.1))) 168 | }).map(|i| i.unwrap()).flatten().collect::>(); 169 | 170 | let mut proof_transcript = &proof.transcript[..]; 171 | let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&mut proof_transcript); 172 | let msm = self.params.empty_msm(); 173 | match multiopen::verify_proof(&self.params,&mut transcript,queries, msm) { 174 | Ok(guard) => match guard.use_challenges().eval() { 175 | true => Ok(()), 176 | false => Err(Error::InvalidProof("final verification failed".to_string())), 177 | }, 178 | Err(_) => Err(Error::InvalidProof("final verification failed".to_string())), 179 | } 180 | } 181 | } 182 | 183 | pub struct Node { 184 | commitment: C::CurveExt, 185 | poly: Poly, 186 | children: Option>>, 187 | } 188 | 189 | impl Node 190 | where 191 | C: CurveAffine, 192 | { 193 | fn new_inode( 194 | params: &Params, 195 | children: Vec>, 196 | ) -> Result, Error> { 197 | let data = children.iter().map(|v| v.to_scalar()).collect::>(); 198 | let poly = params.poly(data)?; 199 | let commitment = params.commitment(&poly); 200 | Ok(Node { 201 | commitment, 202 | poly, 203 | children: Some(children), 204 | }) 205 | } 206 | 207 | fn new_leaf( 208 | params: &Params, 209 | data: Vec, 210 | ) -> Result, Error> { 211 | let poly = params.poly(data)?; 212 | let commitment = params.commitment(&poly); 213 | Ok(Node { 214 | commitment, 215 | poly, 216 | children: None, 217 | }) 218 | } 219 | 220 | pub fn commitment(&self) -> C { 221 | self.commitment.to_affine() 222 | } 223 | 224 | pub fn is_leaf(&self) -> bool { 225 | self.children.as_ref().map_or(false,|cc| cc.len() > 0) 226 | } 227 | 228 | pub fn to_scalar(&self) -> C::ScalarExt { 229 | point_to_scalar(&self.commitment.to_affine()) 230 | } 231 | 232 | 233 | pub fn describe(&self) -> String { 234 | return self.describe_internal(0); 235 | } 236 | 237 | fn describe_internal(&self, lvl: usize) -> String { 238 | let us = format!("Level {}: {:?}\n",lvl,self); 239 | let children = self.children.as_ref() 240 | .map_or( 241 | vec!["Level {}: Data layer".to_string()], 242 | |c| c.iter().map(|c| c.describe_internal(lvl+1)) 243 | .collect::>()); 244 | us + &children.join("\n") 245 | } 246 | } 247 | 248 | fn prover_recurse<'a,C:CurveAffine>(p :&Params, curr_pos:u64, parent: &'a Node, 249 | isroot: bool) -> Vec<(ProverQuery<'a,C>, VerifierQuery)> { 250 | let index_in_layer = curr_pos % p.arity as u64; 251 | let eval_to = C::ScalarExt::from(index_in_layer); 252 | let next_pos = (curr_pos - index_in_layer) / p.arity as u64; 253 | let pq = ProverQuery { 254 | point: eval_to.clone(), 255 | poly: &parent.poly, 256 | blind: Blind(C::ScalarExt::zero()), 257 | }; 258 | // XXX avoid that double eval for verifier ? 259 | let eval = eval_polynomial(&parent.poly,eval_to); 260 | //let eval = parent.poly.iter().rev() 261 | //.fold(C::ScalarExt::zero(), |acc, coeff| acc * point + coeff) 262 | 263 | let vq :(C,C::ScalarExt) = match isroot { 264 | false => (parent.commitment.to_affine(),eval), 265 | // hack to avoid making a special case for the root - the verifier is 266 | // gonna fill this in. 267 | true => (C::identity(),eval), 268 | }; 269 | 270 | let mut root = vec![(pq,vq)]; 271 | if next_pos < p.arity as u64{ 272 | return root; 273 | } 274 | let children = parent.children.as_ref().unwrap(); 275 | let children_queries = prover_recurse(p,next_pos, children.get(index_in_layer as usize).unwrap(),false); 276 | root.extend(children_queries); 277 | root 278 | } 279 | 280 | pub struct Proof{ 281 | transcript: Vec, 282 | // contains the commitment and the evaluation of the polynomial 283 | // the evaluation point (x_i) is provided by the verifier 284 | // in SNARK the evaluation result (y_i) will be given as witness by verifier 285 | queries: Vec>, 286 | } 287 | 288 | // (commitment, evaluation result of the polynomial) - the positions are 289 | // given by the verifier 290 | type VerifierQuery = (C,::ScalarExt); 291 | 292 | 293 | 294 | // verifer_recurse gives all the indexes at which the verifier should queries 295 | // all the polynomials given by the prover. 296 | fn verifier_recurse(arity: usize, curr_pos: u64) -> Vec { 297 | let index_in_layer = curr_pos % arity as u64; 298 | let next_pos = (curr_pos - index_in_layer) / arity as u64; 299 | let mut root = vec![index_in_layer]; 300 | if next_pos < arity as u64{ 301 | return root; 302 | } 303 | let children = verifier_recurse(arity,next_pos); 304 | root.extend(children); 305 | root 306 | } 307 | 308 | 309 | impl Debug for Node 310 | where 311 | C: CurveAffine, 312 | { 313 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 314 | f.debug_struct("Node") 315 | .field("comm",&(format!("{:?}",self.commitment)[16..22]).to_string()) 316 | .field("childrens", &self.children.as_ref() 317 | .map_or("None".to_string(), |v| format!("{}",v.len()).to_string())) 318 | .finish() 319 | } 320 | } 321 | 322 | fn point_to_scalar(point: &C) -> C::ScalarExt { 323 | let mut transcript = Blake2bWrite::, C, Challenge255>::init(vec![]); 324 | transcript.write_point(point.clone()).unwrap(); 325 | transcript.squeeze_challenge().get_scalar() 326 | } 327 | 328 | 329 | #[derive(Debug, Error)] 330 | pub enum Error { 331 | #[error("Not a power of two")] 332 | NotPowerOfTwo, 333 | #[error("Invalid size (wrt to params)")] 334 | InvalidSize, 335 | #[error("Invalid proof: {0}")] 336 | InvalidProof(String), 337 | } 338 | 339 | #[cfg(test)] 340 | mod test { 341 | 342 | use halo2::pasta::{EpAffine, Fq}; 343 | use halo2::arithmetic::FieldExt; 344 | use super::*; 345 | 346 | #[test] 347 | fn verkle_tree() { 348 | let n = 16; 349 | let arity = 4; 350 | let params = Params::::new(n,arity).expect("invalid params"); 351 | let data = (0..n).map(|_| Fq::rand()).collect::>(); 352 | let root = params.build_tree(data).expect("cant build tree"); 353 | let commitment = root.commitment(); 354 | println!("root1: {:?}",root); 355 | println!("{}",root.describe()); 356 | let pos = vec![5]; 357 | let proof = params.openings(&root,&pos).expect("cant build proof"); 358 | params.verify_proof(proof,&commitment,&pos).expect("valid proofs"); 359 | 360 | let mut inv_proof = params.openings(&root,&pos).expect("cant build proof"); 361 | inv_proof.queries[0].1 = Fq::rand(); 362 | params.verify_proof(inv_proof,&commitment,&pos).expect_err("supposed to be invalid"); 363 | } 364 | } 365 | --------------------------------------------------------------------------------