From 64e0ee9546827d4d6e8cdd299339313cd2285b81 Mon Sep 17 00:00:00 2001 From: oskarth Date: Mon, 29 Nov 2021 16:02:46 +0800 Subject: [PATCH] Initial Circom 2 support (#10) * Import circom-2 test vectors * Add failing test under feature flag * Add exceptionHandler * Add showSharedRWMemory * Add getFieldNumLen32 and disable getFrLen * Add getVersion Also print version, n32 * Add getRawPrime - Disable getPtrRawPrime - Write as conditional cfg code blocks * Refactor cfg code blocks * Add readSharedRWMemory and get prime from WASM mem - Add fromArray32 convenience function * WIP: Debug R1CSfile header field_size in header is 1, not 32 as expected Don't see anything recently changed here: https://github.com/iden3/r1csfile/blob/master/src/r1csfile.js (used by snarkjs) But this seems new: https://github.com/iden3/circom/blob/0149dc0643c3842635fb758efc9ebc102f170a38/constraint_writers/src/r1cs_writer.rs * Add CircomVersion struct to Wasm * XXX: Enum test * Trait version * Move traits to Circom, CircomBase, Circom2 * Simplify Wasm struct and remove version * Feature gate Circom1/Circom2 traits * Use cfg_if for witness calculation Make normal dependency * Fix visibilty for both test paths * Remove println Can introduce tracing separately * refactor * Make clippy happy with imports, unused variables --- Cargo.toml | 4 +- src/witness/circom.rs | 91 +++++++++++++++++++++----- src/witness/mod.rs | 8 ++- src/witness/witness_calculator.rs | 67 ++++++++++++++++--- test-vectors/circom2_multiplier2.r1cs | Bin 0 -> 264 bytes test-vectors/circom2_multiplier2.wasm | Bin 0 -> 29071 bytes tests/groth16.rs | 32 +++++++++ 7 files changed, 175 insertions(+), 27 deletions(-) create mode 100644 test-vectors/circom2_multiplier2.r1cs create mode 100644 test-vectors/circom2_multiplier2.wasm diff --git a/Cargo.toml b/Cargo.toml index 59b97ab..2cc83dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,13 @@ thiserror = "1.0.26" color-eyre = "0.5" criterion = "0.3.4" +cfg-if = "1.0" + [dev-dependencies] hex-literal = "0.2.1" tokio = { version = "1.7.1", features = ["macros"] } serde_json = "1.0.64" ethers = { git = "https://github.com/gakonst/ethers-rs", features = ["abigen"] } -cfg-if = "1.0" [[bench]] name = "groth16" @@ -45,3 +46,4 @@ harness = false [features] bench-complex-all = [] +circom-2 = [] diff --git a/src/witness/circom.rs b/src/witness/circom.rs index d9e9dbb..ae31b72 100644 --- a/src/witness/circom.rs +++ b/src/witness/circom.rs @@ -4,41 +4,92 @@ use wasmer::{Function, Instance, Value}; #[derive(Clone, Debug)] pub struct Wasm(Instance); -impl Wasm { - pub fn new(instance: Instance) -> Self { - Self(instance) - } +pub trait CircomBase { + fn init(&self, sanity_check: bool) -> Result<()>; + fn func(&self, name: &str) -> &Function; + fn get_ptr_witness_buffer(&self) -> Result; + fn get_ptr_witness(&self, w: i32) -> Result; + fn get_n_vars(&self) -> Result; + fn get_signal_offset32( + &self, + p_sig_offset: u32, + component: u32, + hash_msb: u32, + hash_lsb: u32, + ) -> Result<()>; + fn set_signal(&self, c_idx: i32, component: i32, signal: i32, p_val: i32) -> Result<()>; + fn get_i32(&self, name: &str) -> Result; +} - pub fn init(&self, sanity_check: bool) -> Result<()> { - let func = self.func("init"); - func.call(&[Value::I32(sanity_check as i32)])?; - Ok(()) - } +pub trait Circom { + fn get_fr_len(&self) -> Result; + fn get_ptr_raw_prime(&self) -> Result; +} + +pub trait Circom2 { + fn get_version(&self) -> Result; + fn get_field_num_len32(&self) -> Result; + fn get_raw_prime(&self) -> Result<()>; + fn read_shared_rw_memory(&self, i: i32) -> Result; +} - pub fn get_fr_len(&self) -> Result { +#[cfg(not(feature = "circom-2"))] +impl Circom for Wasm { + fn get_fr_len(&self) -> Result { self.get_i32("getFrLen") } - pub fn get_ptr_raw_prime(&self) -> Result { + fn get_ptr_raw_prime(&self) -> Result { self.get_i32("getPRawPrime") } +} - pub fn get_n_vars(&self) -> Result { - self.get_i32("getNVars") +#[cfg(feature = "circom-2")] +impl Circom2 for Wasm { + fn get_version(&self) -> Result { + self.get_i32("getVersion") + } + + fn get_field_num_len32(&self) -> Result { + self.get_i32("getFieldNumLen32") } - pub fn get_ptr_witness_buffer(&self) -> Result { + fn get_raw_prime(&self) -> Result<()> { + let func = self.func("getRawPrime"); + let _result = func.call(&[])?; + Ok(()) + } + + fn read_shared_rw_memory(&self, i: i32) -> Result { + let func = self.func("readSharedRWMemory"); + let result = func.call(&[i.into()])?; + Ok(result[0].unwrap_i32()) + } +} + +impl CircomBase for Wasm { + fn init(&self, sanity_check: bool) -> Result<()> { + let func = self.func("init"); + func.call(&[Value::I32(sanity_check as i32)])?; + Ok(()) + } + + fn get_ptr_witness_buffer(&self) -> Result { self.get_i32("getWitnessBuffer") } - pub fn get_ptr_witness(&self, w: i32) -> Result { + fn get_ptr_witness(&self, w: i32) -> Result { let func = self.func("getPWitness"); let res = func.call(&[w.into()])?; Ok(res[0].unwrap_i32()) } - pub fn get_signal_offset32( + fn get_n_vars(&self) -> Result { + self.get_i32("getNVars") + } + + fn get_signal_offset32( &self, p_sig_offset: u32, component: u32, @@ -56,7 +107,7 @@ impl Wasm { Ok(()) } - pub fn set_signal(&self, c_idx: i32, component: i32, signal: i32, p_val: i32) -> Result<()> { + fn set_signal(&self, c_idx: i32, component: i32, signal: i32, p_val: i32) -> Result<()> { let func = self.func("setSignal"); func.call(&[c_idx.into(), component.into(), signal.into(), p_val.into()])?; @@ -76,3 +127,9 @@ impl Wasm { .unwrap_or_else(|_| panic!("function {} not found", name)) } } + +impl Wasm { + pub fn new(instance: Instance) -> Self { + Self(instance) + } +} diff --git a/src/witness/mod.rs b/src/witness/mod.rs index 6112a06..2a2cb37 100644 --- a/src/witness/mod.rs +++ b/src/witness/mod.rs @@ -5,7 +5,13 @@ mod memory; pub(super) use memory::SafeMemory; mod circom; -pub(super) use circom::Wasm; +pub(super) use circom::{CircomBase, Wasm}; + +#[cfg(feature = "circom-2")] +pub(super) use circom::Circom2; + +#[cfg(not(feature = "circom-2"))] +pub(super) use circom::Circom; use fnv::FnvHasher; use std::hash::Hasher; diff --git a/src/witness/witness_calculator.rs b/src/witness/witness_calculator.rs index 46fb6f2..54904da 100644 --- a/src/witness/witness_calculator.rs +++ b/src/witness/witness_calculator.rs @@ -1,10 +1,15 @@ +use super::{fnv, CircomBase, SafeMemory, Wasm}; use color_eyre::Result; use num_bigint::BigInt; use num_traits::Zero; use std::cell::Cell; use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, RuntimeError, Store}; -use super::{fnv, SafeMemory, Wasm}; +#[cfg(feature = "circom-2")] +use super::Circom2; + +#[cfg(not(feature = "circom-2"))] +use super::Circom; #[derive(Clone, Debug)] pub struct WitnessCalculator { @@ -19,6 +24,16 @@ pub struct WitnessCalculator { #[error("{0}")] struct ExitCode(u32); +#[cfg(feature = "circom-2")] +fn from_array32(arr: Vec) -> BigInt { + let mut res = BigInt::zero(); + let radix = BigInt::from(0x100000000u64); + for &val in arr.iter() { + res = res * &radix + BigInt::from(val); + } + res +} + impl WitnessCalculator { pub fn new(path: impl AsRef) -> Result { let store = Store::default(); @@ -38,22 +53,44 @@ impl WitnessCalculator { "logFinishComponent" => runtime::log_component(&store), "logStartComponent" => runtime::log_component(&store), "log" => runtime::log_component(&store), + "exceptionHandler" => runtime::exception_handler(&store), + "showSharedRWMemory" => runtime::show_memory(&store), } }; let instance = Wasm::new(Instance::new(&module, &import_object)?); - let n32 = (instance.get_fr_len()? >> 2) - 2; - - let mut memory = SafeMemory::new(memory, n32 as usize, BigInt::zero()); + let n32; + let prime: BigInt; + let mut safe_memory: SafeMemory; + + cfg_if::cfg_if! { + if #[cfg(feature = "circom-2")] { + //let version = instance.get_version()?; + n32 = instance.get_field_num_len32()?; + safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero()); + let _res = instance.get_raw_prime()?; + let mut arr = vec![0; n32 as usize]; + for i in 0..n32 { + let res = instance.read_shared_rw_memory(i)?; + arr[(n32 as usize) - (i as usize) - 1] = res; + } + prime = from_array32(arr); + } else { + // Fallback to Circom 1 behavior + //version = 1; + n32 = (instance.get_fr_len()? >> 2) - 2; + safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero()); + let ptr = instance.get_ptr_raw_prime()?; + prime = safe_memory.read_big(ptr as usize, n32 as usize)?; + } + } - let ptr = instance.get_ptr_raw_prime()?; - let prime = memory.read_big(ptr as usize, n32 as usize)?; let n64 = ((prime.bits() - 1) / 64 + 1) as i32; - memory.prime = prime; + safe_memory.prime = prime; Ok(WitnessCalculator { instance, - memory, + memory: safe_memory, n64, }) } @@ -162,6 +199,20 @@ mod runtime { Function::new_native(store, func) } + // Circom 2.0 + pub fn exception_handler(store: &Store) -> Function { + #[allow(unused)] + fn func(a: i32) {} + Function::new_native(store, func) + } + + // Circom 2.0 + pub fn show_memory(store: &Store) -> Function { + #[allow(unused)] + fn func() {} + Function::new_native(store, func) + } + pub fn log_signal(store: &Store) -> Function { #[allow(unused)] fn func(a: i32, b: i32) {} diff --git a/test-vectors/circom2_multiplier2.r1cs b/test-vectors/circom2_multiplier2.r1cs new file mode 100644 index 0000000000000000000000000000000000000000..e61b9b091d3e08fab09d4639f9763e776451f221 GIT binary patch literal 264 zcmXRiOfF_*U|?VdVkRK20AdgTiGlb)@L}@Tht3lVc2;`4FxH5TXl&f(8n8oif#Jg< zzZ3(QUJ#8BfaIZS2%6^rHAn%X2d{b-h#IgCkb6OV5P-Q$0n9@XFn2=z2KF~d7696s BEn)xw literal 0 HcmV?d00001 diff --git a/test-vectors/circom2_multiplier2.wasm b/test-vectors/circom2_multiplier2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7ea487c35dda61de0272a262f576c463808a5ab4 GIT binary patch literal 29071 zcmeHPdyHJwc|Z3t&pWeoclN>TdUrWFJ`^G zD%jn`Jd!Gv5|uPj6BVZsqJKb0DumodYB!HoRBcpRRTXMe#ZlTkT4-IBR3t%hf8TfS z+_{f)?u<9cB$5|AckcPlW-lVo(c}=u{=lt!8Qd-noUn2WH%v2X31= zxUl!`xvTcf-M)WjUer6H=ApapzW;{1_RP=R{=S>9ojGv#{DZ==?wApVX;_wJ+XW$t zrE;NwEB+V46h@(3#-(7E48t&mX%-5Wg$%);C=@D{YN23s61ZeR4AyD`?m)dU&~$3G zdc9u5ja@JfqK<-Xh~uTww~Six0Bc~1>YXzSH_gl+g399kqqs?`c*DY;`2}%fbAD#e z?MaEaY3Tm>y$ds`)SK{WPh7=ntvDpg0l{Z`EAkE!uxxu&AW~=2)2`Q#IA79GBof}+Q z&IJ%}uv)w99dvcWYdO4%U5$b-8cz3v?zl>WnpRf%uAoY3px*1ldg;auZ=mM}s~p}y zeZaV0)y%`OdfspEK+6Tzl`Fmp2}Y0rw5U9mBS?tl2wIHg2ohpB0yS=GIQ&N-PkrmR z##;YJxbLDA7nx8D9}l%HX-aDwZjokFn*ETprsXFhiA@sV!beI1lV4=kNK#8lY9s+$ z_()0M9~YU!BpFUghDp*QNh>93kz|4-6Di3AtNOI_DKmlU;Q-b23H&tSB_0pK zMHylk)fr+?)fr-l)fr;I)fr;=)fr+ms53Adk3a3a+Ax}nz!|_N@`P6)`h-_pB>I1*J@2c-x))I3KGwx$N_dILH{B}wYV z!9-vyte#Tj%xnDhyRAa`dLH>rLCxn|4rd+M$eTd9aZg(^VLXhJUTq>Uyg+ z^i~_xTWu&~H6Df6X8Mn#r@pSIzM-dnP*43(hI%G(eTKxkp2UXU^#}DN4rNGWI?l_` z(a_T|sHbBnLk9!flmRxV2R4)e#wuQzQE@1vA~&|B(-;@K)cLtRMm4XZAwmXonv+#FUn5L5dU53PEKAia zG}0-X=PG3$4wV(=E9>@FcGz?D^wCUB*=%`LIP#UnY@-*mEuK~?H7ZFhjxvwVs#1BR zSF{j&_-v~;jtQ@>V>XzYs+5&wJ;Y$vsw$NSG2b4TION!)B()IA{NPb3BP&`6d3sj0 z2JB%9nG2z;hY(D&l@O|{f{;%&CJH%JSM+*TwN&Tv>1ZMJ;B$Sho>je`!3_%u&@~tc9(R#@@_O(mYN+9#CeNcEB3S)*aZ6N46jhOB7xI@KRXt9Xz~CR()~ZJy4)B zUH$~JRefW&UFBLT6ic=VM0sc3oHp7OU$m>NU&TLSx&tIMWpy9WR>x&QR%d0!Z7@?` zG7V?RUoqPjK7d`c9sd{z11ZN$*Yuac7Cv0Wr`t9Ehy|1a7f4nt&~(gdm;4nwRJQEe zP_Tf9BtW<>K2g?oG1Ni98sf>znBBVN@QbWM8&q2_z@(7yt0hfsMO7M|EoZusa zIKf8-ae|Kw;shTV#0fq!h!cEF5Yw0$MTio7$SxmthmUfa4=l@~(g{8?h!cEd5GVM^ zAWra+L7d-b zGKdp=WDqC#$RJMev6lG2OfRx-b zGKdp=WDqC#SWkQev8%ws2|mv2^5J&)7*6vM1kx4$kwKi`BZD}>M+R|%j|}1j9~s06 zJ~D_Ce4IyoV7{yNk4;@ZhC6(;(tKcv9F-bGKdp=WDqC#$RJMekwKi`<3i#C zYlXbClw~;jZ+FDD)%M|(c_w)OhG`=hZe_d z>|3VyHPid7McS3MISfWqKlMIql_QucIkod|Ha7pX8?{~SV1W*<>_#3DhUr&Ff6QQ^ zSJ`XmKnH;cB_B|!>CtcJ5C^Z=-bgcn>`TBJXLXa4WrM*juA> zk*Xw8l`b+MwLaI>``l=*&kcLyc|J#Il)B_>NR8;X)5e%@RfWX&YqmGpkEdc3?(#j6 z(opHCE$>WZQ@TJ^k|~s;0XBtF)MQgAMWcHE9?$jfmN%8>-vm_I;Gj(VT;z@;nJvFa zG?v+Vj6{>Uz2bG-YxmPDXk8Zd3Yk(~*()Zzvye^Y9T-!|fJ)H-8&D}~>ig2DzAugI z`_fcyUz+f|yuL&nDAgR4X(x{SdxUMPEH$Rz5}wrez;F2FQ@eV($n|Lx+Hi?4F_e~t0RXWVT&qDjp=*qq`tSd z^}TgnrngeKnu1+43xf`wUO2#ZQ2J6+-@Qil-D_Omy{7ct%hPwSK_*+N;Ruth6piV- z&!oQlwDsL*U8ei+(Fio2n%6e~XzkPw73|(|h)a-bcsuK02xQ(YD@4*JXT^ zy$#E?l(&uQy=`3YZBu%0^Yq>}ruUmkz2CI;ezPv)H|zmHaEu1=PM;drd%%?513bM4 zOzLgf*4uJj#+GaqEQC{5nbKRu(_5vj=XYI(UuG%@6UCYG^h~YGU}o*H6c4oL8WC7{ zy1N-b8w<*vgTrlOva|LsB1#eL4D@rL2oq3&L&)0WJ;m6{yi*^|TBDUYl#b3m;Jv(E7%k$U<=19^<}G^wod8AN51 zja){s@^ALz2!nA(22Yj|mm?uc^{V_JRf+N#SuimG1O&{N#h zyXr`;t77Tj>c>^jh*L~~lDLLFDQmrrAh z6b%j<$6-gJrrt0kdc(AG4TIJGntlv39yiQ*ishb$neb=@tOQk+TAS7NHgi{Hvwpfo z8e7DELvOPtPnwnRM)Wpo>20Ne^7_$` z){=eoqcp6TxwMuH!bVDXK|oC@YUwSxMsLX=q7<*t=zVOyuIJy?^FOpI{(JX{hSZYW zlv!_CGKHRmbXL3Z(WJ}NF z8a^kPqHKeC&bX9cq_O1~Tfhy56>1qlYk+fxuQk+(55qo-z|!1NA}mR7Ou$ z2K#!EGQla~30a^Z1WtA0ATS?aq_2E@F-pTJ#po#UC{8R&ob<#&Pw<6G@Ve0i(!lyTIpQ z4PPE4*gNeV4V+>nO+~xN_2VS!m8icct&KulB2a}z=VvX6!??5IVO(9~s43IBGl8R= z&}F&qPT_;DW1wicj`OXf&Qqpg;xMy+q>f|7mW$a++h#RvI{FI(NSjUs13!*CyJrBy zvhiM6hpzkD1(ag};SS|1B9N~LwOj^bsg}Bd)KyE%fwZia+JO`Yl2v+{fz(t>Lr^J9 z_RhM&XOI8cFf7pI+T@t71)EK@P5%fE6ccY?v*hC>WZjkczCz!Z2XM*_$EuMCcXEq$ zxNhA)GT@%!8-XL?rX#^Ol7xn)22roSGJvXKlznW8z$nlM=_EP>2bRe~>`oSxq2xNM zM>8l4T}Gh9K`o=F9;WU212!Ep&j0E}QW=lGk5n+HT1-u9`JAeItI6tF6PaqFRa`@5`+b_>j zF1lFE_nByNqbR}2?g8Th0@vP2>QUzv?{apb$!T#T$)xt>BbbUvfU&}Ix-pp_ICn!rP0O9UzvxnNrFzT zL^Wf`BMU#0U9%c)I`+VJv)Xylfglb zu3e1$%yH(jHKD2)-#yxmSLI`T34D)?fjc%w)9L6IxXEv_); zseM~3UAMwo1Us@8y#{L#q$>!&sid(yxM%`iirFeebslSn^VQ-C(6 zj)qTsqt7m1rEUYEH8@A`Hm!)&SgzHu`sQm85%lh^{!Gv~c*E$ui9k-%!~o90FFbRc zgoxgOMk31MIOoPo1tO~D?TT8Gd4m@E%bTt@sX~6esl3mch;o}-+^zrz=Jm$>QC;36uM|X)HW6F9=f9gFjPe1)GzoK9k_3UH6$^hVe)_|Wx zm;f?1=YjguZvq9$I!s@N-bs@Q^S+DmGF3E!WtgBQQ7H87P3}f_CyKfje(j8(cBV6+;70d~AVc33hWYgshAleiQiy@j+f6wF0gnss zgSU3#9xCzMN=feG9!r+)n?Z5kat_-`?jtk2IU{lEd306Gl;qqee#CI?h}55`%~>l?|y6iDBJ9*^CLyu zeLoC-g|3rrHs(k3xqkF$YCOVy!jEvD@FU!xYCpQ>t?{F5v!~9F6m6dhKgu>6^P@v4 zKe`w)ey&X~u@mu4nQt&8@e_@25Q^i+8Q-*=oha7l^NSx4Jkh=`{BuSwasmVHyjNAjm@;NVGT+8!G;% zx6n)!P60uO?2+&-A3d_xa4_@`-3)X-510Va?3p;lm-EidX-=Dwk_D%2*yf^C?Cr-R~ z;=?ke>1!{I!dq96YlrxY=fD5*6CXMH!Y?NN{EJte^XlI{ z`K8e5d>H1oGh@44mmn!pBWKP@g7vF7Uahn-i>_4;4`{P>ryI{Mv9*S?CJ9vx}j^+Eg5FPP&)=l|%6qD4Am zo7<6JyzK9nZ#*=2+drMyxpv`y&;6s9tv{JsIcMihKl<5i8=m;@-J}lZ2-}v!wUTger<#UI;!)#-0KK6qV&yAx~+%&SIU=OpeG@NqC?lO9Q z!&z>Hx2G?s892?rX$DR+aGHU)lz~sZVmx-ucW-;nd`q?X&1n3o{{dIuyX`rief(yq zd|J!X44h`**NFkdCJ<1-AFVrR)D6}$pWfoEr&Rnc^NJ++uKq+k3&9ThL!iNlK3{yN sBX`cv-#st)&dG(D0|)o-S(uU6-n)Nc@4@|hXXfAW(M`yE^fE{MKk84CFaQ7m literal 0 HcmV?d00001 diff --git a/tests/groth16.rs b/tests/groth16.rs index c7eadd7..f4873ef 100644 --- a/tests/groth16.rs +++ b/tests/groth16.rs @@ -58,3 +58,35 @@ fn groth16_proof_wrong_input() { builder.build().unwrap_err(); } + +#[test] +#[cfg(feature = "circom-2")] +fn groth16_proof_circom2() -> Result<()> { + let cfg = CircomConfig::::new( + "./test-vectors/circom2_multiplier2.wasm", + "./test-vectors/circom2_multiplier2.r1cs", + )?; + let mut builder = CircomBuilder::new(cfg); + builder.push_input("a", 3); + builder.push_input("b", 11); + + // create an empty instance for setting it up + let circom = builder.setup(); + + let mut rng = thread_rng(); + let params = generate_random_parameters::(circom, &mut rng)?; + + let circom = builder.build()?; + + let inputs = circom.get_public_inputs().unwrap(); + + let proof = prove(circom, ¶ms, &mut rng)?; + + let pvk = prepare_verifying_key(¶ms.vk); + + let verified = verify_proof(&pvk, &proof, &inputs)?; + + assert!(verified); + + Ok(()) +}