diff --git a/Cargo.lock b/Cargo.lock index 8f6bfbed..f80f7ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,7 @@ checksum = "eb89b97424403ec9cc22a1df0db748dd7396c9ba5fb5c71a6f0e10ae1d1a7449" dependencies = [ "ark-ec", "ark-ff", + "ark-r1cs-std", "ark-std", ] @@ -88,12 +89,15 @@ checksum = "74b83a7e125e5c611e4a997123effb2f02e3fbc66531dd77751d3016ee920741" dependencies = [ "ark-ec", "ark-ff", + "ark-nonnative-field", + "ark-r1cs-std", "ark-relations", "ark-snark", "ark-std", "blake2", "derivative", "digest 0.9.0", + "tracing", ] [[package]] @@ -158,9 +162,30 @@ dependencies = [ "ark-ec", "ark-ff", "ark-poly", + "ark-r1cs-std", "ark-relations", "ark-serialize", "ark-std", + "derivative", + "tracing", +] + +[[package]] +name = "ark-nonnative-field" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17887af156e9911d1dba5b30d49256d508f82f6a4f765a6fad8b5c637b700353" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-relations", + "ark-std", + "derivative", + "num-bigint 0.4.0", + "num-integer", + "num-traits 0.2.14", + "tracing", ] [[package]] @@ -176,6 +201,22 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ark-r1cs-std" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a90fea2b84ae4443983d56540360ea004cab952292b7a6535798b6b9dcb7f41" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "derivative", + "num-bigint 0.4.0", + "num-traits 0.2.14", + "tracing", +] + [[package]] name = "ark-relations" version = "0.2.0" @@ -1961,9 +2002,21 @@ checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.67", +] + [[package]] name = "tracing-core" version = "0.1.17" @@ -2345,8 +2398,18 @@ dependencies = [ name = "zokrates_embed" version = "0.1.3" dependencies = [ + "ark-bls12-377", + "ark-bw6-761", + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-gm17", + "ark-r1cs-std", + "ark-relations", + "ark-std", "bellman_ce", "sapling-crypto_ce", + "zokrates_field", ] [[package]] diff --git a/changelogs/unreleased/918-dark64 b/changelogs/unreleased/918-dark64 new file mode 100644 index 00000000..5c8b59e9 --- /dev/null +++ b/changelogs/unreleased/918-dark64 @@ -0,0 +1 @@ +Introduce the `snark_verify_bls12_377` embed for one-layer composition of SNARK proofs (over `BLS12-377`/`BW6-761` pair of curves where `BW6-761` is used as an outer curve to `BLS12-377`) \ No newline at end of file diff --git a/zokrates_core/src/embed.rs b/zokrates_core/src/embed.rs index 8276efd1..a99af8ba 100644 --- a/zokrates_core/src/embed.rs +++ b/zokrates_core/src/embed.rs @@ -8,14 +8,19 @@ use crate::typed_absy::types::{ GenericIdentifier, }; use std::collections::HashMap; -use zokrates_field::{Bn128Field, Field}; +use zokrates_field::Field; cfg_if::cfg_if! { if #[cfg(feature = "bellman")] { use pairing_ce::bn256::Bn256; - use pairing_ce::ff::{PrimeField, PrimeFieldRepr}; - use pairing_ce::Engine; - use zokrates_embed::{generate_sha256_round_constraints, BellmanConstraint}; + use zokrates_embed::{bellman::{from_bellman, generate_sha256_round_constraints}}; + } +} + +cfg_if::cfg_if! { + if #[cfg(feature = "ark")] { + use ark_bls12_377::Bls12_377; + use zokrates_embed::ark::{from_ark, generate_verify_constraints}; } } @@ -24,8 +29,6 @@ cfg_if::cfg_if! { #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] pub enum FlatEmbed { U32ToField, - #[cfg(feature = "bellman")] - Sha256Round, Unpack, U8ToBits, U16ToBits, @@ -35,6 +38,10 @@ pub enum FlatEmbed { U16FromBits, U32FromBits, U64FromBits, + #[cfg(feature = "bellman")] + Sha256Round, + #[cfg(feature = "ark")] + SnarkVerifyBls12377, } impl FlatEmbed { @@ -116,6 +123,36 @@ impl FlatEmbed { DeclarationType::Boolean, 256usize, ))]), + #[cfg(feature = "ark")] + FlatEmbed::SnarkVerifyBls12377 => DeclarationSignature::new() + .generics(vec![ + Some(DeclarationConstant::Generic(GenericIdentifier { + name: "N", + index: 0, + })), + Some(DeclarationConstant::Generic(GenericIdentifier { + name: "V", + index: 1, + })), + ]) + .inputs(vec![ + DeclarationType::array(( + DeclarationType::FieldElement, + GenericIdentifier { + name: "N", + index: 0, + }, + )), // inputs + DeclarationType::array((DeclarationType::FieldElement, 8usize)), // proof + DeclarationType::array(( + DeclarationType::FieldElement, + GenericIdentifier { + name: "V", + index: 1, + }, + )), // 18 + (2 * n) // vk + ]) + .outputs(vec![DeclarationType::Boolean]), } } @@ -136,8 +173,6 @@ impl FlatEmbed { pub fn id(&self) -> &'static str { match self { FlatEmbed::U32ToField => "_U32_TO_FIELD", - #[cfg(feature = "bellman")] - FlatEmbed::Sha256Round => "_SHA256_ROUND", FlatEmbed::Unpack => "_UNPACK", FlatEmbed::U8ToBits => "_U8_TO_BITS", FlatEmbed::U16ToBits => "_U16_TO_BITS", @@ -147,15 +182,21 @@ impl FlatEmbed { FlatEmbed::U16FromBits => "_U16_FROM_BITS", FlatEmbed::U32FromBits => "_U32_FROM_BITS", FlatEmbed::U64FromBits => "_U64_FROM_BITS", + #[cfg(feature = "bellman")] + FlatEmbed::Sha256Round => "_SHA256_ROUND", + #[cfg(feature = "ark")] + FlatEmbed::SnarkVerifyBls12377 => "_SNARK_VERIFY_BLS12_377", } } /// Actually get the `FlatFunction` that this `FlatEmbed` represents pub fn synthetize(&self, generics: &[u32]) -> FlatFunction { match self { + FlatEmbed::Unpack => unpack_to_bitwidth(generics[0] as usize), #[cfg(feature = "bellman")] FlatEmbed::Sha256Round => sha256_round(), - FlatEmbed::Unpack => unpack_to_bitwidth(generics[0] as usize), + #[cfg(feature = "ark")] + FlatEmbed::SnarkVerifyBls12377 => snark_verify_bls12_377(generics[0] as usize), _ => unreachable!(), } } @@ -163,44 +204,26 @@ impl FlatEmbed { // util to convert a vector of `(variable_id, coefficient)` to a flat_expression // we build a binary tree of additions by splitting the vector recursively -#[cfg(feature = "bellman")] -fn flat_expression_from_vec(v: &[(usize, E::Fr)]) -> FlatExpression { +fn flat_expression_from_vec(v: &[(usize, T)]) -> FlatExpression { match v.len() { 0 => FlatExpression::Number(T::zero()), 1 => { - let (key, val) = v[0]; - let mut res: Vec = vec![]; - val.into_repr().write_le(&mut res).unwrap(); + let (key, val) = v[0].clone(); FlatExpression::Mult( - box FlatExpression::Number(T::from_byte_vector(res)), + box FlatExpression::Number(val), box FlatExpression::Identifier(FlatVariable::new(key)), ) } n => { let (u, v) = v.split_at(n / 2); FlatExpression::Add( - box flat_expression_from_vec::(u), - box flat_expression_from_vec::(v), + box flat_expression_from_vec::(u), + box flat_expression_from_vec::(v), ) } } } -#[cfg(feature = "bellman")] -impl From> for FlatStatement { - fn from(c: BellmanConstraint) -> FlatStatement { - let rhs_a = flat_expression_from_vec::(&c.a); - let rhs_b = flat_expression_from_vec::(&c.b); - let lhs = flat_expression_from_vec::(&c.c); - - FlatStatement::Condition( - lhs, - FlatExpression::Mult(box rhs_a, box rhs_b), - RuntimeError::BellmanConstraint, - ) - } -} - /// Returns a flat function which computes a sha256 round /// /// # Remarks @@ -210,6 +233,7 @@ impl From> for FlatStatement { /// - arguments #[cfg(feature = "bellman")] pub fn sha256_round() -> FlatFunction { + use zokrates_field::Bn128Field; assert_eq!(T::id(), Bn128Field::id()); // Define iterators for all indices at hand @@ -260,7 +284,19 @@ pub fn sha256_round() -> FlatFunction { ) }); // insert flattened statements to represent constraints - let constraint_statements = r1cs.constraints.into_iter().map(|c| c.into()); + let constraint_statements = r1cs.constraints.into_iter().map(|c| { + let c = from_bellman::(c); + let rhs_a = flat_expression_from_vec::(c.a.as_slice()); + let rhs_b = flat_expression_from_vec::(c.b.as_slice()); + let lhs = flat_expression_from_vec::(c.c.as_slice()); + + FlatStatement::Condition( + lhs, + FlatExpression::Mult(box rhs_a, box rhs_b), + RuntimeError::BellmanConstraint, + ) + }); + // define which subset of the witness is returned let outputs: Vec> = output_indices .map(|o| FlatExpression::Identifier(FlatVariable::new(o))) @@ -290,6 +326,111 @@ pub fn sha256_round() -> FlatFunction { } } +#[cfg(feature = "ark")] +pub fn snark_verify_bls12_377(n: usize) -> FlatFunction { + use zokrates_field::Bw6_761Field; + assert_eq!(T::id(), Bw6_761Field::id()); + + let (out_index, input_indices, proof_indices, vk_indices, constraints, variable_count) = + generate_verify_constraints(n); + + let cs_indices = 0..variable_count; + let input_indices = input_indices.into_iter(); + let proof_indices = proof_indices.into_iter(); + let vk_indices = vk_indices.into_iter(); + + // indices of the arguments to the function + let input_argument_indices = input_indices.clone().map(|i| i + variable_count); + + let proof_argument_indices = proof_indices.clone().map(|i| i + variable_count); + + let vk_argument_indices = vk_indices.clone().map(|i| i + variable_count); + + let input_arguments = input_argument_indices + .clone() + .map(|i| FlatParameter::private(FlatVariable::new(i))); + + let proof_arguments = proof_argument_indices + .clone() + .map(|i| FlatParameter::private(FlatVariable::new(i))); + + let vk_arguments = vk_argument_indices + .clone() + .map(|i| FlatParameter::private(FlatVariable::new(i))); + + let arguments = input_arguments + .chain(proof_arguments) + .chain(vk_arguments) + .collect(); + + let one_binding_statement = FlatStatement::Condition( + FlatExpression::Identifier(FlatVariable::new(0)), + FlatExpression::Number(T::from(1)), + RuntimeError::ArkOneBinding, + ); + + let input_binding_statements: Vec<_> = input_indices + .chain(proof_indices) + .chain(vk_indices) + .zip( + input_argument_indices + .clone() + .chain(proof_argument_indices.clone()) + .chain(vk_argument_indices.clone()), + ) + .map(|(cs_index, argument_index)| { + FlatStatement::Condition( + FlatVariable::new(cs_index).into(), + FlatVariable::new(argument_index).into(), + RuntimeError::ArkInputBinding, + ) + }) + .collect(); + + let constraint_statements: Vec> = constraints + .into_iter() + .map(|c| { + let c = from_ark::(c); + let rhs_a = flat_expression_from_vec::(c.a.as_slice()); + let rhs_b = flat_expression_from_vec::(c.b.as_slice()); + let lhs = flat_expression_from_vec::(c.c.as_slice()); + + FlatStatement::Condition( + lhs, + FlatExpression::Mult(box rhs_a, box rhs_b), + RuntimeError::ArkConstraint, + ) + }) + .collect(); + + let return_statement = FlatStatement::Return(FlatExpressionList { + expressions: vec![FlatExpression::Identifier(FlatVariable::new(out_index))], + }); + + // insert a directive to set the witness + let directive_statement = FlatStatement::Directive(FlatDirective { + outputs: cs_indices.map(FlatVariable::new).collect(), + inputs: input_argument_indices + .chain(proof_argument_indices) + .chain(vk_argument_indices) + .map(|i| FlatVariable::new(i).into()) + .collect(), + solver: Solver::SnarkVerifyBls12377(n), + }); + + let statements: Vec<_> = std::iter::once(directive_statement) + .chain(std::iter::once(one_binding_statement)) + .chain(input_binding_statements) + .chain(constraint_statements) + .chain(std::iter::once(return_statement)) + .collect(); + + FlatFunction { + arguments, + statements, + } +} + fn use_variable( layout: &mut HashMap, name: String, diff --git a/zokrates_core/src/flat_absy/mod.rs b/zokrates_core/src/flat_absy/mod.rs index 8a9a048b..9da11f49 100644 --- a/zokrates_core/src/flat_absy/mod.rs +++ b/zokrates_core/src/flat_absy/mod.rs @@ -23,6 +23,9 @@ pub enum RuntimeError { BellmanConstraint, BellmanOneBinding, BellmanInputBinding, + ArkConstraint, + ArkOneBinding, + ArkInputBinding, Bitness, Sum, Equal, @@ -64,6 +67,9 @@ impl fmt::Display for RuntimeError { BellmanConstraint => "Bellman constraint is unsatisfied", BellmanOneBinding => "Bellman ~one binding is unsatisfied", BellmanInputBinding => "Bellman input binding is unsatisfied", + ArkConstraint => "Ark constraint is unsatisfied", + ArkOneBinding => "Ark ~one binding is unsatisfied", + ArkInputBinding => "Ark input binding is unsatisfied", Bitness => "Bitness check failed", Sum => "Sum check failed", Equal => "Equal check failed", diff --git a/zokrates_core/src/imports.rs b/zokrates_core/src/imports.rs index 39a5c8cc..5e645ae1 100644 --- a/zokrates_core/src/imports.rs +++ b/zokrates_core/src/imports.rs @@ -17,7 +17,7 @@ use std::path::{Path, PathBuf}; use crate::absy::types::UnresolvedType; use typed_arena::Arena; use zokrates_common::Resolver; -use zokrates_field::{Bn128Field, Field}; +use zokrates_field::{Bn128Field, Bw6_761Field, Field}; #[derive(PartialEq, Debug)] pub struct Error { @@ -107,7 +107,8 @@ impl Importer { if T::id() != Bn128Field::id() { return Err(CompileErrorInner::ImportError( Error::new(format!( - "Embed sha256round cannot be used with curve {}", + "`sha256round` is expected to be compiled over `{}` curve, but found `{}`", + Bn128Field::name(), T::name() )) .with_pos(Some(pos)), @@ -121,6 +122,26 @@ impl Importer { } } } + #[cfg(feature = "ark")] + "snark_verify_bls12_377" => { + if T::id() != Bw6_761Field::id() { + return Err(CompileErrorInner::ImportError( + Error::new(format!( + "`snark_verify_bls12_377` is expected to be compiled over `{}` curve, but found `{}`", + Bw6_761Field::name(), + T::name() + )) + .with_pos(Some(pos)), + ) + .in_file(location) + .into()); + } else { + SymbolDeclaration { + id: symbol.get_alias(), + symbol: Symbol::Flat(FlatEmbed::SnarkVerifyBls12377), + } + } + } "unpack" => SymbolDeclaration { id: symbol.get_alias(), symbol: Symbol::Flat(FlatEmbed::Unpack), diff --git a/zokrates_core/src/ir/interpreter.rs b/zokrates_core/src/ir/interpreter.rs index c2c07190..5c54eaa0 100644 --- a/zokrates_core/src/ir/interpreter.rs +++ b/zokrates_core/src/ir/interpreter.rs @@ -6,8 +6,9 @@ use pairing_ce::bn256::Bn256; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt; +use zokrates_embed::ark::generate_verify_witness; #[cfg(feature = "bellman")] -use zokrates_embed::generate_sha256_round_witness; +use zokrates_embed::bellman::generate_sha256_round_witness; use zokrates_field::Field; pub type ExecutionResult = Result, Error>; @@ -148,7 +149,7 @@ impl Interpreter { inputs: &[T], ) -> Result, String> { let (expected_input_count, expected_output_count) = solver.get_signature(); - assert!(inputs.len() == expected_input_count); + assert_eq!(inputs.len(), expected_input_count); let res = match solver { Solver::ConditionEq => match inputs[0].is_zero() { @@ -237,6 +238,17 @@ impl Interpreter { }) .collect() } + #[cfg(feature = "ark")] + Solver::SnarkVerifyBls12377(n) => { + use zokrates_field::Bw6_761Field; + assert_eq!(T::id(), Bw6_761Field::id()); + + generate_verify_witness( + &inputs[..*n], + &inputs[*n..*n + 8usize], + &inputs[*n + 8usize..], + ) + } }; assert_eq!(res.len(), expected_output_count); diff --git a/zokrates_core/src/solvers/mod.rs b/zokrates_core/src/solvers/mod.rs index d6bc0c0c..d8387f26 100644 --- a/zokrates_core/src/solvers/mod.rs +++ b/zokrates_core/src/solvers/mod.rs @@ -13,6 +13,8 @@ pub enum Solver { EuclideanDiv, #[cfg(feature = "bellman")] Sha256Round, + #[cfg(feature = "ark")] + SnarkVerifyBls12377(usize), } impl fmt::Display for Solver { @@ -34,6 +36,8 @@ impl Solver { Solver::EuclideanDiv => (2, 2), #[cfg(feature = "bellman")] Solver::Sha256Round => (768, 26935), + #[cfg(feature = "ark")] + Solver::SnarkVerifyBls12377(n) => (26 + 3 * n, 41991 + 4972 * n), } } } diff --git a/zokrates_core/src/static_analysis/propagation.rs b/zokrates_core/src/static_analysis/propagation.rs index 2438971c..3799755a 100644 --- a/zokrates_core/src/static_analysis/propagation.rs +++ b/zokrates_core/src/static_analysis/propagation.rs @@ -582,7 +582,10 @@ impl<'ast, 'a, T: Field> ResultFolder<'ast, T> for Propagator<'ast, 'a, T> { _ => unreachable!("should be a field value"), } } + #[cfg(feature = "bellman")] FlatEmbed::Sha256Round => None, + #[cfg(feature = "ark")] + FlatEmbed::SnarkVerifyBls12377 => None, }; match r { diff --git a/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_1.json b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_1.json new file mode 100644 index 00000000..0a6e7a71 --- /dev/null +++ b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_1.json @@ -0,0 +1,46 @@ +{ + "entry_point": "./tests/tests/snark/snark_verify_bls12_377_1.zok", + "curves": ["Bw6_761"], + "tests": [ + { + "input": { + "values": [ + "14854361253166356985827940126714566475275412573126716429346293669288261212877767002588736768495892518280555332082", + "213732210873464696846782550184555137228225514409080200587629835423679101735680889388490505365220726936653232900722", + "87538607630632344401950048588801759619324114043687193925268161368645768079512372989046887060116208844098222887523", + "5657143080315521889991799092902512094315245718355471372723437558193333983953910948320586493950523700874011063560", + "57443068623489368358651325326719858590550354409074784986003051193396111859230778144944186401073595967957696940521", + "239017299818740889416561988179003000999440599788233589838386981563280711497257601212385904595560069610856834048609", + "210817940648895568697680255986492415724502544301527123629003695092329489846191300997148203752109923795482648905049", + "104796720182429147963427519368170838521257629224027565408974396362211239635140389257768036297199752289691646178885", + "1", + "237849156256827398282019388933972533154056715710612980343582379691235964002811111531637163291964836316287473866944", + "121324456153144638357885760921484124420296650752569739652599982435599667566663818850130137668154167962818124679946", + "73600332144130508132510040050892177274732799381796146541825372722030832659283233558443467575385522990242420388929", + "44732797483932307692113794160403754043679743522093845554740681666841720206796756514002159097899004452746371432672", + "11133333007786916806537653763736510041397904969121754556887982143919681840159919608974969747422557814633960596319", + "90561577672782365102721874737156537447800052875073945376839636447536979602099666234669817779872333362600029687267", + "5450223346768511418345330845468131514992561567665451102957435878264997759483533580796977034429945593412389724558", + "235853237950439075722577332685219091953664185148611937130324227335365792837509030624805785387473218289296335533890", + "89396333230537847366322364436342481695658547414236326093675863540417141298105682739791578537835191912089484203681", + "115830385654423364502343021113073028365721746246232924567075277636234346135515984504152518055968175024342452068593", + "11263613907940703510226043272578077114062568830909561875804816268614922948545123959608046723806484765856945366386", + "85099371298035679603247495321780481321948685394995318303952199333118698031562067002765732094949837892213467834453", + "237849156256827398282019388933972533154056715710612980343582379691235964002811111531637163291964836316287473866944", + "121324456153144638357885760921484124420296650752569739652599982435599667566663818850130137668154167962818124679946", + "73600332144130508132510040050892177274732799381796146541825372722030832659283233558443467575385522990242420388929", + "44732797483932307692113794160403754043679743522093845554740681666841720206796756514002159097899004452746371432672", + "147751075268067473595930126919015490789314687953476809410426208666203744311411068892162888393693647317357680788622", + "253223744369647051774471619931702227054534749249995484100066505466186263584769989160049762529720081850824722544795", + "226753899873357669326157359116609350824063726018578587491538482132599227769745752321252816012800490263881222618536", + "176875521207730154886136120529839690202784860066517231969835480145708453592054364059780266900035568058186799176840" + ] + }, + "output": { + "Ok": { + "values": ["1"] + } + } + } + ] +} diff --git a/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_1.zok b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_1.zok new file mode 100644 index 00000000..ac406215 --- /dev/null +++ b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_1.zok @@ -0,0 +1,35 @@ +from "EMBED" import snark_verify_bls12_377 + +// Verifies a proof with 1 public input (0 inputs + 1 output) +// Circuit used in this test: +// +// def main() -> field: +// return 1 +// +// Save the circuit as "circuit.zok" and run the following commands (in order): +// $ zokrates compile -i ./circuit.zok -c bls12_377 +// $ zokrates compute-witness +// $ zokrates setup -b ark -s gm17 +// $ zokrates generate-proof -b ark -s gm17 +// +// To get flattened inputs needed for this test, use the following script: +// #!/usr/bin/env node +// +// const path = require("path"); +// const fs = require("fs"); +// +// let output = [] +// for (let i = 2; i < process.argv.length; i++) { +// const source = fs.readFileSync(path.resolve(process.argv[i]), 'utf8') +// const flat = [...source.matchAll(/0x[a-z0-9]+/gm)].map(n => BigInt(n).toString(10)); +// output.push(...flat) +// } +// +// console.log(JSON.stringify(output)); +// +// Save this script as "flatten.js" and run the following command: +// $ node flatten.js proof.json verification.key + +def main(private field[8] proof, private field[1] inputs, private field[20] vk) -> bool: + bool result = snark_verify_bls12_377(inputs, proof, vk) + return result \ No newline at end of file diff --git a/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_2.json b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_2.json new file mode 100644 index 00000000..096e63d2 --- /dev/null +++ b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_2.json @@ -0,0 +1,49 @@ +{ + "entry_point": "./tests/tests/snark/snark_verify_bls12_377_2.zok", + "curves": ["Bw6_761"], + "tests": [ + { + "input": { + "values": [ + "30886639936493049016175318852868223421962513695924799011862965798142544864756272917016480650319179059444391880142", + "210714472424410627451557273311118253425679108293022860127144278352441005505195339659420709617036779682326673533186", + "101969549978420613687361685686211943788361418391955806064423246725556175258043096446227634997412743489772585976407", + "225606981549539274850150853435430709464645103097569777238240148161962808333007149588258118157237589076622092111900", + "94477902787810056005140833707514971680416163466937669331638397632516292559333058429168736236263588445181668773613", + "156965166665593649649919836247487186182263277589020558979047044043456286367751723077006781061358137877318135673282", + "137741518433065408317198878994358008499493756319076293122811213051853618947340414216838530582726247267590705502194", + "126547036337175013106414910386451161000910611736141896590177615068467376299665795605927145787930213987505973766731", + "2", + "4", + "26150522755032959261786285436959898551137848025504557005325333189168466417284586793885098543725273736029879389211", + "169754513648720531797744265077389392707096818238531464510797707592538650668826008029773773893361012602508598834793", + "172926009578431040673671475398833553033375949638930965254433842547261096474109828672139964685904707258254717562981", + "190737508410333459842769941580905855813961948279753848892816073188168697419955701512184037596994386514528425558736", + "1619785665530270858283718034422422029553639813181597813279549759777153426792287594479505827096186872882300711765", + "63694115876363306907024906479487765094262979049817897093877772048737865300854356915611214233650510384715733840309", + "138256625715993632167333368395637908886726696039897946710436000177289042559378071109224721507617736881530800812544", + "107857276706363405428900669135705736327281608718185524590709570009027542794964888233568166787710632979062032163927", + "117681951719142414345371029336876269027160875021843115377112400246872843732924494507290756295050251515524804614493", + "16932482238351125436073535332269385696327441869886865463514408400096260901383164481505901002564992831623879258663", + "46308513493241827384377341904914105301671994851198058483103383539450400464257917932036866988024757095122827891763", + "12774065758179916688827174319525442607170697024774973507481660009802587305759263737719583503498828398179974682702", + "26150522755032959261786285436959898551137848025504557005325333189168466417284586793885098543725273736029879389211", + "169754513648720531797744265077389392707096818238531464510797707592538650668826008029773773893361012602508598834793", + "172926009578431040673671475398833553033375949638930965254433842547261096474109828672139964685904707258254717562981", + "190737508410333459842769941580905855813961948279753848892816073188168697419955701512184037596994386514528425558736", + "187872074241198211214687054253180244660204447307195753216396863454451962530721491538379804696965671145239859590846", + "172889367615248592096001816975404506869611319851954669628812891122278364377518978073247194031011246327549860771430", + "227539811502856876734690781228675876891342950061206768786526280217867721882979938383152839106086209430522325241347", + "33214331578997688306993838825659395665609935174693430136691944882187065031251282996759071511854109007793069549563", + "231745969633345194328768544928321593376710672347115907704852838281813505601170157293937606734791368236398411854640", + "47237328152391646101146711114931457284784793248831449686265996627039097119070481703804420386021717476164037563466" + ] + }, + "output": { + "Ok": { + "values": ["1"] + } + } + } + ] +} diff --git a/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_2.zok b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_2.zok new file mode 100644 index 00000000..47ec38d6 --- /dev/null +++ b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_2.zok @@ -0,0 +1,35 @@ +from "EMBED" import snark_verify_bls12_377 + +// Verifies a proof with 2 public inputs (1 input + 1 output) +// Circuit used in this test: +// +// def main(field a) -> field: +// return a * a +// +// Save the circuit as "circuit.zok" and run the following commands (in order): +// $ zokrates compile -i ./circuit.zok -c bls12_377 +// $ zokrates compute-witness -a 2 +// $ zokrates setup -b ark -s gm17 +// $ zokrates generate-proof -b ark -s gm17 +// +// To get flattened inputs needed for this test, use the following script: +// #!/usr/bin/env node +// +// const path = require("path"); +// const fs = require("fs"); +// +// let output = [] +// for (let i = 2; i < process.argv.length; i++) { +// const source = fs.readFileSync(path.resolve(process.argv[i]), 'utf8') +// const flat = [...source.matchAll(/0x[a-z0-9]+/gm)].map(n => BigInt(n).toString(10)); +// output.push(...flat) +// } +// +// console.log(JSON.stringify(output)); +// +// Save this script as "flatten.js" and run the following command: +// $ node flatten.js proof.json verification.key + +def main(private field[8] proof, private field[2] inputs, private field[22] vk) -> bool: + bool result = snark_verify_bls12_377(inputs, proof, vk) + return result \ No newline at end of file diff --git a/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_5.json b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_5.json new file mode 100644 index 00000000..bbd9e48c --- /dev/null +++ b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_5.json @@ -0,0 +1,58 @@ +{ + "entry_point": "./tests/tests/snark/snark_verify_bls12_377_5.zok", + "curves": ["Bw6_761"], + "tests": [ + { + "input": { + "values": [ + "60457684924193218954780799695448128402450659922819148866042534731462934804174889585425668379894806827192129355035", + "30692976080216123852486339295726836787768525847434467843252380267593056117653493955651719058012291818530914713503", + "125357500613234885873309304302314409734224357144836572740733227274842238671614205545693580811504410323545780196986", + "247651569074486453849718457544382537302063316144666696072138162914150063434773021124866593709430271032906775848230", + "205925817697152648573187530836257591106879527302418969217282393297385329815356611501846344314549412686571096624542", + "52408210490520029867007596145203947635265945726032430791270964404506413872638222587937059680769423104224418341783", + "245391920863498220927838330312555206148254951255756591670548373412908886220063896460749603543488483619267089689381", + "78589112605630898410537770266153128219408270888620016803188045523021815277982064356841045190284469829851165791293", + "2", + "2", + "2", + "2", + "8", + "177010185638377511324501604520563739739317808509681430951164029757345810095174696221494505373089763385490312116358", + "153053794329278051178743982884522858761476481700312111393093980133181276780354993379856934110282786295862092443919", + "119352601414532465114802089751399530705471140760948692288569726825270783170792459863146836606243083937386903791326", + "154395739702801688661668736325735092854144372763349598896957725187031688340840056329057020108410010039817499025290", + "219300867221825583189537695275783744511701200221265601874271468574900788700976161865886103325397401233680596662586", + "6184162650897786738969218350774078215930829358700672611442020481749290685465136203052430712512726892174302053960", + "223615432567648858214064557325028920329759681028000034077399269834838357009569712943919669143358231307616009815434", + "175981229674044997402551815995123920812483064905277870260193130162059294237262155034620065210131116619520563506519", + "149763821034046861511733819294494872607002147076380551480035933618221202037885306159876853613449195409389418630899", + "63613896436066139625271941484202721828670668029032907443649108037543119043920396499152980372905932782160074585332", + "83664647128245200609718804963435898883339854035469313658046832013326011731523365594256699720796643575140884572905", + "144568623182910160643612162930255558274463299944237682869943691750723939109705466332836308875557772911097578331002", + "177010185638377511324501604520563739739317808509681430951164029757345810095174696221494505373089763385490312116358", + "153053794329278051178743982884522858761476481700312111393093980133181276780354993379856934110282786295862092443919", + "119352601414532465114802089751399530705471140760948692288569726825270783170792459863146836606243083937386903791326", + "154395739702801688661668736325735092854144372763349598896957725187031688340840056329057020108410010039817499025290", + "149057616125424973478283591473814309565673764317022249169395023211664620308712452144732942791215282170059517214134", + "41780114592407788188439225711342125468082786659527520800331438243958377257657588645284569742745602805176661321513", + "221720891820590314635918157317325200201212159883016241641294528146177946855934688201173659819769674033460232363042", + "50797017918692117248188277962054664678983362179572320962314605303222488912037808946253081883636411158993624881368", + "45015881196637283857089803245084152220024891684216432418680197321055655838083895789012460620698622411348666936603", + "6614931577154387449192604140382084380965299734550787093003728565193454839715263838958636766466130999302518638149", + "245269024464910939632469903406535269950072210280644644046910525955649284275684019199442256400616717695144071175450", + "43420687027898212877864397162744483839228857355048382441491263977288496076621257227893835793326940537504242772685", + "83272518748948630820579332810320118472860479700707035306680609335770289292207880205558249065217561951629308682324", + "6938552589263177251253935997174459628120498877543020100980702178088439767196046212767463370826879237199769600513", + "170649759404749298159628447642105098367401676132263627894428382159110486377464596992614660456131317719055604455895", + "52917028619173381482821776446008133295138882162362339762583394451153359715914188291769779536313083815565710768404" + ] + }, + "output": { + "Ok": { + "values": ["1"] + } + } + } + ] +} diff --git a/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_5.zok b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_5.zok new file mode 100644 index 00000000..3e828495 --- /dev/null +++ b/zokrates_core_test/tests/tests/snark/snark_verify_bls12_377_5.zok @@ -0,0 +1,39 @@ +from "EMBED" import snark_verify_bls12_377 + +// Verifies a proof with 5 public inputs (4 inputs + 1 output) +// Circuit used in this test: +// +// def main(field[4] a) -> field: +// field out = 0 +// for u32 i in 0..4 do +// out = out + a[i] +// endfor +// return out +// +// Save the circuit as "circuit.zok" and run the following commands (in order): +// $ zokrates compile -i ./circuit.zok -c bls12_377 +// $ zokrates compute-witness -a 2 2 2 2 +// $ zokrates setup -b ark -s gm17 +// $ zokrates generate-proof -b ark -s gm17 +// +// To get flattened inputs needed for this test, use the following script: +// #!/usr/bin/env node +// +// const path = require("path"); +// const fs = require("fs"); +// +// let output = [] +// for (let i = 2; i < process.argv.length; i++) { +// const source = fs.readFileSync(path.resolve(process.argv[i]), 'utf8') +// const flat = [...source.matchAll(/0x[a-z0-9]+/gm)].map(n => BigInt(n).toString(10)); +// output.push(...flat) +// } +// +// console.log(JSON.stringify(output)); +// +// Save this script as "flatten.js" and run the following command: +// $ node flatten.js proof.json verification.key + +def main(private field[8] proof, private field[5] inputs, private field[28] vk) -> bool: + bool result = snark_verify_bls12_377(inputs, proof, vk) + return result \ No newline at end of file diff --git a/zokrates_embed/Cargo.toml b/zokrates_embed/Cargo.toml index 6bc25697..c2188c38 100644 --- a/zokrates_embed/Cargo.toml +++ b/zokrates_embed/Cargo.toml @@ -10,5 +10,15 @@ wasm = ["bellman_ce/wasm", "sapling-crypto_ce/wasm"] multicore = ["bellman_ce/multicore", "sapling-crypto_ce/multicore"] [dependencies] +zokrates_field = { version = "0.4.0", path = "../zokrates_field", default-features = false } bellman_ce = { version = "^0.3", default-features = false } -sapling-crypto_ce = { version = "^0.1", default-features = false } \ No newline at end of file +sapling-crypto_ce = { version = "^0.1", default-features = false } +ark-bls12-377 = { version = "^0.2.0", features = ["curve", "r1cs"], default-features = false } +ark-bw6-761 = { version = "^0.2.0", default-features = false } +ark-gm17 = { version = "^0.2.0", default-features = false, features = ["r1cs"] } +ark-relations = { version = "^0.2.0", default-features = false } +ark-crypto-primitives = { version = "^0.2.0", default-features = false, features = ["r1cs"] } +ark-r1cs-std = { version = "^0.2.0", default-features = false } +ark-std = { version = "^0.2.0", default-features = false } +ark-ec = { version = "^0.2.0", default-features = false } +ark-ff = { version = "^0.2.0", default-features = false} \ No newline at end of file diff --git a/zokrates_embed/src/ark.rs b/zokrates_embed/src/ark.rs new file mode 100644 index 00000000..47e14529 --- /dev/null +++ b/zokrates_embed/src/ark.rs @@ -0,0 +1,340 @@ +use ark_bls12_377::{ + constraints::PairingVar as BLS12PairingVar, Bls12_377 as BLS12PairingEngine, Fq as BLS12Fq, + Fq2 as BLS12Fq2, +}; +use ark_bw6_761::Fr as BW6Fr; +use ark_ec::PairingEngine; +use ark_ff::{BigInteger, One, PrimeField}; +use ark_r1cs_std::bits::boolean::Boolean; +use ark_relations::{ + ns, + r1cs::{ConstraintSystem, ConstraintSystemRef}, +}; + +use ark_crypto_primitives::snark::constraints::SNARKGadget; +use ark_gm17::{constraints::GM17VerifierGadget, Proof, VerifyingKey, GM17}; +use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; + +use crate::Constraint; +use ark_crypto_primitives::SNARK; +use ark_r1cs_std::fields::fp::FpVar; +use ark_r1cs_std::ToBitsGadget; +use ark_relations::r1cs::{ConstraintSynthesizer, SynthesisError}; +use ark_std::test_rng; +use std::str::FromStr; +use zokrates_field::Field; + +type GM17Snark = GM17; +type VerifierGadget = GM17VerifierGadget; + +type G1 = as PairingEngine>::G1Affine; +type G2 = as PairingEngine>::G2Affine; + +#[derive(Copy, Clone)] +struct DefaultCircuit { + pub public_input_size: usize, +} + +impl ConstraintSynthesizer for DefaultCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + for _ in 0..self.public_input_size { + let _ = FpVar::::new_input(ns!(cs, "alloc_input"), || Ok(F::one()))?; + } + Ok(()) + } +} + +#[allow(clippy::type_complexity)] +pub fn generate_verify_constraints( + public_input_size: usize, +) -> ( + usize, + Vec, + Vec, + Vec, + Vec>, + usize, +) { + let cs_sys = ConstraintSystem::::new(); + let cs = ConstraintSystemRef::new(cs_sys); + + let mut rng = test_rng(); // has a fixed seed + let circuit = DefaultCircuit { public_input_size }; + + let (pk, vk) = GM17Snark::circuit_specific_setup(circuit, &mut rng).unwrap(); + let proof = GM17Snark::prove(&pk, circuit, &mut rng).unwrap(); + + let mut fp_vars = Vec::new(); + for _ in 0..public_input_size { + let fp = FpVar::new_input(ns!(cs, "alloc_input"), || Ok(BLS12Fq::one())).unwrap(); + fp_vars.push(fp); + } + + let input_booleans: Vec>> = + fp_vars.iter().map(|i| i.to_bits_le().unwrap()).collect(); + + let inputs = ::Fr, + ::Fq, + GM17Snark, + >>::InputVar::new(input_booleans); + + let proof = ::Fr, + ::Fq, + GM17Snark, + >>::new_proof_unchecked( + ns!(cs, "alloc_proof"), + || Ok(proof), + AllocationMode::Witness, + ) + .unwrap(); + + let vk = ::Fr, + ::Fq, + GM17Snark, + >>::new_verification_key_unchecked( + ns!(cs, "alloc_vk"), || Ok(vk), AllocationMode::Witness + ) + .unwrap(); + + let res = ::Fr, + ::Fq, + GM17Snark, + >>::verify(&vk, &inputs, &proof) + .unwrap(); + + cs.finalize(); + + let num_instance_variables = cs.num_instance_variables(); + let input_indices = fp_vars + .iter() + .map(|f| var_to_index(&f, 0)) + .collect::>(); + + let proof_indices: Vec = vec![ + var_to_index(&proof.a.x, num_instance_variables), + var_to_index(&proof.a.y, num_instance_variables), + var_to_index(&proof.b.x.c0, num_instance_variables), + var_to_index(&proof.b.x.c1, num_instance_variables), + var_to_index(&proof.b.y.c0, num_instance_variables), + var_to_index(&proof.b.y.c1, num_instance_variables), + var_to_index(&proof.c.x, num_instance_variables), + var_to_index(&proof.c.y, num_instance_variables), + ]; + + let mut vk_indices: Vec = vec![ + var_to_index(&vk.h_g2.x.c0, num_instance_variables), + var_to_index(&vk.h_g2.x.c1, num_instance_variables), + var_to_index(&vk.h_g2.y.c0, num_instance_variables), + var_to_index(&vk.h_g2.y.c1, num_instance_variables), + var_to_index(&vk.g_alpha_g1.x, num_instance_variables), + var_to_index(&vk.g_alpha_g1.y, num_instance_variables), + var_to_index(&vk.h_beta_g2.x.c0, num_instance_variables), + var_to_index(&vk.h_beta_g2.x.c1, num_instance_variables), + var_to_index(&vk.h_beta_g2.y.c0, num_instance_variables), + var_to_index(&vk.h_beta_g2.y.c1, num_instance_variables), + var_to_index(&vk.g_gamma_g1.x, num_instance_variables), + var_to_index(&vk.g_gamma_g1.y, num_instance_variables), + var_to_index(&vk.h_gamma_g2.x.c0, num_instance_variables), + var_to_index(&vk.h_gamma_g2.x.c1, num_instance_variables), + var_to_index(&vk.h_gamma_g2.y.c0, num_instance_variables), + var_to_index(&vk.h_gamma_g2.y.c1, num_instance_variables), + ]; + + vk_indices.extend( + vk.query + .iter() + .map(|q| { + vec![ + var_to_index(&q.x, num_instance_variables), + var_to_index(&q.y, num_instance_variables), + ] + }) + .flatten(), + ); + + let out_index = match &res { + Boolean::Is(x) => x + .variable() + .get_index_unchecked(num_instance_variables) + .unwrap(), + _ => unreachable!(), + }; + + let matrices = cs.to_matrices().unwrap(); + let constraints: Vec> = matrices + .a + .into_iter() + .zip(matrices.b.into_iter()) + .zip(matrices.c.into_iter()) + .map(|((a, b), c)| Constraint { + a: a.into_iter().map(|(f, index)| (index, f)).collect(), + b: b.into_iter().map(|(f, index)| (index, f)).collect(), + c: c.into_iter().map(|(f, index)| (index, f)).collect(), + }) + .collect(); + + ( + out_index, + input_indices, + proof_indices, + vk_indices, + constraints, + cs.num_witness_variables() + cs.num_instance_variables(), + ) +} + +pub fn generate_verify_witness(inputs: &[T], proof: &[T], vk: &[T]) -> Vec { + assert_eq!(proof.len(), 8); + assert_eq!(vk.len(), 18 + (2 * inputs.len())); + + let cs_sys = ConstraintSystem::::new(); + let cs = ConstraintSystemRef::new(cs_sys); + + let mut fp_vars = Vec::new(); + for input in inputs { + let input_field: BLS12Fq = BLS12Fq::from_str(input.to_dec_string().as_str()).unwrap(); + let fp = FpVar::new_input(ns!(cs, "alloc_input"), || Ok(input_field)).unwrap(); + fp_vars.push(fp); + } + + let input_booleans: Vec>> = fp_vars + .into_iter() + .map(|i| i.to_bits_le().unwrap()) + .collect(); + + let inputs = ::Fr, + ::Fq, + GM17Snark, + >>::InputVar::new(input_booleans); + + let proof = ::Fr, + ::Fq, + GM17Snark, + >>::new_proof_unchecked( + ns!(cs, "alloc_proof"), + || { + Ok(Proof { + a: new_g1(&proof[0..2]), + b: new_g2(&proof[2..6]), + c: new_g1(&proof[6..8]), + }) + }, + AllocationMode::Witness, + ) + .unwrap(); + + let vk = ::Fr, + ::Fq, + GM17Snark, + >>::new_verification_key_unchecked( + ns!(cs, "alloc_vk"), + || { + Ok(VerifyingKey { + h_g2: new_g2(&vk[0..4]), + g_alpha_g1: new_g1(&vk[4..6]), + h_beta_g2: new_g2(&vk[6..10]), + g_gamma_g1: new_g1(&vk[10..12]), + h_gamma_g2: new_g2(&vk[12..16]), + query: (16..vk.len()) + .collect::>() + .chunks(2) + .map(|c| new_g1(&vk[c[0]..c[1] + 1])) + .collect(), + }) + }, + AllocationMode::Witness, + ) + .unwrap(); + + let _ = ::Fr, + ::Fq, + GM17Snark, + >>::verify(&vk, &inputs, &proof) + .unwrap(); + + cs.finalize(); + + let cs = cs.borrow().unwrap(); + let witness_variables: Vec = cs.witness_assignment.clone(); + + cs.instance_assignment + .clone() + .into_iter() + .chain(witness_variables) + .map(|fq| T::from_byte_vector(fq.into_repr().to_bytes_le())) + .collect() +} + +#[inline] +fn var_to_index(var: &FpVar, offset: usize) -> usize { + match var { + FpVar::Var(ref fp) => { + let var = &fp.variable; + var.get_index_unchecked(offset).unwrap() + } + _ => unreachable!("expected variable, but found a constant"), + } +} + +#[inline] +fn new_g1(flat: &[T]) -> G1 { + assert_eq!(flat.len(), 2); + G1::new( + BLS12Fq::from_str(&*flat[0].to_dec_string()).unwrap(), + BLS12Fq::from_str(&*flat[1].to_dec_string()).unwrap(), + false, + ) +} + +#[inline] +fn new_g2(flat: &[T]) -> G2 { + assert_eq!(flat.len(), 4); + G2::new( + BLS12Fq2::new( + BLS12Fq::from_str(&*flat[0].to_dec_string()).unwrap(), + BLS12Fq::from_str(&*flat[1].to_dec_string()).unwrap(), + ), + BLS12Fq2::new( + BLS12Fq::from_str(&*flat[2].to_dec_string()).unwrap(), + BLS12Fq::from_str(&*flat[3].to_dec_string()).unwrap(), + ), + false, + ) +} + +pub fn from_ark(c: Constraint) -> Constraint { + Constraint { + a: c.a + .into_iter() + .map(|(index, fq)| { + let mut res: Vec = vec![]; + fq.into_repr().write_le(&mut res).unwrap(); + (index, T::from_byte_vector(res)) + }) + .collect(), + b: c.b + .into_iter() + .map(|(index, fq)| { + let mut res: Vec = vec![]; + fq.into_repr().write_le(&mut res).unwrap(); + (index, T::from_byte_vector(res)) + }) + .collect(), + c: c.c + .into_iter() + .map(|(index, fq)| { + let mut res: Vec = vec![]; + fq.into_repr().write_le(&mut res).unwrap(); + (index, T::from_byte_vector(res)) + }) + .collect(), + } +} diff --git a/zokrates_embed/src/bellman.rs b/zokrates_embed/src/bellman.rs new file mode 100644 index 00000000..b178f295 --- /dev/null +++ b/zokrates_embed/src/bellman.rs @@ -0,0 +1,319 @@ +extern crate sapling_crypto_ce as sapling_crypto; +use sapling_crypto::bellman; + +use crate::{Constraint, Witness, R1CS}; +use bellman::{ + pairing::{ff::Field, Engine}, + ConstraintSystem, Index, LinearCombination, SynthesisError, Variable, +}; +use sapling_crypto::bellman::pairing::ff::{PrimeField, PrimeFieldRepr}; +use sapling_crypto::circuit::{ + boolean::{AllocatedBit, Boolean}, + sha256::sha256_compression_function, + uint32::UInt32, +}; + +fn sha256_round>( + mut cs: CS, + input: &[Option], + current_hash: &[Option], +) -> (Vec, Vec, Vec) { + // Allocate bits for `input` + let input_bits = input + .iter() + .enumerate() + .map(|(index, i)| { + AllocatedBit::alloc::( + &mut cs.namespace(|| format!("input_{}", index)), + Some(*i == Some(::one())), + ) + .unwrap() + }) + .collect::>(); + + // Define Booleans whose values are the defined bits + let input = input_bits + .iter() + .map(|i| Boolean::Is(i.clone())) + .collect::>(); + + // Allocate bits for `current_hash` + let current_hash_bits = current_hash + .iter() + .enumerate() + .map(|(index, i)| { + AllocatedBit::alloc::( + &mut cs.namespace(|| format!("current_hash_{}", index)), + Some(*i == Some(::one())), + ) + .unwrap() + }) + .collect::>(); + + // Define Booleans whose values are the defined bits + let current_hash = current_hash_bits + .chunks(32) + .map(|chunk| { + UInt32::from_bits_be( + &chunk + .iter() + .map(|i| Boolean::Is(i.clone())) + .collect::>(), + ) + }) + .collect::>(); + + // Apply the compression function, returning the 8 bytes of outputs + let res = sha256_compression_function::(&mut cs, &input, ¤t_hash).unwrap(); + + // Extract the 256 bits of output out of the 8 bytes + let output_bits = res + .into_iter() + .flat_map(|u| u.into_bits_be()) + .map(|b| b.get_variable().unwrap().clone()); + + // Return indices of `input`, `current_hash` and `output` in the CS + ( + input_bits + .into_iter() + .map(|b| var_to_index(b.get_variable())) + .collect(), + current_hash_bits + .into_iter() + .map(|b| var_to_index(b.get_variable())) + .collect(), + output_bits + .map(|b| var_to_index(b.get_variable())) + .collect(), + ) +} + +impl ConstraintSystem for Witness { + type Root = Self; + + fn alloc(&mut self, _: A, f: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, + { + let index = self.values.len(); + let var = Variable::new_unchecked(Index::Aux(index)); + self.values.push(f().unwrap()); + Ok(var) + } + + fn alloc_input(&mut self, _: A, _: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, + { + unreachable!("Bellman helpers are not allowed to allocate public variables") + } + + fn enforce(&mut self, _: A, _: LA, _: LB, _: LC) + where + A: FnOnce() -> AR, + AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination, + { + // do nothing + } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // do nothing + } + + fn pop_namespace(&mut self) { + // do nothing + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } +} + +impl ConstraintSystem for R1CS { + type Root = Self; + + fn alloc(&mut self, _: A, _: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, + { + // we don't care about the value as we're only generating the CS + let index = self.aux_count; + let var = Variable::new_unchecked(Index::Aux(index)); + self.aux_count += 1; + Ok(var) + } + + fn alloc_input(&mut self, _: A, _: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, + { + unreachable!("Bellman helpers are not allowed to allocate public variables") + } + + fn enforce(&mut self, _: A, a: LA, b: LB, c: LC) + where + A: FnOnce() -> AR, + AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination, + { + let a = a(LinearCombination::zero()); + let b = b(LinearCombination::zero()); + let c = c(LinearCombination::zero()); + + let a = a + .as_ref() + .iter() + .map(|(variable, coefficient)| (var_to_index(*variable), *coefficient)) + .collect(); + let b = b + .as_ref() + .iter() + .map(|(variable, coefficient)| (var_to_index(*variable), *coefficient)) + .collect(); + let c = c + .as_ref() + .iter() + .map(|(variable, coefficient)| (var_to_index(*variable), *coefficient)) + .collect(); + + self.constraints.push(Constraint { a, b, c }); + } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // do nothing + } + + fn pop_namespace(&mut self) { + // do nothing + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } +} + +pub fn generate_sha256_round_constraints( +) -> (R1CS, Vec, Vec, Vec) { + let mut cs = R1CS::default(); + + let (input_bits, current_hash_bits, output_bits) = + sha256_round::(&mut cs, &[None; 512], &[None; 256]); + + // res is now the allocated bits for `input`, `current_hash` and `sha256_output` + + (cs, input_bits, current_hash_bits, output_bits) +} + +pub fn generate_sha256_round_witness( + input: &[E::Fr], + current_hash: &[E::Fr], +) -> Vec { + assert_eq!(input.len(), 512); + assert_eq!(current_hash.len(), 256); + + let mut cs: Witness = Witness { + values: vec![::one()], + }; + + sha256_round::( + &mut cs, + &input.iter().map(|x| Some(*x)).collect::>(), + ¤t_hash.iter().map(|x| Some(*x)).collect::>(), + ); + + cs.values +} + +fn var_to_index(v: Variable) -> usize { + match v.get_unchecked() { + Index::Aux(i) => i + 1, + Index::Input(0) => 0, + _ => unreachable!("No public variables should have been allocated"), + } +} + +pub fn from_bellman(c: Constraint) -> Constraint { + Constraint { + a: c.a + .into_iter() + .map(|(index, fq)| { + let mut res: Vec = vec![]; + fq.into_repr().write_le(&mut res).unwrap(); + (index, T::from_byte_vector(res)) + }) + .collect(), + b: c.b + .into_iter() + .map(|(index, fq)| { + let mut res: Vec = vec![]; + fq.into_repr().write_le(&mut res).unwrap(); + (index, T::from_byte_vector(res)) + }) + .collect(), + c: c.c + .into_iter() + .map(|(index, fq)| { + let mut res: Vec = vec![]; + fq.into_repr().write_le(&mut res).unwrap(); + (index, T::from_byte_vector(res)) + }) + .collect(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bellman::pairing::bn256::{Bn256, Fr}; + + #[test] + fn generate_constraints() { + let (_c, input, current_hash, output) = generate_sha256_round_constraints::(); + assert_eq!(input.len(), 512); + assert_eq!(current_hash.len(), 256); + assert_eq!(output.len(), 256); + } + + #[test] + fn generate_witness() { + let witness = + generate_sha256_round_witness::(&vec![Fr::one(); 512], &vec![Fr::zero(); 256]); + assert_eq!(witness.len(), 26935); + } + + #[test] + fn test_cs() { + use sapling_crypto::circuit::test::TestConstraintSystem; + + let mut cs: TestConstraintSystem = TestConstraintSystem::new(); + + let _ = sha256_round( + &mut cs, + &vec![Some(Fr::zero()); 512], + &vec![Some(Fr::one()); 256], + ); + + assert!(cs.is_satisfied()); + } +} diff --git a/zokrates_embed/src/lib.rs b/zokrates_embed/src/lib.rs index b7a3e2d5..b22972f3 100644 --- a/zokrates_embed/src/lib.rs +++ b/zokrates_embed/src/lib.rs @@ -1,26 +1,16 @@ -extern crate sapling_crypto_ce as sapling_crypto; -use sapling_crypto::bellman; +pub mod ark; +pub mod bellman; -use bellman::{ - pairing::{ff::Field, Engine}, - ConstraintSystem, Index, LinearCombination, SynthesisError, Variable, -}; -use sapling_crypto::circuit::{ - boolean::{AllocatedBit, Boolean}, - sha256::sha256_compression_function, - uint32::UInt32, -}; - -#[derive(Debug)] +#[derive(Debug, Clone)] #[allow(clippy::upper_case_acronyms)] -pub struct BellmanR1CS { +pub struct R1CS { pub aux_count: usize, - pub constraints: Vec>, + pub constraints: Vec>, } -impl Default for BellmanR1CS { +impl Default for R1CS { fn default() -> Self { - BellmanR1CS { + Self { aux_count: 0, constraints: vec![], } @@ -28,289 +18,13 @@ impl Default for BellmanR1CS { } #[derive(Debug)] -pub struct BellmanWitness { - pub values: Vec, +pub struct Witness { + pub values: Vec, } -#[derive(Debug, PartialEq)] -pub struct BellmanConstraint { - pub a: Vec<(usize, E::Fr)>, - pub b: Vec<(usize, E::Fr)>, - pub c: Vec<(usize, E::Fr)>, -} - -fn sha256_round>( - mut cs: CS, - input: &[Option], - current_hash: &[Option], -) -> (Vec, Vec, Vec) { - // Allocate bits for `input` - let input_bits = input - .iter() - .enumerate() - .map(|(index, i)| { - AllocatedBit::alloc::( - &mut cs.namespace(|| format!("input_{}", index)), - Some(*i == Some(::one())), - ) - .unwrap() - }) - .collect::>(); - - // Define Booleans whose values are the defined bits - let input = input_bits - .iter() - .map(|i| Boolean::Is(i.clone())) - .collect::>(); - - // Allocate bits for `current_hash` - let current_hash_bits = current_hash - .iter() - .enumerate() - .map(|(index, i)| { - AllocatedBit::alloc::( - &mut cs.namespace(|| format!("current_hash_{}", index)), - Some(*i == Some(::one())), - ) - .unwrap() - }) - .collect::>(); - - // Define Booleans whose values are the defined bits - let current_hash = current_hash_bits - .chunks(32) - .map(|chunk| { - UInt32::from_bits_be( - &chunk - .iter() - .map(|i| Boolean::Is(i.clone())) - .collect::>(), - ) - }) - .collect::>(); - - // Apply the compression function, returning the 8 bytes of outputs - let res = sha256_compression_function::(&mut cs, &input, ¤t_hash).unwrap(); - - // Extract the 256 bits of output out of the 8 bytes - let output_bits = res - .into_iter() - .flat_map(|u| u.into_bits_be()) - .map(|b| b.get_variable().unwrap().clone()); - - // Return indices of `input`, `current_hash` and `output` in the CS - ( - input_bits - .into_iter() - .map(|b| var_to_index(b.get_variable())) - .collect(), - current_hash_bits - .into_iter() - .map(|b| var_to_index(b.get_variable())) - .collect(), - output_bits - .map(|b| var_to_index(b.get_variable())) - .collect(), - ) -} - -impl ConstraintSystem for BellmanWitness { - type Root = Self; - - fn alloc(&mut self, _: A, f: F) -> Result - where - F: FnOnce() -> Result, - A: FnOnce() -> AR, - AR: Into, - { - let index = self.values.len(); - let var = Variable::new_unchecked(Index::Aux(index)); - self.values.push(f().unwrap()); - Ok(var) - } - - fn alloc_input(&mut self, _: A, _: F) -> Result - where - F: FnOnce() -> Result, - A: FnOnce() -> AR, - AR: Into, - { - unreachable!("Bellman helpers are not allowed to allocate public variables") - } - - fn enforce(&mut self, _: A, _: LA, _: LB, _: LC) - where - A: FnOnce() -> AR, - AR: Into, - LA: FnOnce(LinearCombination) -> LinearCombination, - LB: FnOnce(LinearCombination) -> LinearCombination, - LC: FnOnce(LinearCombination) -> LinearCombination, - { - // do nothing - } - - fn push_namespace(&mut self, _: N) - where - NR: Into, - N: FnOnce() -> NR, - { - // do nothing - } - - fn pop_namespace(&mut self) { - // do nothing - } - - fn get_root(&mut self) -> &mut Self::Root { - self - } -} - -impl ConstraintSystem for BellmanR1CS { - type Root = Self; - - fn alloc(&mut self, _: A, _: F) -> Result - where - F: FnOnce() -> Result, - A: FnOnce() -> AR, - AR: Into, - { - // we don't care about the value as we're only generating the CS - let index = self.aux_count; - let var = Variable::new_unchecked(Index::Aux(index)); - self.aux_count += 1; - Ok(var) - } - - fn alloc_input(&mut self, _: A, _: F) -> Result - where - F: FnOnce() -> Result, - A: FnOnce() -> AR, - AR: Into, - { - unreachable!("Bellman helpers are not allowed to allocate public variables") - } - - fn enforce(&mut self, _: A, a: LA, b: LB, c: LC) - where - A: FnOnce() -> AR, - AR: Into, - LA: FnOnce(LinearCombination) -> LinearCombination, - LB: FnOnce(LinearCombination) -> LinearCombination, - LC: FnOnce(LinearCombination) -> LinearCombination, - { - let a = a(LinearCombination::zero()); - let b = b(LinearCombination::zero()); - let c = c(LinearCombination::zero()); - - let a = a - .as_ref() - .iter() - .map(|(variable, coefficient)| (var_to_index(*variable), *coefficient)) - .collect(); - let b = b - .as_ref() - .iter() - .map(|(variable, coefficient)| (var_to_index(*variable), *coefficient)) - .collect(); - let c = c - .as_ref() - .iter() - .map(|(variable, coefficient)| (var_to_index(*variable), *coefficient)) - .collect(); - - self.constraints.push(BellmanConstraint { a, b, c }); - } - - fn push_namespace(&mut self, _: N) - where - NR: Into, - N: FnOnce() -> NR, - { - // do nothing - } - - fn pop_namespace(&mut self) { - // do nothing - } - - fn get_root(&mut self) -> &mut Self::Root { - self - } -} - -pub fn generate_sha256_round_constraints( -) -> (BellmanR1CS, Vec, Vec, Vec) { - let mut cs = BellmanR1CS::default(); - - let (input_bits, current_hash_bits, output_bits) = - sha256_round(&mut cs, &[None; 512], &[None; 256]); - - // res is now the allocated bits for `input`, `current_hash` and `sha256_output` - - (cs, input_bits, current_hash_bits, output_bits) -} - -pub fn generate_sha256_round_witness( - input: &[E::Fr], - current_hash: &[E::Fr], -) -> Vec { - assert_eq!(input.len(), 512); - assert_eq!(current_hash.len(), 256); - - let mut cs: BellmanWitness = BellmanWitness { - values: vec![::one()], - }; - - sha256_round( - &mut cs, - &input.iter().map(|x| Some(*x)).collect::>(), - ¤t_hash.iter().map(|x| Some(*x)).collect::>(), - ); - - cs.values -} - -fn var_to_index(v: Variable) -> usize { - match v.get_unchecked() { - Index::Aux(i) => i + 1, - Index::Input(0) => 0, - _ => unreachable!("No public variables should have been allocated"), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bellman::pairing::bn256::{Bn256, Fr}; - - #[test] - fn generate_constraints() { - let (_c, input, current_hash, output) = generate_sha256_round_constraints::(); - assert_eq!(input.len(), 512); - assert_eq!(current_hash.len(), 256); - assert_eq!(output.len(), 256); - } - - #[test] - fn generate_witness() { - let witness = - generate_sha256_round_witness::(&vec![Fr::one(); 512], &vec![Fr::zero(); 256]); - assert_eq!(witness.len(), 26935); - } - - #[test] - fn test_cs() { - use sapling_crypto::circuit::test::TestConstraintSystem; - - let mut cs: TestConstraintSystem = TestConstraintSystem::new(); - - let _ = sha256_round( - &mut cs, - &vec![Some(Fr::zero()); 512], - &vec![Some(Fr::one()); 256], - ); - - assert!(cs.is_satisfied()); - } +#[derive(Default, Debug, PartialEq, Clone)] +pub struct Constraint { + pub a: Vec<(usize, T)>, + pub b: Vec<(usize, T)>, + pub c: Vec<(usize, T)>, }