From 6a7e8d0fb0d0e34783c279e3feeeafb1acf4d608 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 13 Aug 2022 12:26:04 -0700 Subject: [PATCH 01/95] Trie related segments and metadata Summary of the design: - Tries are stored as immutable, copy-on-write trees - All trie data is stored in the `TrieData` segment. Since it's immutable, data is never modified/deleted, new versions are just appended at the end. - In order to support reverts, each context stores a pointer to the initial state trie version, plus the initial version of each storage trie. One variation which may be worth considering is storing the whole state trie as one big trie (with sub-tries for storage). Reverts would then be simpler - we'd replace a single pointer. I thought that approach might make hashing the trie a bit more complex, as the node associated with an account would be a special type of node, rather than just another leaf. Either approach seems reasonable though. --- evm/src/cpu/kernel/context_metadata.rs | 7 +++++- evm/src/cpu/kernel/global_metadata.rs | 30 ++++++++++++++++++++++++-- evm/src/memory/segments.rs | 28 ++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/evm/src/cpu/kernel/context_metadata.rs b/evm/src/cpu/kernel/context_metadata.rs index 26bd541f..17945d98 100644 --- a/evm/src/cpu/kernel/context_metadata.rs +++ b/evm/src/cpu/kernel/context_metadata.rs @@ -20,10 +20,13 @@ pub(crate) enum ContextMetadata { /// Whether this context was created by `STATICCALL`, in which case state changes are /// prohibited. Static = 8, + /// Pointer to the initial version of the state trie, at the creation of this context. Used when + /// we need to revert a context. See also `StorageTrieCheckpointPointers`. + StateTrieCheckpointPointer = 9, } impl ContextMetadata { - pub(crate) const COUNT: usize = 9; + pub(crate) const COUNT: usize = 10; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -36,6 +39,7 @@ impl ContextMetadata { Self::Caller, Self::CallValue, Self::Static, + Self::StateTrieCheckpointPointer, ] } @@ -51,6 +55,7 @@ impl ContextMetadata { ContextMetadata::Caller => "CTX_METADATA_CALLER", ContextMetadata::CallValue => "CTX_METADATA_CALL_VALUE", ContextMetadata::Static => "CTX_METADATA_STATIC", + ContextMetadata::StateTrieCheckpointPointer => "CTX_METADATA_STATE_TRIE_CHECKPOINT_PTR", } } } diff --git a/evm/src/cpu/kernel/global_metadata.rs b/evm/src/cpu/kernel/global_metadata.rs index 6343a2e6..6378cd74 100644 --- a/evm/src/cpu/kernel/global_metadata.rs +++ b/evm/src/cpu/kernel/global_metadata.rs @@ -9,13 +9,34 @@ pub(crate) enum GlobalMetadata { Origin = 1, /// The size of active memory, in bytes. MemorySize = 2, + /// The size of the `TrieData` segment, in bytes. In other words, the next address available for + /// appending additional trie data. + TrieDataSize = 3, + /// A pointer to the root of the state trie within the `TrieData` buffer. + StateTrieRoot = 4, + /// A pointer to the root of the transaction trie within the `TrieData` buffer. + TransactionTrieRoot = 5, + /// A pointer to the root of the receipt trie within the `TrieData` buffer. + ReceiptTrieRoot = 6, + /// The number of storage tries involved in this transaction. I.e. the number of values in + /// `StorageTrieAddresses`, `StorageTriePointers` and `StorageTrieCheckpointPointers`. + NumStorageTries = 7, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 3; + pub(crate) const COUNT: usize = 8; pub(crate) fn all() -> [Self; Self::COUNT] { - [Self::LargestContext, Self::Origin, Self::MemorySize] + [ + Self::LargestContext, + Self::Origin, + Self::MemorySize, + Self::TrieDataSize, + Self::StateTrieRoot, + Self::TransactionTrieRoot, + Self::ReceiptTrieRoot, + Self::NumStorageTries, + ] } /// The variable name that gets passed into kernel assembly code. @@ -24,6 +45,11 @@ impl GlobalMetadata { GlobalMetadata::LargestContext => "GLOBAL_METADATA_LARGEST_CONTEXT", GlobalMetadata::Origin => "GLOBAL_METADATA_ORIGIN", GlobalMetadata::MemorySize => "GLOBAL_METADATA_MEMORY_SIZE", + GlobalMetadata::TrieDataSize => "GLOBAL_METADATA_TRIE_DATA_SIZE", + GlobalMetadata::StateTrieRoot => "GLOBAL_METADATA_STATE_TRIE_ROOT", + GlobalMetadata::TransactionTrieRoot => "GLOBAL_METADATA_TXN_TRIE_ROOT", + GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT", + GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES", } } } diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index 48f9136b..0a0b6245 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -22,12 +22,24 @@ pub(crate) enum Segment { TxnFields = 8, /// Contains the data field of a transaction. TxnData = 9, - /// Raw RLP data. + /// A buffer used to hold raw RLP data. RlpRaw = 10, + /// Contains all trie data. Tries are stored as immutable, copy-on-write trees, so this is an + /// append-only buffer. It is owned by the kernel, so it only lives on context 0. + TrieData = 11, + /// The account address associated with the `i`th storage trie. Only lives on context 0. + StorageTrieAddresses = 12, + /// A pointer to the `i`th storage trie within the `TrieData` buffer. Only lives on context 0. + StorageTriePointers = 13, + /// Like `StorageTriePointers`, except that these pointers correspond to the version of each + /// trie at the creation of a given context. This lets us easily revert a context by replacing + /// `StorageTriePointers` with `StorageTrieCheckpointPointers`. + /// See also `StateTrieCheckpointPointer`. + StorageTrieCheckpointPointers = 14, } impl Segment { - pub(crate) const COUNT: usize = 11; + pub(crate) const COUNT: usize = 15; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -42,6 +54,10 @@ impl Segment { Self::TxnFields, Self::TxnData, Self::RlpRaw, + Self::TrieData, + Self::StorageTrieAddresses, + Self::StorageTriePointers, + Self::StorageTrieCheckpointPointers, ] } @@ -59,6 +75,10 @@ impl Segment { Segment::TxnFields => "SEGMENT_NORMALIZED_TXN", Segment::TxnData => "SEGMENT_TXN_DATA", Segment::RlpRaw => "SEGMENT_RLP_RAW", + Segment::TrieData => "SEGMENT_TRIE_DATA", + Segment::StorageTrieAddresses => "SEGMENT_STORAGE_TRIE_ADDRS", + Segment::StorageTriePointers => "SEGMENT_STORAGE_TRIE_PTRS", + Segment::StorageTrieCheckpointPointers => "SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS", } } @@ -76,6 +96,10 @@ impl Segment { Segment::TxnFields => 256, Segment::TxnData => 256, Segment::RlpRaw => 8, + Segment::TrieData => 256, + Segment::StorageTrieAddresses => 160, + Segment::StorageTriePointers => 32, + Segment::StorageTrieCheckpointPointers => 32, } } } From bfe86d70f5c77b385e9fa3b370ed6d6f1511c0cb Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 17 Aug 2022 12:20:23 -0700 Subject: [PATCH 02/95] Start with PC=route_txn instead of 0 --- evm/src/all_stark.rs | 3 ++- evm/src/cpu/control_flow.rs | 32 ++++++++++---------------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 4b8c7d0a..e2a11ba2 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -261,7 +261,8 @@ mod tests { [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; - row.program_counter = F::from_canonical_usize(i); + // Since these are the first cycle rows, we must start with PC=route_txn then increment. + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"] + i); row.opcode = [ (logic::columns::IS_AND, 0x16), (logic::columns::IS_OR, 0x17), diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index a157653f..e6ded598 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -69,20 +69,14 @@ pub fn eval_packed_generic( ); // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU - // cycle row is 0 and it is in kernel mode. - yield_constr - .constraint_transition((lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * nv.program_counter); + // cycle row is route_txn (the entry point of our kernel) and it is in kernel mode. + let pc_diff = + nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["route_txn"]); + yield_constr.constraint_transition((lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * pc_diff); yield_constr.constraint_transition( (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * (nv.is_kernel_mode - P::ONES), ); - // The first row has nowhere to continue execution from, so if it's a cycle row, then its - // `program_counter` must be 0. - // NB: I know the first few rows will be used for initialization and will not be CPU cycle rows. - // Once that's done, then this constraint can be removed. Until then, it is needed to ensure - // that execution starts at 0 and not at any arbitrary offset. - yield_constr.constraint_first_row(lv.is_cpu_cycle * lv.program_counter); - // The last row must be a CPU cycle row. yield_constr.constraint_last_row(lv.is_cpu_cycle - P::ONES); // Also, the last row's `program_counter` must be inside the `halt` infinite loop. Note that @@ -122,25 +116,19 @@ pub fn eval_ext_circuit, const D: usize>( } // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU - // cycle row is 0 and it is in kernel mode. + // cycle row is route_txn (the entry point of our kernel) and it is in kernel mode. { let filter = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); - let pc_constr = builder.mul_extension(filter, nv.program_counter); + let route_txn = builder.constant_extension(F::Extension::from_canonical_usize( + KERNEL.global_labels["route_txn"], + )); + let pc_diff = builder.sub_extension(nv.program_counter, route_txn); + let pc_constr = builder.mul_extension(filter, pc_diff); yield_constr.constraint_transition(builder, pc_constr); let kernel_constr = builder.mul_sub_extension(filter, nv.is_kernel_mode, filter); yield_constr.constraint_transition(builder, kernel_constr); } - // The first row has nowhere to continue execution from, so if it's a cycle row, then its - // `program_counter` must be 0. - // NB: I know the first few rows will be used for initialization and will not be CPU cycle rows. - // Once that's done, then this constraint can be removed. Until then, it is needed to ensure - // that execution starts at 0 and not at any arbitrary offset. - { - let constr = builder.mul_extension(lv.is_cpu_cycle, lv.program_counter); - yield_constr.constraint_first_row(builder, constr); - } - // The last row must be a CPU cycle row. { let one = builder.one_extension(); From e3d131b99d32e0c6b5d0b16dfb1ec241f5cf2f94 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 17 Aug 2022 16:29:30 -0700 Subject: [PATCH 03/95] Update paper --- plonky2/plonky2.pdf | Bin 214900 -> 235072 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/plonky2/plonky2.pdf b/plonky2/plonky2.pdf index 349b22a6a5792c7c60dca48ba5874159d95b16af..8e99f30127a0507f5a82e18dd6bb35a2be2bbb00 100644 GIT binary patch delta 158693 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

@7u~{H!~{ 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 z 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 Cp?5<7 delta 138712 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

Cxz{-%u{X|4w!JtO7Uhi@f zUm8yMIN6Tlpjq~bo{0Aj2}(N9s>4rA-o^R{a1y1SV3`==r7QV`MNYgQ^r6gvv+FF) zum7cKy94{hxV29JF54Ydufc3oUXAQX!QVG9;6`TS;MTCmYtxrIYYTsEC5)My&!FNJ z{j=}ba{nM=cJ79nq~5Z9DzX~FOk#zHmh^g@_wYICLs*tW0zJVZT;dfFn<%)p=rhU2}j*K=el8!A}RaDwjXe8)1MkQyUYX~HU0B3f&s zxR{zb%C?|JrA{B^N1Md5aH-MyRU4{LcwY=G_xA?L3pw#nqT`@K&h`=UnW60nOmG8c!#Ej|+6o2sX513Ec@hv@X9_|Q#avo4 z>~mt0N{Pnb#82adfj&y=qaM6m9E^-vCSoULW?|tDxt7WI44K@GU5*=Nyb!)@2{Px; zjnEfdXFU+1vZhs7sw;^>t2x@VS`CZ(Ap?nZ_M+frv@29=p`2#z>j}twC_5I0ecy;E ztoG1Tlgy8u(<}glhN@c?U3dS^HuubjrTK|vxEz@z7fOsE>`=6P{SphPxFlZN);!@^ zHzv~TiH$vzD9B0DW01E&!zoI`l-?)fJCnAMcRtnsDobBr@U6#B50#I^Lng0Ow@)1B z(11xAn4d_y0oa?6@xi+VL}l2TyugNe@>8 zyH&N+tms}Wj&xD<;f)tr8sBxme1*n=QcxI+d@wZ7{(KLD&IZ=T44$ZFimAzPl*_Qq z)q7v70uA_qWwC@H^osqoU5vKU;WvjwRN+>T8b<>=pxpO$4yo5Ob2^jM&^6DIjW%B< zhnkGc^wtOkRj!D3@{AeghySmj{5Jt^^e?fWRp2{5$$0_4aMP?W# z(q-D{d4p&R<%u2?BUS521}sqw@(z3O1YWg$3iN@qIT?z{tVi!Nb!t7d8D@76RVlkk4YZafBEq6DmQ+~_gJ-Xu z?~2wWjJ9GfBB9rpQ#{0Nb5Ck={9^DeOh&vGqy`AG!#QI`mzBIVW)B_7Ad-chGZ$^1 zrIXr^AAAbdOrezh5<=rRlk)O0YjUe~IAiFabE)Oy67@*^03|85{k<$#mWV~~J5l_QCtg@B$d3GMYGb$n7x zp039D%Waqpb?E(X$O|JqYxqCN-}zMk=11aXkzcJunI^(O2{Hzso7HVvffZgI$da1^ zLFUD9ltLt$ZgBaD?qKO!>2GYoKA#(sBTVu~JsijSdy}(LMiwTOTjge6sFoo!R7$oY zbst|E(W!2s{;f;A8R+PrpyE<+$}hN8nZ;c3!!kSdB`dx;fXAB7QK@G?je2irvy7N~ znnW*}Dx6QqGB@>(79a_^1jwy1^r)?LeWH#t8Td5CPfA zuCfJc5XOgYEH6{vJM-RZbNbV?3HOA?$~*ogC6h?xrgs=fk$Nxs#zj4sp)W7!68hgf zC#XK7ktWS^vj|APhXvPwlX9>dF8zc({I)tXmk9mFqDyL%)sJbXuJ&J$h2Vb%BqQb zkYIt?cA{TkVVEjQkk?7xoA84*3D1%00>46iip4v@WC6XT_K79e(zzH|5b+*0umQZ_ zUr4~ws=(0Tw!t7?K$0Q+h+vco`u2=qvUXrzv`|QW`6&VX-n?ttI(?)~FH0ERMw4)0 zH+Ofn@9?nUd;vN3;|gS8RBFv!%RA1}u4+C3F!Sp$NKemISV&(Notg{(A&^;Fgjr>f z(a5KERKWN!gi~FhF64XJGOHDM<|21>*ExZ*Tys_#2i~<+ zbrTz`xKSKBGa6zw$F{R;Kv*`p=m0v>OH{=(ZXopa(+ZfVT|Q9yCh+P(PB@%rq~_}S zJGhXpzYoR)y1Kpw7E-+kdu9952`7NAf0t)w9w347i{^3TYN+Ai@}Yb$TgUpxjKZ+` z*I;5N##aE9NSXRZsx7E6|>9M>vl;5)JPQYKs7pnJ75l9NLgq|X~52*z}m8O>p+TC zz{w8W=N{Atw}|`p#Q~ffl(sN8_+w)Yl*p&CXOFyo4_I>@;_}*AUMxro?+aRY?;sHh zE2QpAJEDj|XI+fIHuANK&lj-kr>iZ7K4~ul;^#R(Gp3mP08u6tK?^ysAPEIue*oXH=R1eOQ2dwykQ9&+#IloXw(8}Xj^7mm@B z$Sh2Coml?$5L&nC)FznRZw_Ga%J%R8ta|K|`$O#g9nAd>pg@Z!>~^?A9fmY&`heYs zu3r0qyaivr06~&A^rO7M`mPV%Y{l=f)$EC|2RDPAPl5vC9F~FMxVLV*3L<%s_U9iT zS6s`Zp}iN1)uF-{+|N6hz@AGZ*XRMh-z`S*_V&tWZVh;4glCWGXuzoRXah6snRX{S zLA#dCsHc!31%;o?FLm!4SW0`dwZDv)KXO|5fx`xkr}E9PZk~}UdH8@rZvlvFepP~|CI@QYE1qx6e=)G6{VOTeK0`6p?AoXJUDOu zcWdLx&f*Q;R(Q3@QOPBaw)jV)BX3I^#7knG#qY0`zc&jx=sZfL3VywjDN6R<^MdMv2i66)4^uw5rkqunDG!@rX@z>n;xFj0%o zHO5lW zY9@j=&rt4+SCeh>b9|rEAdIY55D2i%QRB?F2!?x3AFO1kU)B2VK#D4_L5YlXDn{Db z|55Kv^s_6F+>GGqMp5HS<>#b4C+0{XLEvYm6L4p@;?gc$8-&pA)}t`uX(N759wA}y z34Z&n|0d)#DU2BD61%k zL3yf;K8?L#tswGgpXPw%Rw;>Uuz(w93tSdthIQC@9TbXX%WrAkg^Wr+G$Ay;7F+r& z9-x^QD@c|A@{EPJ5mobt;L11G%0@c-!hziQ{q`IZ55J*2q(Q&kAAIezM`Ry^Wwz*T zRSfteo7{M&AeS~cGGV`;jA1kGr{ZKtI?J8EGwXRH{`He=JlLTJR9s8-u@oXU&J?0n z5+J_m(A<4;@SRl72)>8rnW4XYnMhOXc>r>0aZ=y5BZH-!g}59hlS?;=ZQAWw-9*Q( zb(vk}a^n&_ur!RTUJm6%BUZeJz#qU~)x_TT6m49jL!A;i_hm2;kE5>fz+hXnN^SCn z*K0Q>ru}|E#sG7JI`U2m?q=1R5mZB*NX}+ejZ{|W~|4vD+8tE z*1S!cKRKO14)ouiMP+iX@plCup~7c+>F!38C9Q8fxEzlvVlAo_|o754yDI#wCmFTE3` zjuGwxyas8(teSpB6-2N!rA9|2e1PE36nq*eUV$!a&2V=AeKiKx57Qw06Xh{kmyxE& z0ocf^2M7KE`>G0hhu}YRk?jiQ{!n%$TDo&+C707hgI1%DwYxKjZo2q#XWj_BcKR`T z8x3kh?IT}stZsWe?UfnbTHM(5Ef(V0;!@*q6bz%fxLqrOwbG@AIDf3S)B;G2O@{bAtLPpDmy zpQ>RlI^UW7CoV}*mXCa=Zb;JS;OXED5@jWmbFwc-etx0pO% z#Wf`}bMaHYaj`Uwir;rRwEj$CP<{;H@T&?%6r{R*Ykkc(i{0#)nZvlpH~V4uU`OIH zSP(5Acg*Bx^sZo(0ZdQ13av9j&=IBhlCf!K(llJ1z-krnJF6qXMLMqbA5iAnK|gDD zxkc^jR?W#~T@DM6Y2j9)n`2JhGW)Bd4I~jBlXkNum*N+xuD?Zb1~i{QjXBXM>b4l# zMJYO1goqRWaBm90@2DsLz8=WWm@6sI1=1&<I=ao}`xsU92 zPLE(?bq?`KNvSTD0#kXjR4u#E%k1a!24|zt_;Smb;*AS|w7)J~o;@qX;W2Fc5?-mk z%y)X99J{yq96(n~T8UlA12u*Hyg(GM7#9a2&$>$w5* zH7`+t16!(X#blx#ud$WHK$cQ5GN^>X!DIIH<;w{J@|Fh#R5b$o*%nWnpzX=pg(w(Q z9H{v<8#6SoWl6S7c7@x46dA%>#-kVY*bViT!&KKDJKnn}IsWLSWU*-n*R{_WNd&!i zp4V{Y1i&>dC=IDBA>MaduuxdWyTgoDoA%ld?Fl;{un0@!%jo5fm)N+_mPpua^{`{A zvxH?*R79nk;3yx5kAU_--#g8HZ%R)q=InHSs@s_J!O0NbrF7+oIx1Lb!HcOBag)@TQEq_NZ)wQR z8USqHzzsjH|1*>A&+dr?*=cd}QigQzs2vBQ=COZ9r@5B94~eHDF~NGkPv@U-T{@(E zn>rk2y3Jh$&3@oMOWI~5I0lQM2rmxy2Ey;k*@%2eyRnN$k-tjBK29cNwp5nYX9_tN zzaL||Wd1bca<$G)C}`Bu>jH(@e{_HmVgt@z%5wUF0x&uuIsR*26RQwROudMv*3vKJ z3roerq_F2>)#7O%OPw<;59qr6yX=6U8m`_CLju|_o?+7hj0LwNNj|V31ZndibZWWz zCwcqc1f#bTmcl=|)Whi}#8h9!Vxd(^X)=@{j{f}hKs;;@6j^n*&NKaH)ebMB!w3YU z$@z*w#$mfp_q1Gc(_yz9+p-;6x}bgFYGCQ}qPfA7N?;n~S21@%1(t5=L< zEx5j5F<6Z3YAa8FIPZ(_Qa|M(K=>Z2hLj9;%{4lP@V3=4)!*Q`I(W*T^bR%d-f+UjYmF1~fXUfZEB@{9!Q|L$7O<6F#xS-z3wWih71T z39^dgR0d77WfQByj>vw;6t(6)X+N_3=`wTi&MF`Gv_$H}Q|}o*bw*aQcM?fcbdR-X z9%)-HppvQN*1QSap2*P5_k$_}e&@tc+Qbtg6DoCN`w%eShOaPmp$8g|^Z=nF`H4%F zu=l7oP(QcC$7c@HIb$w-`8s=1%hS3S>K*;;&4X{t1WkIPL{qgdWpcA=bk7N_fX$uGbeH z_1}PuL9+2t&rNPhJxe2mCIy_0I}?dr?EBU5!A_^3BWTW>i=)KnUR$bl;E|%zaOX)K z*GucZX{JzfukEftKxge{_)TBr?s%$uIsCbgHjeXit^{`oVsyN>)G4Nk)>y(q=!M0` z1}gyP{I0KLEC zP$`l)nBYMfA+O?QgSRu8vt=EYpXa8^1RvP{- z^1KA4Neo;ES2mZOZ58k`zU{pFsP?4YC%U9f{R^r(H6+Q(5o_bK1Zs^~NPJ{`ixlTVta(0zLO<60920+E|xvLV&ph;aT{r>eD&b{`Kl_ z%7zBi6C9?$RNW`-${*u)B`&((@4u|(C@THybJZ@Agk4901(*K>}Eywp8j8-0*4S_D(wD==pub_Ix2bEtv!KS; z$9kFmIVBRfKTCZ1DqNS$v>RHiNsNqBlHe1U)+hWmM`pM!d`)ZK=kwLDQAbMYa^cdy zY{^0^nuSaFqz?gjLhSnsWj6h)24#(uP71N4Yv$tWv7B1s0oB#s$uG-v<%h%Si zNMw$uAKkTz&}A-9pqeVhdRRT-3~?uh)(n4VGwf0?)z`l21S7K+lc^|l1V24JeB`BI z;aeT`sK5BCub72}YUd~NE|1q3Y`-l7@2`)7N4C{rHv4vf~qPh#8ghCMpQdXJoV?qzqM*S<|h_oWQ7{TWGUaY-y~_ zaDFdWgr7n?+zd+^duDoF8BjbZqQ{${Q)>62;aIm2hbE5FrI=LeQnF$Yqlfg=|cd@H&$I*`NqLdcxo4G&8%;)vOQYoKTZ(D_?xJQt@T zLgNHbP&wkWk0KRO5=Q$O26s463Eo^4s913{kRRMl&gCOyRbfgQq zS>1bue57B#aDmrbLE;!rEpf>r%GFDIKKX}QdM2!vh8$9YtjEolO0RupyA1vH{HL zycCX_vJ3C3PHDb7#*D}z(nNiMuGKQTpFE#j=+=SaQa@R`CS?A)3iG9^1rhVoTur0w zw)zj}Xu(`Z9unq$1P5I&p`0db-yb$%7t9{c_EHXPs8zVCEG?6Y3!7k$T~kh#gDMuc zl(>{y?{)t0K2o>9| zdcs&FUT<=rYdU+;ruTz;`WDuh5jGh680`pmG?svPN6u#5uq@V?UM0qI_^)@tN^L(X zheb`NN1ZXdoMkc4DZTHjnwtU4tS%OPsROe+hXnQtZ~>j78Hvg)gMp9#_o%_U zAcx(9-bEugQQYNbUf61f&sahw*rC!*GX~;He=7`8z59{Mc=AeSF#aXN0 z2LAAKY(7fd+3(l=m;1YjVNDZ2{(4P^J!{il)dv%IUG#A{2MWt7DysVCYYsI=VFJu3 zC4(eu|HNA^G>5!li_Ymm2-L3E0(J?BMLj=4b5EV^;VrD?8CONZ`i$dlW7^@$jF9y8 z@74-9GQuxIY|X4utw}ogrBSRe?$C@C-kC@8_XqM00gJ)MYca=|AsBFgt&W?R`BWH; z)`W@HUbNQSvfhut%{U&3kPUk2MKNeqDnt8%+DBxkh>Yw0jlKlRLuz5=v}o36d6llq zGfMopf!6S=%Q-Gug7iCWHA36{O^|_&@71@`$qnqkWFnSO--y>TtzCqb=IytfGiYLm z{hA_ENSZPd#?m8}(!l5eOLUf9q25&Za+Qpf^PyA-ySSBx7-YivnBP#SSUIQY(h&x_ z;;I57r=rxX$k5~`SxgzClNlpmavNaf^?awuR7h%34}&h2LwBSR5Hj5yln|+9H=9i|n3)aI^=#yNl%owuGdXF0${h;gfCA(_U&RKjFL(6pP8@j$h-C z+{Ydl+tz~qYfjK+)r4&fd?)4Sf_&4EbY8-7s6uwzvK!s7nlnK?K1zb|wCcW5Mn3Zc zM96j$O1%#2zc~*Ww09Slvxvl;5~v2F25T_H+)GWbVDlzaM zijVZLoR#Yn@8D-H(OJGc)u%hxj?0~78W$cH6u4zV=7XVf5MjI00k9Pz#J* zPFm~A9|lNRh_}5UPvf$f!t_6z^T)X(K{(`9Pmein12uClZ|eH0Y<%8}-xwVXA5zU6 z9o7F;3v9T7d^i03iY%>#TDRRrAVwv++B8L_gxFS42oY>x((`;4*N5 zrU2^4gb|n_slUSGwUu|5>_GxKuXmG*+KPG*R695bYGIO)C3em82|OZcN@c_k(c^30 zd%P#DH}2BDnAx06C~0Y4`*kz@7x%a)3nh=$F0hP%-cGc(2w&U0wsUnsFq#y*iS+$V zI2w+_f{yvobc7%`IpnDQC%tYvD!X2o>D~EJY`AG*jIBC=-)|bRx3q0sbUuEG57TbI z$hc0Gu4Fh`5p>@i9LwjJ*plRU9_8NS(2}|lV)*d%>#lR}W>`HI9Gq)>(W|Q=L!Fp~ zVL%J;3|H|J_!D`InPI-kjA5Agx22;DOA*y)!pRG|Q~>)OF-8z24#l>Fyo<|-F~>Yh z8;*WX&U7@V#-lduO}G$vl3C?F7K}+dvuke0Zo7C{f{KacwTG?OUWA}}MQTDGmM@fb z==-rzCHkkkkM3zcue9bJo1A&9IR>6CRUHoS8+|8~dO7FQWFhJpHp4#&Wha@+JyItkAwHuxp48wb;7+&S9IBB+?Zqhb;4khLL#TFc^g zg4>v?Ps!Ap$z$7&myBH5*;^Mf4maH}uh^@TX4jrXXw`e?P_U#-a_#L?$DI`JLn z!%{6|&VcE;i|u%gr53l`nXZv(2GSWop42O{pYRB>LCPL??W|zEtTlZL5znkg;4B#; z4B`4Viu)vw(r9saO)XV<(n@;R0k=5gI2hYWCT{Ga!9YZ3cXfWgQFK^xmmq6l}N0*S1s&tkP&zL|INj6EvG`TL@-+4Bp= z)Sac`cjSvvWx0w%gwL8$%vm7JeUy1Gjh+w2-aqO#$7TEc4xBfcr<)1!XWed=)cB9@S>L$me|41ZAmn0&Q#iYVjHA{;wn^*i{Y2n`kSZ7hJNno;F_p` z4?X>qS5Nc{rDj=oKb-(-$^1ZQ81e4wmfx-F?ELyjNfwXtF)%(=`Y9$ga0J1abSAs# zoPQDw4$tbsgHw!ADru64so@ExvZ-1s1*T8&q20y&OoRa@07fDVd z=Agwe91S*Y_>$|o6#hI>FK`g*#5dty;a%9YiIt>hr2|Vfe6?4| zA6rt0l3)?OF(liEIpSLWdsq{9$TZkdX_$$Xz-X>~yp&H{GyTGdPcNY+cUwUr1C=K( z1`BrRQ%mxZ!^5xRuYb`l;LENItrj1!r#_HBUfp76_3c-m=6X)1??HrxoI%Q^iF%eL zoqmHuLauvP^41yHhrS4%}4F(vy6 z0a-tvS6{p_kOrH4hF|lTkwe*5Yv^$y5Ii)lO*D!n2kQYuv+Yu2`yf_e^L51Sxt?%? z$%#b$xpl%Dm2)=W-Ztpt@2PdSF-`=v{Ebb0_BXyOe~UoHFy)=s@jq0{oUp)0bucfs zoJqNsk(P5hI|}dHv@wmytzmd-!0En|`a=#QACC zvbko&W5)%+(A_X-!gXVr@Y!YFfBbk;%pAUkq{xA&JfV{jpCjOP->7q)PT0$fjq!ix z!njjqXh9au|8PhEQG}53hV_)0)((CB@x73``2FQU=$5s&FHn4~=_(#~(!|*hR>*#H zXL7vy=vX$z15O1j#=qh!+*d?CPSCfC6gM0>`SBo?Y&7=}JF)LT$5S$z_ z;B3&)ck5#F*<-9&;!*~f^7~ZIoLNO>0l(qMDi3iFexP2sd4$)dk#k02mYRNr5wv0Q z)EWB<;6$3vz->MF9j!gQKoBoPnYsLXZ3>Z=BNH{k#iX*E8D^btlA*HI@mojay5-&; zt`}YNnSp07M9$8pDevicBD^dd^cs+o3QhJCSNadQncTs^3;fMPS~G&UurQUW=sGCO zhKhY2-y|4LgfGB*aGc!6hwa5yNfprdT~!AF60G^|`XOrd);YP;yO>lIU3;d8e;p_T zo+ibs)i5g)L<_{G<*deV0t*Qd*G(+3GqB=I>lYv0$=6jL6;55v1Sk_V4i#vB8E7N>yhyrZdfFMV0(Wg0( z&WK+=`+cMo|2Yn_Z`lUo2BMvpL~@;>F{dgYvxCBz_BpFvu8Q(?1Q%&oQ5@m-^68VY|q zfU#2>v>^Iam2rL*xur*#Q0*a$v|*Nf45RL?qG0Y1YDW2&UW-YZ+-H8XRWgR(8Y4U8b5gNDp$ zL*@jGHrqR9V$81r0P8AJ%&Ta9M0+hiw{BU+TiJ<+sV7?A;caKqtZK=hc2IUJwX+nn zR!lj)B^#to@F4zvDgGd82-U#5hWmYDhnyMlFY5yXsFhwV^6`%KU@P)+;Uf9iL*#sc z5Hak}OaFES?vn}nOR5~7@^5QEW5bddf<8a@7R$Cj8Cil!gi^}`r#zYy^=kY*MBV0h zUeCiXEG>z5#e;81HZ&)f^L05WtkSV-wQ*S@jyzT0Y2#70BN*5Whin+JJYgR2zsUtc zK`KR-i9U}a(o@|KkHD7iQ|!03^1t}Mels@2Y5RH8dctX*bHc#=bGYFTU~x1Uq9=%4 zJWw8+mOD>YJeFNJk?zBO%|Dt!#xpbEg=>c_AO*Y@WE%gnoUrq{?U*Yi_ixtz=@vVi z`=k@e9glLSuk1LDbj&QqPy@jLKkr2@*`Lm&Y*L%4oA3gFkwppR{(q;Xc>gEdo(7`< ziGsn-`hSA$Y`mQRQ*6hE;^X1u{*PFn28eEiRwlA(Wr~FLK%H%d_V{;|Ium$!K){o% zZ;&;^B40Z6Ff|M4J)ZL=2_1Em`$W+nKa9M-JJNnjRzC@yI9CY3c>IFvZ2?0~67ahn zfdexjA|fR)A_6)$=W4(*aQ_z;9p(TtI~NnR^h8kcfmNbm_{YOOv4ZZ< zMeSuFjSdeF-|tyG0;-9pM@HxIpyGMlYoJy;q&c}7p?|MteFxVU1Vsg9PR$Ms1pZ8z z`6t!yS)R=3*&O*AaERHxF*<+&?sw8wzy~C0 z2K|~=S5sEjkrT`|JcnQg&e~K94S_|qkEbW7Ck!4fW&z+6tnsgPy%7R!__`|uS?z@H z46ddDA=T{N&mG-B+L7I<*}?Yi__m$-n^r_LFw^d#Lu5v6<7f#B)xz5cO36!4!JNMD zxX6tLlonyku2612d03kuGBQ9KAsK%l3;BmfXRv9R9vzcB(BDZjpr@g`C;R*RMcxg`NxYN3;-3IeagKX1XeW#cR+T(cfjRWrSM&Uptd*F)exd>@9rbqKYW(% zcA*P$aCDE4&O;kP)K^3FfQ$iL$O+KkRi8)gE!Z)8#}x@jz)4je$afYnWa04n=d>ri z118c`Bo$@kz9(H?MS==3(QBT-Jm>1zz>js7Gr-0HCj2Ad4R#AuqQc(z>OlayPJzi- zgKP#(K6WU8l4{nva06bqhQuJB9hsruo+_aseuEfWNH7JGPTp7!K}Q;(v@y_D7---Y z(7gra#E1N;yy%*r2hD(jD0C?74LomW{o2cXV1tT^gSppN;{MX-V2+1Rx}1>S@3s-( z(MVr>(mZgxeY`rsTzzPsyQoKd;Ek|8PT>dL;4LIJyXa18Q7bv)SE8&Lpo@8nt z)x=*~aj=BD`{`+Y{U9#;LFBf}<5nc{2}%JJyu8`hDWc~N9D@iD3s*$8eZaPXZJ#9o zKM*u9G=6W8@N_>M{)oC}_}xdV6o~I2)P9HTEVX>0S$U$<_U6~_ z6qx}SH_buYmfllSucX%Feqyb0a}bUziXgV-uOt^g=guhOS?Sl_DUGo^tm zc>~eXF7c10gv?fcz79a?;|$-gYa7%9dR2vj5DF-{mb-f_X(wB3AiH=1xOz!9PCSNQ zENR`j)U1v>(z5yWNE1YS3(F?qzI(J{e{#fKxIFi=iP zy(YKI#*I34CmPrnV?f)Seq3_5&X2Sw-65lWeYl|+RP244rIr^5XjCODPPzMNk^UX1 z;uh@_WjY&=jAb!~c5>)2bJzE*l|tKMIH`&F`Y;b1sn z(&H}%f#_lJ-C1s9cmX^2N3Yp2dm39fvqC;6^=YYjlcV0$^x!YwO)*^ZxaVl zz3GnaLa5SnK4IJ$pfd&e2qA~C&I+;d^7ysKv4;r3ky<2iY6|h1QD-2Az4y24+(4Vi z(P!i@)x%0??s1qjwp>Sb6OQl{Z;Y`4#~Ky`qyFM_*)5L^aauF1otFFY=H!taUehXn1%b*`myh5e#CXk&fCNGznH8 z+<%E6kKo=ZSANl+gRH03uD0*wqaW{CO)o;kMxxTZ(Mv@0&b_iyw<4@c>K$gMT|kQblx-&ujVZ92r*i`6VwWVQZQ=`yzs!* zrj^x_n0Z4iDLn@=euTsr@n%9+zE$2uj)_Oa#dyVDM{b$#8X(x=Lr0`hG!(Ba$t!uoO;5D@t=1#u8@Py7uzJst@IPk*IDKdyRvI-k*kiva1>ERH+C7SsnK3vTRU1jSg zo3B0f$q))}R>|)7zgT#Tg#u^ZOL*oQgTKmTQ>FdDaQ+Z zFwdnnjrtTJ24+^csUQ+if@3Nn0EEz~5IDUDm9|&P-Y2 zjKbMOk}4t^KV{LD0{2@efa^hY<=nfC{JqI#swh0oZwW87X>6GCyFt7)2@6T#jBaaK zk_P_9sLrpr979l%V&1l2DT5}N)5;I z-FF>o#lz-CD!BGgFno*>CH5=84!B1h>F+&&&{b&CWQ^Qa34iwloI{|2kk$yQM{oq zNj#5NbrY{)Y|nQ!O#i^bYac_cg#BTXNksO@I{FDdFVP#`_)qMkRyr>ANAK@H<^GS- zHMycVEr|A`y!Vc<(^A^)i?#A4SHUl^8LHXA(}%D((_9%;g4BwZsEJG9I92Y>hJ=Eg zf{p~KGbQXY4&$Hl?C^cNs*bpCa(LMi%_2#rftd}G43Zh7Z+fHSA;Xul z&E3@YSiP2oX0|}LtA*!XoBtH`GK{RCYTp+|f0gD)Mu?-0Wkyb)%408n@VF`oKZ8>H zxcxVs@YIw>$K{7tWz$9roZDL~-_1Xv+Qi?K0x9ZNLXnD7Xk*0xZ;Z6y3dVjbGQxL9m{Um<~EpHS9 z)05V*B9ipB?h7Rcj+p2Q<(fjm>f&ne_=a4Fk|UmpuavWF+y|OVRVtr?WXDDTccBz? zGq_h6I*PQY7%;gUN+`p&JP!5@7{ir((PJitnQEZZ#la}a;r0*g{Tk|9|F^&VhNS;S z5Ea60FW!3NgV?Y?lXviIaAS#M+R5Y#T_^1uQGC9be<8G(n))LbKgmyNZ$k*2Y*gP( zwD|FtM8tb!9|=M*M*OCAPW~?dL_oX0H;ySRgQmYuyM|Uf8y)Lx)DL<_evgY0aBqkSU$f$ zXg=5Tu1FHiW1?zfkg|$gvoBpe@7-@o>5%!bKCyl3`n6fL}bC)>?uyD$>iM(7j zI5R??MmAbdUzd7dDStwC5zwvL;u=ekbnb5sOm9^Q@w=YQT$h8#=V^ zxz#L*f0FA{5iuZC5iz#?apX~Fn6sdr$y3WNUtA5fzIVEUc^3HrBf6~;rQxH0$rGb> zLFh*RQZ9+_FQdfMBQZ)%f`GxYoq1)g7IGM=_2PY$Q5Wfg%62&a!8wK{ZGsD-MG#iG zQxdA-^)YBQum={YQ}KD*MOXovUJePA6qPIpe{J{++rTh%#R4I8AnPbRc`3C%v7_vepUDV(cib< zdtCy9sScPT8A;OqcKjRcfn*k38g^EDf45{N8&8(vz_d`jy7`#sDV<+rX;fPOV}L3Q z^BsWfYNi#<5_wctIZV#7Oq+f;$WPVj)zg?AAg`n?Svoh#OT-QJl|Ub=bP7is3_B`R zh&>>FZGDOwAO1cgP5>dZH@Im?Ov*Yu4|Ura%LyyUV>JnWkD*6kd2+tGI(Nb*f4OSe zwvU%GshW&$e>tt@5*SNkKD8s^m2T8D&?K0*Eo}-Wq$)+3#P9)Ai2bOV8LVFYGM z1QF@ie=UR?VUXbA7aR=iA1BAdu%Ogb_NTbZA)AVhJ`$xn| zE?3W0a;M9rwX1Kg@aDdzj=gm3-JX6VFKha9nU5duyN;37KUIQvy5!L8S@B3+=5#8C z_N`D?83Gjp4+$kSI$s?pe_^l%DqIgX`sgKjA~rAjyn?er)1`FuC}Px4+!njQh>#|| zwheGMMnt}PQUoZdZ8bNt^5a%%9c>7<+hNMY-(%Cyu!h_bT5T^#39ZHknok73*fgs! z9Hrm9xKRZR*fF3}jj3+;h_#I_*?K+`<8mfo=OJp}%!}4l_S+pce;71YOS-ZfN~V9f zZ&+e3dO~qw{4$I1y76^%>l}WA?ell$N>GI1lbC?70}S1sd6@;bB{|_^Nk6#0(3$*$ z(W1e0i=^x=FMBymf&0Kw8`O@pljE6i39Gqq(7HC7m1{*n&)QD!WyjRn@ie-&6Y3$M z*v&0Z%-3Zzq7N#Ce~@c{1Ys96#3TB$*iV==Tdl@r1w*ZtTNB*6v&F7&u&VpG+_Ld_ zA!vPSvZcma%HEk~BlL&VT~N4lbP5xQ7TFuQ+E!4f)axqh+c0{R zK=VG=39$pcf7}%1VEz;2nXHijz^J~2qQ@vH?{w|QkaL+}tJ_H1PXoQ@&$RJa^DoN7 zne?VZ0uvg}FWzAhE3!?k-&U0M9L7&G39j&-e<$eBvz>&4J|^jvE$tC)m% zq-}>7w77j^6VzhS?}=qAkxCoOOJBH5Gw7allmpt=e`chppIrGN-WA5jPjU(z)BG&B zN|ge|WFp1UB{c}mu5^sZoN&sJken+ z6={hd7bl?(GW^~o>6+{%rIlGxh}1q_rYHR$4o;~ZvGS9 zKDIMfh2(3&h5=)9sbhUF)X*)s<(GbxiiZasq6)7Nu~HWyMAkbXdRlt+f&8M7fHe)D zrlrx2e0zkz+d|&Zo8KmI<3vMDX3i(MwN(qne|M@_)G2Zh=cn4B@ZbT0IqnNYN`8Ez zk}ofGBnz8c za{tR@3RR{u>!1m*2U&A8G3ynv>L^c{wF%nE3PrPvOxy=Xf#F=mx6=$lCyif$J$N4a ze>E*I?E*AgXW88i`T@3?50LEkov6C|xswvSsGk%lOQRw~|&R0RxX1Sp`4-?qIX5Jzp)?^v8!mwDU20>$>3Oz+n z6bf?peffkCp`*|&jx{L?eqDWsppEcVe+3%Y;MFsGd(PTEtq0$@=yLM6oVt- zXPEBM{WTa;JsieFxheunA3t8)yl$OY(_+H2OorZS9X3%V z!6mlo@iK0}^8<)czSC9IER%JxEJ~O``3?(c>EbfBXTmW$4psdt zekH^06YRjx4QhaPWibHi`+^ruMUFBCv!9!l_(CN63&{t<2KZGt`?3gC ztD;T36fUcy^Pz2w(0i(M&6?dvy3H850Hh;Cn{g=-GLso5cw#@ve+L9{-sm!-!Bi-P zLz|N;pWJ_IaQA#AZ#{O(Dt8Aaw*DK&* zy~}#=XEHYWD6*=EO`__Et+IKMuZE`?GHnTgq6dUYU&slhtk3e!PaEb&r*2VZmV056 zK1Qi+3t2Z8&>Pt*e}7Tp()>XPBRt^tr6~nMry(T}k(1NjuFM1(40q>NhB{ZSF|ekW zZajzWQfQNOUGH0C)HabP*c;KXS*IKp&k&~a1!>3J0QpD&8K=%!2(xmsKxxOplYg)z z(`J^$Pki{B+xgmKoZCWU?UR&xr2x9e&x5p4c{7WuG1NJEnPqV`tBCmm*>l#{ev8em+Q7309i~ zaS-W2z}v-pz7UZtUbH8N!_G1-!jR6_U%_4Eh3}r(rnZf@RGKewaYNJWY`^*ETlF^s z`=-aqUWw<%e-W{4T&LU~)hP{1b=kwW;k=)D9gw-+ZjV(NHPQkmiDhyA4RN&iLgDq$ zp%>$76@Y?z^+}+w1|z;g$G6InJ0~YDUk-P-D{U9f_zq;IGw4B@_$+m8Nh-cj2a2xs z%ux6eGIS&WSBy}pbM?*#yElQVm;jy4Z}$i%nEBZ9ev)lWZwc9_%)LGqeU&a%XiqYtrAi&Rc=ejh9QlJg3BZA=h*1$&UTl= zHN;GQYSrcda8Dph#<&4}N#VyAKH|9A&K!_tg6dwpmnrc~@i;prz15QYPPs4MYnonE zVyx{Ge|-43qy5pvxOt`*bn zfq`a7uzau~LFeHPoL_dKl!K}`8Lnj`ZsJ?i_Y#2>-T0zVZuTAT8inv*|IV{sP ziUHN$TxZg<JhcGW$gB1l6Vrb1Lb&#Ij2E(tH;4FRmmMe zeBiV)9;8oXyX#|ycRxr@nXON9dcqq$qO749}khb(3m9$!!0|SGxj9Y@3e_sS_g6;D$ z?m#-I1KGpQ*=NhQ+J*uM1t%9q{FUnF3!q^!v!KlelWzP$g z2jPA;0J^OkVWqc6y*=mC9k8Cq|{3$C-L8A16oHpVF7|J!& zwsL}57#|P#II#BNH;=E@mo6MQlVw@zfkGX$5p)i2`@#^9>ul^}e~2tj&Wng?_eM#S{v!7!V)y%^f!LWQz1|opTXz-n2g)x)P6YN7n!BiGjFJw1aY)$a}oyVD{ zMivpya4#jtBidN`yH<=7_Eg^66)`o^g`4<4X!j9d`9p>m zs>O@Ba%nrsewSafe}v!L3sWnbK(R|NMCZ_n1mtd*d5rjeeL$av0o;_LnUomOCMUp> zig8$7oXfT88;cI@@K`J&684&*z4Em-*{6qfiyZ1Sj|8uL(6RcxFi(#8`KaplX_wb) z+|??FP#Ck(=q;i?aos*J#VIM^;s}9b>)KQCe%|!HgiZTGe?3~NU@2vM%3Bi$(aF$- zfbA=~B{kPCVS@e!p%d<1Vl7aFpaB8yrxG`8hVZJM*gHYpyS!$Z(a!n0UwUPtCd4@q zX-k1nNc_JFsjR|r!p9N>#2g^l0r#2%iq3lCB!`^HF|YWn*;oFoPGXNR;ICEQgLN^F$2*5A%Laz%c*mDS&%3tNmJWrQwC}&NEK-*3n)v%&gbnAgT=>-te5>h zf7__y#vh7Dlguoh)_i!Xhu0>yhmg7X>xSg{&arOU3~PEneD*Hb(@tT?=!=gOQL2ul zACbnsYcJA7)VkT@P!#EJ{9tVB0jTUp_er*NKb85A*K6_HNF=dRSt|Kk6+5VJzi|lR?4C~4*QEJDQ3sWLZQ5Z@+-?pa zO37&H1!c?leY6of%U>wv5Q5%XLa|}kr`@I(w4-Q$fsn#6Jf9%eA z%QP+dlwB6tLA3FvPVXuC+SAtyiw^@EC(TgvzeMcE} z2RW+;vy0NGh}B1++VGUNbYkMUyDPOgdxT!n9LH7OH=z1Tf^9!^*)ZX28Myh9x{7*P zS|q$vD+vAJQveJRMGmCcK+ht;e?3KqUB)t=DvzRawq-kJ%#IKs0m zSD@MT3Ru%uVXf3V8Fp$Pw& zIkZns%m!Az5#p)@YH92$?EI`oRTl$IIL-8{xU!(#aP?tt14PPeM!7m0k-tVtxSbqhD-M)+Jwz@e$u$iuEFC)GdRe?=-Jpdey|mZ${6 z;vXRmUwdU#qyu+_pOtbZ9%8z>@p{~N4u*?S3XEZ(YoXo|M&=;W_UJAhNK-0nM%`~* zQNt^|f@%+88Wh{n-D6`mmIyz1;2n6|o&KF8Mw?(>orqVEz7rP<%6kJUPB^Wv<<<5qQ zM{W|-mAr0%-^$cvq6pim(6#_&S4)5QnpX617M zk8goigpG$;W@b`q>*DolPkzGSl*Hzm>01yT~X7gcB>pOcgN)rPAKf#hf~%2{RH8i_BngF}J&v38u`$L_k5>7dV&$MH}Y z&ck*|x8uOlGcKnYd-=dGVsEQQ#WH@^h)bxjrVBunhZS5TBEwJXgTFFoCpFd$%5{N9 zNjB@7VY_A3{74q<#TyW{1z-jpdi_2>UwI;Ee{jbk9YWjhI~FAUo%P`qZ+JWJ=GpWR zmh&cN3DL(tX{}^32S`MLtC#WiqXjSE%%@kK!yn|2zsPz)aHo}K15sM(E9m{p* ze`Q(oqmjp%mS5dvcz5Q_Y9~Tuh@)w#`B$4vIOR zo!|bB%CKAhxRXm({{h3{y(3mSBi}M(e*qre)mT~Tp?9^zIjsdt?KgV57VXb9>(6Kf zRIU$KOq}IE!}N%>zV6E`nAuY*u&&Hc`H4ESc!%LvXJ>HV%o7;u+Lw~e_-Oi zc0#ng)k1_n=k7p8gu*8c-7Q@?1o!gN^N8qb8b*UDHo-7_FOQa*yj$_QS^0R^3G(zz$U`BZ--)v%oTLE>b^B`xQ)I>0LA$!K!^Q?Je+)QDUuIuju|wB~bDE7>K=e^}@2Ax_rHVV1=L`z=q6F8(&YK-Xt=RG@fIF_Sr(|wz^CZ-+2}Q)xNT2;(fdfC z5UZw1e?Vs6WZKumw69mh)UNsrn8%k#!x$J5kxTY@2uQJgBpaeRjjeL~e+rH$-qbnd ztcFv{?H5@vlJE}0aXhnT7;RvW7XYWC_K&-VvCYRFZxe{ji~KYXSg!WH`30yo6If@^VK}a|+P!_=|N;nSP zC_L5y)mG#E;t9JAfC-(DN~FwU+F&73n-F6SEVDJa%r~HKZH;w&dNjj9I|qxj{9`#SBZb&|G0d|5hE!SgL2~26-M5*EfYle^*;vH93R9L|F;{ z0O?!ayM0U@4=%X!ZN7{fjZW!~hrR;h)RUt)>i3~D6ZjiN!si)p=?ok(mA9Kb!r~Ce zzK@XKK7Se7W6QJI?4@ke|$x0AL}*sEyBFXRE!xr#cmf~ofbn&sNDyD`nfx!G^?kjd$mx4OwrnRxb;POm~NRtgk_hPI8rxSsybNKwUgp5ipzL=!e`Qp9mdYrEA8+>1;OXeb39>c{6Wd{UWNNo~g|@oWs)5eK6ATKm`|Tr2 zv!b)viQfTe-@UzK=-0K0q^$%?P$L_8S2Xpjvi;@}T#2r5Jq+BQXA%7nqhd%UldQ;a z>aolN*hyi-Fbhpon6x414Z_UfXb&9Ob7dMT_%tS@e=*h}f8;Dx;8F2{~Xx4s;TmKj9I0Zc=_0z$uqV*1mI(mp#Y}KeK3g|>=b7qzz3Hb ziePFeC(`%fD{~>E0U@zIN8OQl|)`cA5YcBMnS;mo;;Ti?58D)^$r#V3w{!SPmq z96N}S8SbH}5INY-R_vlPgQImAKM|mj4;dTzOxb4On~%}VE-}N^Am^(%-M0trEppQe zsepz+09AFg`6bdtE`Kk2%A-I(Ky~Ay1|2O}fAB7+WU>kLop|N`n~-JCjyt5WTJi(KMFKms@q$RI;%Q;)YglqE8})4SBUU ze_f#Ikw)Q8Yor*LB9Fj1-8n;Zm{j7&$mcYy_Atez4PMj={Dmeez3(*NIR$en{b;Y` z>Q$+`KI+Nx(W^WfVydF)@1?5Xh@5}%FhUt4^ned9qlb-#bcB>)nzkc=eD<|JwaLT_ z(h(AR(E~>wT(sbv%P-Q&NFe&c0BcgDe*tI=L)fn{>lnA9^ktFUjKZWdUZg1M&*`f; zog`H1kPtH6-LJDKY8@38ZBjP>fCvRn(|AOQT0el!oVOdplb?e@HS? zM5wz&tAT{2Ylr+!FA$5Pq%C2f^#)iJ8{TNR(^=4^jd_xvr@EJTUy;n?xIWrlEfCVB zK4Qw8vrxO5aXXIUH1mTTzwp~-&_U3LK#s8@&W`p~-$*Q3Xb)5OaHyXKU*a}D#; z9A)wgX))V-!%e9Kp#oox9@3Baf3E=_O$G|I@7ZDISH+nlYvz@z6-J!yI=E)^zZOvQ zTv5HWre;uP%--$@{ib7^uRrV@Na9iJrZ51bivSu>#szc--e52y)MYyPz$0@B$TM~V zqxA!RR!6p|Odu8T1{R20tIN%^(|My^PmZK^R2BC|O_vKTK%1SlrL2@He-Wp;^QgF@ z0?w)&rHg?U2?RgH%{x+)Y*sCVU^FSK{GH)Ef8I28gJz9|dsm1rxCKevs`p;o&9!y{ zvf&p$1f!IK`z0+bBpQH~pT}=9;{Fr@cki;v>G+L*xG(n_?x3UPCZTm(J-z)Now9C){ z{j~C=Xib9U4&PCje_5LCn-8{}2yFP7!%+VOQ#urm_ng90VmLTn1aFZ`n{;O6 zfXT?vo>8*IM9NbuPNbe4!k4*ZKrQ6vTE!kKEb}+5`|k_`GSeyCX;FPMGo>y-H{s=N z{{qFA{U98?B}ZA09sr{#W_BGz^Gb-#6QQ|W%;=<|MSU_MfAJsZp2n4CL1#hv@yf6h1RD=4#1&LJ;w;kILnwQvlge@Z(2K=+$SOf*O2p&cU>;Lo`LjB!{9m12-Hol5u^^2e6R2^GmcmfyI$ zV%d1N=>_32svmk!?Og38&wrG@Abx^*^NA3_rCQwEF+)3ggms^gIrF%eDY3{P!#C_k zQ6bFAv>QHE#BBdi>38g)(nbI+mr6oZ-v&*%;itjse~{WQ)5Hj!JiMFJ?n*8BZ1AY& z|J^qIqy7*Q4xCe{ptV?E2_@XHpW>~8Q(D*Td;gbAlg4*Z&&V7l$Wde!>;GE ztkcC825_9vU+`A^x~yH&JfWXt{SqPNIUWrn|ZkfAb&ADSmM16-5AakMc1>69&0#riAqw|K-?sP0Ph zj@r^x{tEEV{*t-+t9h-%i(lUW3o1|Xef2->vdq>Ul~i$ZhT3wU%hL|cfHbSNE`5D_BZML_A7x^-^+`-f8?E% zVg)o?Q1&Yj+@9c8F`!#8&*6VEFKAl+K-U zI@mmX00wV@Gfi$8Cx&-!!)PUkf8G6}^zA3FHZSeW_>2)!_Yi6Xcg1J%I4#MOY;;7| zWkxlWnHV?O$Do0lfOd#%gAccWn-VD-if@xyP`Ax)){*KmQ_^jED!(P=53F9^%r3p~ zbl3K@RU-7<))C2a=~pfZytq@z*!_i9VL-fNXWQ{4)~v-sZ3^kfI{ncrf3VQ)Gf~@{ zk69i{ydxZH){)z9$#BH$9r{uTyF1?fSW(zv+@2yQ(6HtLTyTvB5>25@>AKh;9~&J( za(PO0vFp<~j|CJYhDiAEw2z@56}OK!7Gs4hXmtwdMJy8Pf>y~qXh~vg4Fa&EIyD~5&YO3b|+oQC(%hXgBl@< zGp4$u73)VVE#l`xWN|59$c-WmeA$n4>D_=^pe7c^CBgKnc))?B>2O##O7A!Ykxq#(Dq8Ze?Ry0$Ufl_9gUPW zIQ~z^%FxG>N}Wjsm5q=~=Mjx1gqX91L51gXC`Zsx%=fmihnHZ$Ao|i4{0VoGab~z@ z&JOYt2Xlm~OU|?-Gm)DfnPrO+d^FgC^q%X+7$_z!Gcj$28>V^p6?d3S8_8Ms1Y%;J zybN%bOzsHdtL4Mhf6qe#*Y*3_6k*T|ZDWLc@fLHD_5AqN6HtOIE$J`f zRN}}hz;A4MoCv<`dy+R8B&N2u2lfNmQ01QZ)2E$hk32*PMAFp@H?MF+y-N>)fO=S}CMVaRSHx90xj4&fWWw#^!ES?beO92Y6>@FEX28-y z#rvqYyRj`2+l0B6z*WsYg%8F1CMRN5JNZHvkhQtBf5{34dCr^F-6$ z@9gq-p7kg0E+Ojz%gL4QPCHgNs=c=9 zz@jF?Vkdd#rq=04KP$D^YL?mQ{S5Y0?()~aTR67f>oOGHH3k$Zk}4AgT?`EdL0~mV zM@pkvf1JPgv1xeahO^ENZxXJ!CFc8B!}C%iWN!u!!ZeVd5Ad{wyKwlo`G~$&Oc1fm zIM+UMQz3rSPS=e|^lQ5$ViU@~ou)mlQ)*sG8>c48Vr#=%ohd=FJ366=Q_P2k6wM*| zblyfjP|VJmLIzicvH)rvm_SyKRr0a(s`o$&f8UAOp=je77)$*8CUF^Pv?R3Shmbg8 z`&mX|RhQMZ^xhdpTb5d@7*|h}gcfJ$DO1U2iZJZ+VW>TmKSH#2-9e%+`Wc%15iRi= z6~}q3Gw(7>(JGTM`%(+~|?fa-q9lc{1_irxfSUwOE{d=YN5-d)g72U+vS{XY$ zf9|Yp5a!z~I#j>n@SC2mkU1>%EVWFa9yD`d*@{br$t84OVHs8LZh@0ELsMSwjpK5Zv~5 zqzR6;`FDSE5-y`JhcWr0V*CuPnsb&jf2WIQ!_O5tQZ^W%%y+&U{fk>x`>4wsKZ7Zd}dpytjqpo&aIc>yTWB*9?{!ARyx`*`j%0hg&S- zV~k(0nQ&RC&}n{-_a*9}hsp~7C8ZQJu}qSB-W8Net0;ZeG0ama$^;r{1su0l&oLu^ z#S3ylV}w{R1Fx=;5!%kvc1U+I5&6Oap0A4v5y_PbwcBLA&hz==v$1a5r+&)j(aLENA)zI9q1S14ne{e|~`nhbZc#IbpV zIx?#>!4w?_@`vPBdSbM~f6m&tBo7z8{!ajs3vKl4*xzo$vM~>pPB@c!FZTlEWC*-) z3z|ps?GrLwN@>m*LJwE~tE%uniaAd}78!xwN_}wLDELe{7nhZi$&|^y?Qp z!wf|&sm=9+%F80F2Mz+J06Yk80yi&U$~>-Rci||Qp=`=ow{XH!-;}3bl%7~Pj42{w zKvWGmES-_h4mohVR}h+GepzxaRuUj-9*xMmSbo8i| zlM7XEknvE0cMrc&f56OI#P`g?jIE?fKpx$J&837Ml#?7WXU_U$l9RHtK*A*1`{&Hz zm(Q_5Z;n)MU~XQRM82G_%fM9bla0eR0;+11Us{0e!)bLF zzjg$;Zy=ac9)++#F$?Ucr3?NFmlpmTu0#hYWJCu^AH12_auBXptpQqZn&#Ki@ny^B*H>|u{}VZ*{4LD4>RYHkBj(4dHFYeAF9-%he{~4b%QM`83;H7+ zAu7+DabML&J@cE6m8sru_yPI4E}dAM)X2@ndJDX%e%xjy~ zN@WMec?wJ|eK{7eTRZP|`@Oh52vdpz1B~eJFB2mr(V?CmVO`=Li)+;6B!s6zzl?{k zZWksze^l&++JHin%umazT65 zxVKqD{Gu8V-IATdu+q@+1_F!?w9TP>wdHbfOQE1H0eT_3339JJu-I|s;ORkmk)RZe!x{R?6{1G>j(ZYO9RC)x|EifRVptB4 zBH*)v>`;>gLlRju6;V)RN7L5TaonRxawOQnf;$X?e-fIn>7DzA%FZD^-g(y&LJaz@ zjpB^{OwP>u3nwhSA51N6n30UoKRdkiJDyPRL13!DCfKBM^`?_C`ynq6-EQu91yY>JqL*TKCDMp?|0*O_-a}C!CV>bGI z%O@CmBd>(tdI@OZ5;d`f8g(QHb%hZ6VA&JAk1fR8%Ih^iwj2>RE*LflPIJ(dnGGIJ zWZlwuK>C4h8-9X)+`7@zH4>fRJSp`a&~1W}F|t_0ekOz&e`|JC>FUm~TjvYTXkJmZc*}2Og?$q;ht?xE|1_{| zjCk(OTB76ru0Co(gsrX?tl&sLu4FWA1(b*8Z-PKA5ZYD63?Jg0vHd-a}c zyRabc_tw>fxFs$A4`UgC%+KgA~MNiqdQ)>Kt z`ey-IPS4Rg$=k%P7V8n6>?5o?N?ge>oNYlYh?(ClzTXpr12E}zqy1LZ5^oQta2$1=(wqbaPBkUt$!aYG?tsQ891wkOhk$C zjzT7Pf9JnMK>GdZM4vUnJflq~3uJzyUfc};3eCzkdJJv1LQ46gUW_L2hR205L*fht zEM>vACJpU?xKM{u_reS741vx@Nj`d1by6r|(}h8L41B`lRr#q~G5F~9P$EvroxXr5 zEmX$~iPVXbR6sT$s+n$~_x`htB9cj>itFh$e-gNF1xKk4OGO1GAp1z-xeIh8>y3|% zx4>fu4HlUHu1JBp%&-Wg;4xO1)}J61V7@ksIM3P`DodTyBr+J34dn|v-oj94zTOKJ zuPndKA*~5%Wr=TCZ@THb&a=f-t>u3)6@bi9g(v^_hq87dr??dxufXyHck@#QJ1ycRbPFYn@giMqqg8EM2Hd3ur=zXh7TCus7xT*{%MSe_fmkPO)imt)}X{q`lHcnMQHqmN)B^TD9i< z^323H542){E4k|(vLl2~xt=*`GeKorhGO=NKrl=O|MhLo^Vp=8W&zqVnbE^fK%;7E z^nkKgXD*Oa<`JYUhgg+LvY=Uv4=H8t7^XB-wm)kVT+%oyF5uwAD+pBdSURHRJG%N= z7{1<`LMr*DhbnRN-QR~Hy{J3UGPnCs0^2f^8jTYZF(5HCH#Z6|Ol59obZ9al zH!(DqAW;Gp1UNA?Hj{w}D1VK22T&7Q*DXy-=uN5;DbgXK3etP;U0MhMLP>yx8bAc3 z2?8QbkSbjfrGs=35NT4RNUusqK&t#v?|1KizxQU|o6L~2*DhzTbJji+xNaF33o1Gy z9bjrm1X>U*1d;|S>l#S{K_F2f5J&{T#bp9VyTksV04_6_HwunKNPqt?zp^(Bg2rMh z5HvPS7l{CB`M3kYqCl{OG+0s^1OkeHKvMq+M0!gDRUp1_N1(0{Pz#BGp#WUUNY4Oo zxU&lyyUTx;KprSB5G*AnDeyZTsOSOnhC?9;pe_XM0`tJ`2!*%pAf_YC4YqUc9!E60Q$kvEJdyEo8SQwON;2}Aq|*7*}40Q~D{fM6l;-{t>mI5po?gYbr0Wc_tFARwG_JIXsRGm|BU`_m{ zW81cE+qRvVOw5US;!MnmZQGdGwr$(muXg`iwR_nYebc8-Rad{y`+L4x_P4P>5TN+_ z@+@<~CvamxKGX@}`!l~*Rx+ML-@&f~YWu*!MZ5n4Ic#g!eoK&TfPcY$`?mF&Lmf%c zL499HN} z4$RZc&)>Tt9zC1jUw1zr&w_@mjj_YCKn8%Y^!~4?3Z@CvtNq(;BSj7fe((qwi|1}+ z)EG4&=)Io+amTWBoVe_xR}nx=wT z%f3GngTGb!`T!Kw+*))3`n_&YR0jWt+b?_oB@qnnXHGo;HW1K9lgj)Q{(_I%Po{JG zWfv6CXNm2w@;wG>iYk2I@NOKOE3~z>;`GJH!?6Cn(!Gs@GeHamsh~)b=T2_0E!D|i zreSuAZa|nsc&Nvze2h7u;K}`-sfto`^KS|+<5@^c;q*NBt-f;1xsYYjEqv9slqv$S zR^YPbLvF8?XT#IA15-dU2817esMnRhL%~+$hWF{s6luq;f^&aBZD=u88!+eaV?ez& zQu`U6eQ9p~Vkai2)Ydwk=Q8J~s1=n<3}Yd)Rx`{KkS ziOS+6LXke47^M@FS?^Ds9*U=9}D8pXF-LzKwJ%8JpuGh$zO9QRSh$hyXiJLOYdd?l;{a~g`p8$EI~e0S&8dmg zZ*MJ(IUcCI*~GqDEgd%m_|gDweqPrPRD3)9`yIp&vILKosfDH$jQ2~A3T;eGrCnAg8{{b1-UoM3F0OZ6#avgdwES9XtVqtdBMS%I|1G9o$G6j+ z@(Nn#SXCf%pRk8#P}nlOLDcPUe9ApWbWTeT#xwm^d#EG5bxKD|{9C8)AB(dpdDKef-%fo0!hzxuF;@DH4d3Y{8HXRh^uG;$=#XjNX}j_m{m&(@cz_-u zOY5E&zrcvT%=ZJE`hK1T&BlqNN~csEaMZpzcRR!}e_&y-f_wR%wxhW)QO4o%2N@iS zMuoM??gBlVO59Y0W0@@|tI;OL8A+eCH@8546Zy)98jU!4*JC7d;m7dQo@L60!@T~eoshLZAlJ(I!OZEXh6gac&x{Hxk%MpqKbp@>S zUC3l0nak2^q&|Mgc!$6qkr7Z=*B`H!g)d8@Ipp(g{Ut+Ji@Z&pB`EPlo6a*gI20}1A-P{`!d_ZWLJ|F#QMa++?L zPcT!w-YdCqu5=N2Dfp%ZVNJhh;4+I{FR1dG8-P)wfFhGSMYW53i%clpv`eVxga``z z7F`&&YcJ;EPFw}vczxOuGxQF{$}PrgLrX%6cwMph*@}Mn`*WHf3U701)dSap#Yr%R zIipjUzAIPoBMSPhnxATT!MCP!pc@wx#9E!Q9Q$}tw`iO#ntil~enR8Uvg4Mzf9B5v zK>)>6cU(IP9Okk8mzm5x1^0NvJ-Z@Z*9Og{VhQS=M3lwxePXkc^z#yD?>SHIL{`qy z<$8RgB~#=+H9gcb&s`mS{LY^uWI{t06qEIEKI$XYDb^2$lXMt{1Hv6kONr^KNCmwU$=yv8e=XMg1Ww>L-^A&Y#UKX)ZUG}Ff(Ez_|PF03)1&%UPj`LrRAYeuG zZS5a%9sCcTX6Y8(r3hUJ;>=m_n4)D}!iRbDtIK<}Ys*eAX^vvc0Pd>x)rZFO@|UnO z=a?w{Xl>RQBz23wYOiYp&_c^81QfpRPYinqFHWLd$1*X1!*@s_ta)@lc>nB>XPvNf?@VArq>I39!eZveg=H>=nt zJ#4t`#JgV^Y&C|c{G16VG-_0c>ttIK_xG?a??N8e?M0o>by<^-!VrcXbQZf0Qx|*e ze)@-No0*Z<2Lg{yb{4*P)2ss{6XP#aJ}{T)LH4ms_oLd?LH6hJ(Y2;>W?4OZINznM zSRBGh%^~1l9ez(Xp?K%(67V;u+oG&wneQi9qQK3ApU9Y(Zp5S`d(oFH>U~rW-9{cy^;Zf-iB*?4+E8diyz1l-lqR86 z!|N?YHo@hvzsn=cq?)3I=8uttzS z%)xLT2YPsiqi3W}7!dnow;`tXkR|eBLYwGjtavE7B+{S4mVr5d?g0%Mu6d|^a^TfmS7Zn>9ur2 znkdR;DEPO_W#IHa6>neyQQ~HoaI4ESOjI{t);G=I7m7+ju74$=L}qL4RTnzmIafxi z=Kl3FdQsM-to|-%eeOZ4sDFksJPE{JLRYReW4vx>ULgbXd!_|#Ac@psjAyKwVorgG)?Dc8Wg+~pX! zzs|q@6yIL!aB{CPrh_Q-JIw_htGmOhfW{!ug^etCijSc2hWLXr+M9>BvKqPsMBimi zrgSCxf6kTvgwn0V%d^k%{G~**i~iKd#%fd%yC`aA-UG^8aSER%(racfK|CxQrSzdb ze^9FfHM|li@slzlcLuPXa=FhZY*{V%H>&d3*X~!n%{vxN)EQ~}P+!YAt*?5|l=~~@#LzLt zhR+KMD;j|CF_ss~*KqBDJvXvxL@P=BC6sVJA)rV;wsM|5lFv`Olk4!CwU||{axDS< zG@TrQdWks2y~OM}{l9--^|hE7*Y#lEEtuIHdxC2|aZ*(-RNb;>8P6ALj-DE>z`9=D z$d|W>qJcvHsh+}*0A0}t=#Lt?=?=yfsF-b)uR#g;`1xX$GTWYyY7+T=>`sf)8;5gr%7mQ$=##a-=3Cmv-|)@5X;?<|`|Z;+j?TxE}k@7~#Q&2CEz zKWunS+DPuF^W|#xip8kwEJ3l-Hor=w^E_($tyiT?CwuvtkVZaN*jsJzm%W|`jYq8D zXA#i(+Ovy42)1Y+y8aA!0(>rtlHVc)bAoLg7)SU%>cX4tjRhsR&s{FhMgBe*_ z&6dJuJ{c7snOS{FkJhY=;zta39w|5Pg)u6L>*YBowpMm2{Vv6ZcPf^}Je<+De*rmA*&Vc;rQ;i7LFiLb8WXO?iM2 z6lu{x*th?kn(6#{ButGCVce(a-(4K@=4F&e^MWeCyJp>%I5b1WYi|P=akjeLUGCkF~qQp!=>((vX2VC5oQj}%=be-Bb!_$pGPFjqk`}Wy5VFe6T_u5C{ z9o;73cPclkpQT=$%+K6xHw>G0GtSTl-#1bixeK@5Qm}aD91L6j;E6$4{o}94rJ=of zMl-U2K$kY;$lsCl%0$X8%gYfsH&Y;t5h3uEEyAfDMuNw9KE`x0 z{6<+aRU7avDVt_L3}Ht3s|GMc-HVjVC#&2hh(vfqd5f*~%B9MbpI9L5ah2%1^QO8X zaiVo+6SSR7^Lr+)C01jx`Hg#ZMBA+tLALBVl1#2-AOXolvPxomKRFB5#daz_mYBXv zYGo;YzqIGJ(NOKtxz;smHFx;-UF%r#AT4g1HqxYek@JCUa2fD&p92*0)AUFB7dm~( zJj-jAKh_UACtTWoi)ra*Nd^1bcLmS)oZ>Y zGEJvXy<}`(mwdqv`v6$oiuA>;Fw`R^TQBh>FZrpSX_5Uk46QYvpiQG`F|&KDl=UJq zZq-~ZL1k!Nih2Ak?_|$j#y_mIyMOfzDw3Yvmx11L&c&V#`ospDGCqH~63?U2q%$?E ztF(3)%MwVA(b2{6y+{%AwQ&sv`Yeb4o5Jvy{rPe&OUF$PAvVN{E#&lRDb_m~N; z$LQ&=co$|8U-TV=Oo=Iz;l!nj5L}n;tJ}m87A`c2)+Nv(E#{eZKV`he-`RF!EOchO zXyCeQ9>IMC4lfj+?Ni(C9Oa)cUWgDuYFMV;cyhFMc8N#AYLq zZY3v|K`D*%bOMA7VR3%QFqsj0KXywzBR`{^F?k+*Y^8J*8F-z3QmZwK?or+;j>?Ay z&13IlU~*>;L0u%EGjgwsV&N4T{P_FP&W`tD*=;Aa<7n_+;PfMVNBw!`+Hn4wgj5bP zrTJ@yLKPyk8zS+FO#N#2G9j}@SV*7}FTIRG-cdCxw2ccEemDW?k;Z9+0q9%P8 z)qFN_EVqNxL?XVQb+JaZ!sL=IBu8*1sjgofSWrFej})G!-o`)KBtTi^(8;A!8Fr)u zG|nHF`f_VBC-2#rdvkr&&OkNZv`3GeF5e1&#<6Ue z*}b6ScwxfNyzQ(vf%5#>L zVl%{p;SRCFZ42eLq@~5A{NlNQWl40!P62(314a~dV#r;0uY<76_{OhF$4jCwo&u+v zO#(Z@=by))HvwK5TCIuv^Gnv_#Xp7u=JOH>Bn%|eA{sREO-DC*>kiq9l9g?>d17O^ zvRr{Qb@zax&Xbbi1afLs&EK%ph%_-E&0M=oumL`8rGKc7O;>Nxqnp3dP3_{lDp%Ii zJ&hUWzX~SLFBfvnwnq+ctWX{49l)%f4*Lm)&0)bvWt*h94>@-s32q9LTYnzmku%75 z-78MuI=5=;NLq{2tdqq!q{5E-tot+@P0s*Qae=Pv*k9iHGO6{O$p+MnC4;ksrE{W! zE$E?@EYR^kJ?^XiZh1SP6_i6rD5=(}I+JfNfGd2>j2=#_^ZDx7-B#AQbuS8xF6 zTIjI!)t~pKh-fs6lH6-e7bGei)HZABL?h2h<#IR+BYHHyT=tEQ-hF1LoOU5kwCl>k z>n|w?F54oPn>C+hu_X!J+LBT+dB$A`ZCZG)Z*21uh;e;b!mxCWE@dMNqfR!3Zt|&c zUdWRDl19>?X;>Q`s?I!WROMHCOb`HspK8xkhbhW5%zsbxdR?wjCPmt#}7$+0f62A9b-y?Ne9C zv3C)(^FFC-ghlJJnQro)dn#+Lbtsa9d5d52JFHYHKe-jFi)D#0Nw+!p=dl5p+U_TJ zL>1FIo=D?YWO*0yfFA9U&`g$^Xflv67Twfikw`>J`ZB zRYsxefxj_;qIf;(lRZCV15f)9o>6$Jd_#ElUUO@rF>`!*K^*_X$gcuxaK8i3bnmlx ztHKOBf8df@frnSMQ<&1RqYzK+(!Uko)j^dRT6a*yeL6??NWnhVp$aHu(oX5r&L>sJ zM)Xd7<8Jl+{gmcJig;aIl9(V-3XD262)81IbIXaf!+DkJa5gLcGd%f^?cqW(OB%+n z=wb9XPw9L5yTH-y{2-_*Y%h)8^Bt>aD73fwF4wJx*?#Nh;%n@1d3juvZ;tpngl;FHce=IixcxuAZsH5PM|DAsEgr7DKlxBU&W3F4b)tBN zouZf`{0qn2`!pAI%;x%YmBP3CC9foyAp2!?tK*sXV%)~>1DrfjbDCDTHv;99IcJ$$ zpEw&T_P}AEX?yXPn~W4%gqa1hXbcqh|Uv6A{Ta zI_*-_wOz-Z`IWf|jNYMpble_afDvz8C0@HM(x2cUc6L?z8B)Nst9hd(U5Pqn|Akaq3%P9iR^^Z(zw&sGI60Y<_?vPhBtRD|t=KtFwm*}1@ffu8dA;$olRpqA)?J2k{s+Tjap05KYA~{1JFo{N0W2TvcLy8b##!=m-epa5mhL2=$&p6Eeh#Obs}DeJ{8i z3b2vZEJ(}oe;Vw?T)!x*h4{E6LVGXsc{ikqanQ<8J&z};4)3D(2IWrYMzP^dGc0>BK$qFB`4Mi8YgcS4Gs^+G+1b(;mUIMvmuN+NEnz5=;RZw zQ3j@DubMfPX_4KTRT?lkp#6SiX~wiC(GzO0<^}0B9=xsBb@3mjk&<5YdN9N{?f>&E zGwfl`d*4}yWgrk=?Mp&8RyeO%)6k`W=lruzs&wG6sR9>JrH%#0tRAXDCHTa}%gUz{W9@U%t&-l?S-1h!9;`q(O9|X7r zF@eMt4CkYCtuT;~;`i_4 z)i;DnC>$9DJo-S8s_xmGj>_&;n(GHig&VERq(Kr)+;e;-;nna{b z@fFy@q4gx_E)s2GUjNZEP98R_;R!4R%J-`MqBnyWSAT2=SSaw4iC}v~ zn=|5T3%}t=)rO1SK!W5DYyzi!{h!UWN?z0BIj0%E)wwR$TDGvFZRcKvQ`CI=Y?+wv z@O4=JkXyQ02fboz5!r^fBAS9Tp){hy>Vjev`_DviRW8__r+HgThV%Q<=ZQ^ z4jLOc{1eRYZr_rIv>CD3sp-<&3>9l>fT>sU)**540WSPxobuE#OQ|pS!e8>SY#!js zN>hIOym;!N;!{$8a^ru3%H*rv0s+0;ZocT1a{$$m=(?^zAXof&{eTjb&_d>-rcCcv z@C{vV)NT4dL;+XJejykt6%9Mf|H$Ov1O)ysM(`h0z{$z=e^+cIT)ZqSDWs}k7!cgN zob3OHD)4~j*wBA&Cizo}nvb8qJnMbk^-S~AoFxjEwE(?K>Y6e5gz^toK7=TWU@%I) zB(rFo#H;de=k53I>*uORUz?@$*DMQR@0_k_zo#_a{vlhFi4qu5K9R*Z7>*ngVHLRC zN*h~R7zmJnz7LUH6jJ;QSOmnduW0o8G*LknfQ3Tr3}8(X%q8J~QU}drl`y0L%RPjJ zhJgcz7%YSsEb?Hv)^z+9;1C>OrrzPL?6*N(rvYZeCgu!@v z4M0R?Wu3ORuRtWZOQ8`E#6Vm`EiFp%&81mjkXWPwO;sANyTu&GcnW5()WE>DwzfV1 z5^fCPxqjK0NbDU%aenZqT$<7_Yz(j$O*Rqa1E^1RJYiD&o&ns@w{epw7YXkmfm{#- zkg$L}7>uDobkG1V5S9+`C{9<9w=0zaW3#Mom`;D;FmRLu><{Hu;jM14UX<=EQ4^NA zNy6Zp5YkML7#1Fk+?wM4*eB6@FcBDFgqaK`M(oFjs1XSc!GS;NhY}%#iaG~`KMUqp z88YOPe_Mg-2Mx}Lb~IRbpGk}^Ca9`Zdz&b#CB|;ehlUA}3d_-kFXR`;GG@So)XR6* z?_g4rnC>xuk9PP}G81IMymUH%2j|ZX@dRuH)E_X)!2tfhDO z_Ruyq7XwtWem?uZf>Z|JyuyO{cN=|s*7wpN1WNSye&>DL*ZKdgE=s6}oz6-4{;BaF zSpf3!8cYG|lkh{tKQ}iQN5lXyz(9Gq=7Azv$r1QUsiEpag^l~5H=N-8%&K1|Ms)nb zMPcgc{&`BuIu-)P^Nsojo=fwedjkFAJNexq45X4`R1C ze1xm_umII+YCLwxc9{tVlCKPFq)!_c*F2Qel663zbCzU+gN7`VB?34?$Jm9A_z3A& zv1TEr82|}|;{Ps6?{=IwzK04U&4rS|xL8IEx&;IK9uLK3tEE4}5;ZM+U56N)nCX7j zl=P2}Gw!FNpo0SO^ziqAW$hZOlmPt-Of3r*@%4+uCFzM3$!BE(QHTxzkuHSZtr>y? z8w3GEB8fFVaR6zE2LUjUJ~>Uldm#o{SHzW5v0;!TP2S$QnP@>#9D~W@i5yG!g>7X= zg-?v(UhuK{AVPo2^+NPHc@1U0=SiqIp6pEV~;BXbgl`cVwNPoq$sOREC2ugo9NZJJo1C zvmVCe*KS#N1Cn3N+krTxw+!N=V{xyo^TQjHg1EQU!3iTh_yqs&1Af_kTWXj7J29Ghy3j?YU_Tv-_ScyG=h{r zr*g%I>5ts6OaS3#Gjl^JWL+T=`PO~YtV64OD?YdIPn=}O;sqk<`%U*nK2qQI(6iT90qDmoW(`6XkRr?Lzjl=kr#kvva(k-{Yr zZ^o3!Fy36er?g=kOTa_*Y6B=OA9==hgNKAeOR~+KA>g0-_De2`-NBHm0!^P0;@pMi zJXZ_HXeLsA5T?iGB(r-dODy?sj_#QWU>nm9U{Kk<|H^#Z5E7!+RT-f&#A0H-c}+`5 z1qD=3Q$$quvso%t@4K=ReV9bRW9#$bP*Dq;tSy^3M)Aw(-MR( z6hZ0q$?s@RhUeRj<-F1URH%9v<-4S2h86JI;^oTmp+;*IO2pEIcWT+Ro#S`>)_qlx z1t1;6l74cDsd@bO7?5f>N1Q^l#vl{&k}*K|W5z?hbOs@iczm@k%@L8Q<}<$Bayb3* zc3kH!l=$o_tfcE9hi2{U6tWTOqsM}WGS3=KxCmKBgG zK`UR6z_z2U*Ki|vP3Rn9b9yt34+%KS1Ez{*%Cl6?FVmOc3Z>eG^L7MtWYar>)(H$R zvpGO(;s%aOgeM+TaiV@}%*V(**XyH~)GGVcmsFYerY%&L5kZ{B{lw>m7qZY8cYo2- zW5*gnhosgLY5`43o_Kad3!eApRU+^AAQ&#acX5A8#RHq(xqC3V8njIQ#9JpR0M3}X z{@Taem|dIx+Fkqm7L*7(k>0Xzq|o)!Ci*$~=VJ^>@E23o=Hr#kl79)<@uwWea|c^Q z*?O_^{Rg;Kw=X*a1Z+*k-+re#{#Z&M$BNUNNa|TyyrYOiK~DNP#0la_tl^qJf7e5= zLeog`*^ax`?%dT6yz%Bz3+M07egR?F2OjJmK5yyn@ffTSI@0}X z+5iqePQ8tQnRV{!&Pk1+9&2EJG|3uqU8NoK2Kvh5nG!(*lkF$(_={SYBG9d3O07@M z!1fq3kq;^zxYV%wN>X!RwdJ=Yg=I0%X84*IO89!H;eM6o=)|0W{A_lN2%#EKaaLFc zH|Ry1-yGI=%1U*Ph!$fP529z>KUZ3-HpSWF==WPA{kI?bg^SvsmzNo->gp2|~^ zTZ`I*zY7od5Sx8MtU;4Hw&SUg15V!Nqzeia-A)d5tc_nHr2MXpcCH!xgOH~o@1M3k z;SqSPo-o5Xrg}(q6`-BrYk*G(a}K6?u##yzrhJcPdQHfi3%^^YJ7xA`bop)8NYL$< zhm`&`9z|jlXfqF#_eAKGMN50)$&{Ypz|>ktkvv`{8&}8Ma;G^irD3?wD0*o+hnml3 zk(DNta3z?Uq2&|A{ZJ87be~dzo@3J$Jqy`PoU z-}WnyY0j1STra?_0isWJvtvtgczI3XK?_FSzZ$vciIbGYx;m1{-ffpCe%(=`Y7z1> zgNzU@yl$D=`F3Y_znJx4llS6m#egt6;n{8to_cul)j)UqhWO~-Cly-%t@t^*la6+z zOH{_RGXVF5io5j*E4BG@&`& zIePb)y;eC58 z3SvGg1?`dus8FmM(aL!<5d&(&b4m;j%dx5pQO9_J273^P;Vj87w_8CfS|t4!nle8% zYtjTN&!@6*y7v*ozyA4Gqw$V3Mb^kF`N%jV0%_|#DHFfa!+u?7CWToh#3mw}8@C*8 z#65JpT5nS5d9upvAik;U|2Eb-m}H{>e>N&MGw2>(X*}8&E0nJ~&3>W{iIq3S|7JAm zjCUr2?rl%QHV*gVbl#=ogAUc==_9q6gz|wokbPH$4x4EGHH=1-}}q+LfX;Fh`n0;D9DL=x>!IzO&t{A^Yr$0x{~t2M#7_PHtpybpH{QZc_{C@ zYBPn868hsd`T1yN47QIVBt!?PzmT#`1~8z8`TaiNU0!B>kfaLn)+s7M5xoo~TP`nm zuVcyyERTRWK@sWp{(Cl!o18{8)JSpVPd95O0JZ#UckkMEY|7=b@c^<_SZ+S^TV7FS{Gsavck6fg}ri^6- z_s9Ztby(s(l9OQ5?+b0Yt2W#a0>iNLE0isc`S1?BCvZ5M~DA+cF!uvwgr2EMdFER3C= zK}0M@P?^_|#c zT5Sj8epkqc&t4p$p63->tKw@WL^ACD#P<|gJVFPqGn(8A^ZP6)t>2Z@4H3zdlj^yb zf4gIjS6nf;3;3|Q@hFTnK6p8a6~YQ9vzGH%`551{1&}|!>n{#>vJd$}{-Cb7dboHfC6Un4ccJ9A$e!EZHhree7IG&$}2G%XYI zUcbfVWSKNXl?OBYm2t$yg}6+Qg9`nfpKVeBRQ_|y|EXzr#3kRRiJdy#)fuATLY zrgX^P6=NPTYNxYalW^Lc7oS{wWweoU4GRKT+E4MrcqLH8>Sj9WP z`@NBpGk-HUqitlw@%jZ(?Bhf)675M~UjJQk&e$`_M{1`SBOFl& z7BR;161Il7NS!&xR}Vfalg!8QryBojj|ppQX=`iY$7ksQavS2c`{RAYO=dMoPLu2y zF`=;loG|aC%YP%AmV^{JKN1Lo&T9>nXm|X43Qk{WyJXM$m6~8-4LT~UB=3@#29QE_ zy+6*$xGy09H({%^(`uhil$HWbo79&M*7BjOP77n+)-onyJ&24A5rVv5C&l{)u@Eo^ z^uZgJt#@I-FoCk3>G}cEptLwR82-T7&OHO(8k`PkU~&33QP9f@B{(WdiP`a*y&7 zK_cQ(CPJM8Y9YQ;oB3gUp`V|hOm&Y^6o_FggkftoyY0-|9c2J{F9G@Im_$~Ii zP`=D|Pg=778rcq3e=kjnB0nRg^YwNxr9fIAG@2O#vpX;t;C#NyS^KtC2`PNPQ^KBJtQtJ zynNBXoKp2m!lpY9Bj;_oe*0vbw#GDgpIOoVCxF%c@#XuP95C)#2YE|8M#0 zT2ONR@o)k>G^4(fO6$XGb`FiBmxqyOM8aZj`(`uMZ>88w!( zIUVgi3YRTE(X^dM?D_e0(spKo#~hsIZkL7t#gy59oHb`j>29EoJKKX63w1qLj>l=ch0EF~nKAYoBxJ{?*1AZ1tgn7al3iy{&eZ>Culz;-oydotGwx4UDPoZl8Igwk{M4sgmM@2(%5JU~o#{c5u z4!SdGy_7>}B#r*bId9~{-?OrV$MWN6z|DwC^I z-jjMiLU=+~D*S7Ui2exBtA%vCJJD}q*~8pRboQCr>CRnHul{4IogWS|TJ5Gb8~fqg zWk!rQLM>}Wb5m?)*aC`|$t=7s$kHmPyX+DaZ@cV?I84>Nuv>e+bhuP;N!MXVnihT^ zALU16P;bcLtNbhLQOC%aBU)=$GvuEbYleNj`NE+N&*ebNv z#AGJ@w;c6S8wh{z9nvrGWd=2kjlaK7=^lmjQT|DdfirAb=gj(}YcP&k)JTvUODSgf zxH4Kl9gSo=uU={a^mR^WFbv#VgRmAqg`6w+!zp+Q^cDyhT`k1x4{hMLyv~*JL~i8D zaF(2XXDNf49IuT!ZgDhC7#g1P9VcE`A}`+t+)Ki=6MDR)7yXp1+_6?GNTqL}zYMfp z7q%MjZj?r>FBm_sjomM<5m1xbO3Yrn4d|9JI!ZG0e9NK$@*TtHflDU8#lE;(@1^5V zVO;7YSEY!UL}RsAGtcd}4c#%6yr>gauc`6q=SdR+odMPhO|9P9XSHv4rrW=!}2Uya^=Es(=uHeyOp>J*uVw3nbR-0!`LP>@hP#y0xgc7miKUn6^wMxGk=jBJ6JweSna!#5+~0;HxCZ?Voi58N;;69m7N`Qj^AEmiWD05#2_n< z4ND~1Nu?q>o-%z=;l5<+xqT-O+mQ;E5Bw;W?O*5spVJO|PQx$Jn|J0ABg9kCT3{$I zswN@G12Qvhle+HmrdUjJ;XTL&Oh|_G$|vl2?Fqax8})*jL08m*Ap2b$G-=}ZeV=eZ z^zF0Pdd{knuyVms`qa_A!>qDx)qKt?sf4-4*1XKwy_<8GKkZs=?9PDxsFi?c(dXRD zou(haG#)1BQQf~=mg8dMn_PVT0KSOK-4WLEb3<^<&^25S;|9Yk_o5~TuEe6(JM72I zkcIJ9Eo<GR9CfG9+veT#e`Z&@t^Gb&a?{gzq*7@GiV!C4vg=~kDmRc zYd4UWqo!G`A4&o8+q5L%p-afykx-GgtR9B|D!yY^v>JA@Ly&ceo=|Q!`#$snNbml`S$O7R`#b2bCY#a{Wc*Vf09hq*h{`R#6 zzVORo9XwCfSi%h`*k+^2ASO?J)R@ms#juE8?Vk!YyoHU_mtC8^KdCTq!zbYH-IgUv@N`{VkL#*# zbAFH~#@66`u)mcGK^a-OwCU!>TjeAISS^EL<$Ng#@Cf(NdVI$OCcoi5wcZAQMJf0H zR427VX*y+MNsaqkM0f7iLmI+fker7dfChaTM9qUp?!LMe{#e^^=<*LO zbKOJPRhB+(uG5}0&>VbK4);pcS$1Nj$w3{@C;t_JLYg~_5#8Gg-FKdbh@{D3-$G=1 zAGn3+K+VQ3s7y&%JHvB0y=`-8vF6Z)RWzlefCH)ll zE8D7$f_Uyz^@6?J^@2|$AkQ$k%NpbIG|Vakg+Eq&l*eu9>9Btlb~zXzfI&KqAYg=3KYaozI{oN`FF_P@+K*xGKrvNvX^CA=Q2;~NrENCh4x+W zNj$%2NCY=5>?(yDP1g@sCYw&G9QW41heCOV+m!RVKt|47{YkT{sgM%OCch5_@&UMD zk=ZfJxay%#_Qm*I*MH7w0KORGZV6WSw6tNFh830sE#)ohVw$!xYtbo##aDyAKBOTQ zf%v~52W~CnFwM?5v*{l4yrXD`{qE(glszU4!`B zp)Rvr(9ukMX4f!Z%AK7sXNR2Bblhn95}Mw$aL9i8RqnATMSW0}iku)f`)WJSkvUuz zf%`Eqb+SIztU(AngHVDeOR0^}R`#%?x<2r0@}BYIFm!Ev7`R>pj3{R0nl`p@Ifwyn zVTR5}38Vf7q4_igg{~20I|pYkS=rKP1$NiUIjVu4li)!Q(S%Z+pMd;*AEeRZk~bdC zU=@O0j^(K!B56)%RwVkKLz$e;!Y>o7vjngnsIEKIqG3h->(tPT^)=S81b$)m`|% zYTbWU^Ks$|(6t!_-q*sMm3Di7o=i?K9gV`pm&op$+f5_B0a#Pv~^R` zE`oWbX^`9REDF$(?2Zs+s%_Wc~s-IG+$Xm86< z943bx9NA(WZpJZ$U9qgTTC3ql`P$_6i?4`-10<@`WAvYyf;%YI9z~=JvoPm18rt1| z-=KxtPVxUclBZ^6Az>kL_+kD32k{(iy#G%S&&JF8KS4YjH|zfi;$5LN64#d)=LTrz zL?cil9=0+bj0%HLM1G=?h@i=cU3X!}Bjsb9YKlmBL7)D?K$SR^Q}=p|^_mj=Zo7`Y z-5S9eZ8=W2xompMz8@#l5=RI9^`CuC0X$>??hg#yIGE<6B12I?U*C_wzP@~aes_gk z9g7uXmYo=0gy?_*Ag=y9GB+YLppHiYF>3JkK~*LO zBaMnNN@5$Ho?DI) zv{K|Dz8;iTLQ)fpTv#};Fw?Ft%|QMCHn_%PqTSuxpg@oVEP*tQU0}$vf50<$QEzcctvxT1PBW%TwsAt;9s0XP=TQxCai%tt@kykA%1^* zv+DWK$|J-`Ok^0KZYqR1dGMfm2|H3|axz@S@&)3A0LF_QnbOS~(p-BcF>{&L3Va3q zOJ+DEu#ceC{#p@OQ{4YJ1zeH|Oqly%9+*!$hqFTf?41zf6>^{I=H5xP4dl{Myys)b zjb#J52O8Wh47ARYJ2hwe8|lLv5(O1hY!`*$26cic2I9YhKXm`aSEW-*Y^XPwXdlmI zI7kTK`*m*?l!%QH;_~zs_Vw-;!f|Dladv+5nf4;giiw#-JtHlmfI~wF8lMaz zCp!v&e0~`Yp&)!$AnpRco8sMQawl@^orWLWhpzxw|958R8<20qb0WABJ)F3A;&^>{ zc^Gj=W3umUwePGuK>ekL?wdLO+g%**e*v06WxsCD&cDOlzvKT$7>s~Bd;LYgiPjB^ zGk`t{X9MJaQ!QYBOsfxr!rc)69o5EyaYj%^+T+A5e;^_*C@lU5hhsG0o-n8(91C&y zBbh(A*)OX(!;vsU6bAn5-Ga*!7XGgo&R-BG-1`HA)8+3F3}>Hz@2if4prF5eCMqEb z1f$VlFMu#kPNKN`_7TAu5(@MDtz@8(AQFYer2ug=`T^}wXuz*)#nlypIm3Q==~v)4 zhJz4rf214cKY%C>aC64OU2vQJjfqPGgyGT8g;cF)SUmNfC6!%)f82qSy$*XAGZe*OKPr_LwM4P=*yuLd zLl7Z|>xsqGd6-nE*jnXk!9^=(%}VAQ5CJSaBxg-!#KtM-w0^^nv1H}=jD2fqO&(8) z2Bo{1YMYH-;K#t@k5rmt=})|tK=B!G=jZ#gdwbHve&p$PRY_KaQ_)j<>#*hwCiT(N ze>~3CX~5udsz@BFmZT)I(Z4{1b1o2od%9*pGL7vUSTKF|)wrF<`x*@ztb|I8pw^iQ z9RaA{qurjD^9I+Pf+$6rw<2>PNvHEWIvd>e#4AdGR>^qZ0df6;_~yMuaXWLS#|nL_ zWF(}y400ca8au@Ez(jJ-9u1J@sy{(C2q-R&_jiDH4}yw9Frx3H-X~Y=;+91OgPldXII@ zTn~Nn1MOY)ZO8S>10f;EoIGjc^fBMUi&(PZ2kziYCCiw_52E}VX0HQ@x@x=v>^qey zi&`VNRfx0iVZGzlAet!HAyKqPL0)=W z2){z~VB+l?t>=5HgEgN99xBOqF1G|XNf?;2v*y>f61{5-yn6- z0Jh+}Y}Y@$cj*nRoLSRiKEF^zD*|o(6Jt^yfU4+Q+9)zY)V#Ag+yPlFCR=7KHg&;B z3UhugYIE>Fu5ieAhopiYF&nuihgy$=%LEqadQ031B2yyDyC~AhaD<$pKwS27G9AUC z)M&Wmfgnlnj*4UpjnQNzfBne$EMbuRnd@h!4)SQO_#>*%h=7-Y&Qe~&_8hfegXFZ# z`UOv%2Pk;&qza2q@MVdBxKunW@uls>ZrIp9oV+oTMiKayfy6~ik;5yb_Q1$6uv?79 z{v3+V3EfOIWR{i8)c={;c2y?qy=u2POMvPptAKc6K?n3LM{Nlzf0VOI;Iq_z8X_Mc z*658qV*9 ztTmZyF7;la49?T}Kv5&_u@>|lDU;3Xs2_+S1j*mLb*tOP2D^dDC&)E&JMnp{<5&8G z#6Op^eD*18@tRB7e_E)1DY1mvup2*~LKM8~3dlalOvzzL0w!qPxCnTxdxB?FWGsok zZuK#m{k(7Rn|JT2Y1clhd<3r-t&Pe|?kmr;LE+;2%*_O@99FHv$1FLJeUiDN;<|!_ z&n52Hv;s3lcoXnD645Ic5euPQ!Ji3iTO_AOEe1#0QYg1bf2}PM-z|+5UwAcTvT6b) zpm%bUC^mKH4jxVYEIO!v2_n$KmmSe$sPhNfoOj~~wguo-=+S3h-!Fya+df8)bNb+?Z(rfJIZlNRCSlp%%-`+_=sf18y%KDX_uT8q;ypV7Y$F9nNz zA;_Y#$bLQ8_vwe)^O9+P&X*3Xy|W=DQL!^)3|p>0ZGkmJ0LFUVGg9TH7exM2=7W=y zJ*g@wo<}6X69a_$YNC?kOlQjl3|`f!jMuU`VI!;&u@r1-OF%g)1pAM@%uX)6rVI;J z0G+iLe}?n+nA4D-RcU~-=H5~aU8Cd2lc43z<;Czch zL4AQ%5#TpD)GN~6OC5%o;yGEK<5;%m>XO5ff9$V4I*aX_{f~1v{N^OjwK0svq=+7A z({|m^ZVqCeq{WHO5bn$Cmb*kAN?*EK#T-E0X34#cE!Wt?+x9~bZzMi~WHbPHT3H1b zZ#OZlzaO^74@;8CP_Yr8s%R+$Nq&v9B(9#2tW&`Dgcv<6X_ ze@Ijpy*W3fon`aNc)72QuRpO%FWpY|TAm2z8lyq01l$6sH$OmdL2V~*MV5S;sgXv{p{I1#j~-)fU z(AjqBo}S}-?)7aieBwtmu*y`!&T&L9_s+|vG&xxFQyZ*w_A09xN5n>C2N^uDSEt7p8Vel&f(2E+gI2qKjn?loh` zKs9rem;t`O*lLt&@Qj-|X7G|M*YhyOslye@Xe4z4asLh|eoVJdV$4pF9^|P? zx4ABXIcpY)X+`I%b4YiX<*Pz+ZG4Kem`LYWU%hyvM6bPjHLer%5f%<@{wAq9w!ocK zX?1k^Sx@HO10@Q2OIL;G0rZ~TgtvuqQr7d*3_eO{SCujovngbLIo%H|e=jw$6L@J5 z$VGLFQ=iT<%lP~))Z)Cd_JEZ!XLC&HP-vTyH4@*y*VUiB_>p7dd|`2jmGNbc#EwOy@Y$@z2pit|F#(*kC{<{wT66Y)JA9=gHXq1vz_LF z!}L;XWMWN@QDAO7e<@`GA9cx$Zjqp_Q>Rpgla}m~`ioL6CQTU?`665k_tzYQI zE5_xSNsVpu%y55r7lZJ<3Q}FolltVhK53C`8=x`ugREp*e+PX2t#fTyek>;?E0T^Q z=wi=Y!Ny~^^{Opf#S&*%o|<=F^o(=q)CETv6vENX1?&A35ExVr*7UMezUvxUa-Sqe zO4l?}@iUL@Y?-gY5%;pVD9beRzLSj0clDQd4d9DH^vKnbM!)Cvr&@?WJaX9D&7MedXXir zewt>{Y4%IV{&T?RhO_m9PwJ*y@;ig>XLnVeA!qi{MSg4m{P0ynd6G8^8J~}`-4sLT z2ljXjX%1}$*9#wIy{>FQevVwByZs`5;@}G7r|F)af6{$gQx%tYn*h-u6voR~N`hXE zV*EhYK>D=u&X&IQquzz}<_|rs{Fx2Mu=VS#Zti86RG(5R%n?g!Oy#WQ{oaleKF!rY zynK5K2eq`3JN3278hA-hMA_c+3E!@#&c5Dy5~`3YlrEurw_ZbkiMx zie{ESX-!y;F<5&W{tOp{H-sc{H>8{eKb=gR1UF|&QP+5GZJ?B|(SO>UerrRfy- zW|gg1nyekl%Z$(Ct=u?S!fN65@D6miX)x%te4PQwYo!vo>aHo2f@vfukHD-Ej2dko4NE1G6>WN ze|{(mL*z>%@?Tzx%E+F9w|(B#ln3m4jP6)!r3O;2sk4&Qp7G866t_(OFv=w$ zZf&c0W!^3|c7ycDk%awma+9-%PfCg5e<+m&s@^+0H-z9RjPpRLmp`ABt@H3A>IOOG zD0&OP0DXhjM(Z}M&?onP7ZW-gd0Y(QTxt=M7ECALRPRl#ZuYmj3^WN840;r_*;uos z1W=Kj^pp;|ThDf$o0K#YIOHoMEpGm%>xW)hTvS1#D;Z0e+sRqO!2?m_%k#m9fAFR` zIpE@%uu`Y}F}E;p_gExD;YM5Di__@Pi$iOSg}7~q*<0D^n+XEc8Oi~W=ZcRWXdHWTxsSTasodqCi_xO1mLvtww-H_f5;8A`l^GM z7!vbegZf5IR*S8^M1EsXLOPW~^IvHEQ0zWu6vt~#na+{5|4wq8s1QZ5eYg5Da}%+c z$C5uZn6zCHHV_~nStdmfU8p(ZY|sT~cY}Hi?ntbMJB)<4O$`}u@#)A4lFr#2CB*PT z)k_y6gZt}}#3+QaRqYRbe;5)Jov_E{kLQzOc8FK79s5t3KX6FC{%~Zq6Qj~1V+o&} z-V?d5H*yohIrXl+;i7d}F2ZZ_ZLAFweY+(~IM+3)gMI zKAZI?DiuR%avD(=^eRh-)va$HSiFzU6Sw8~ws5P2t!68$LZw;7f0*?hgp@9w9umAZ ztLZwJd*q%ULU+|>(!aL*i5BmW3?Q}5rjJ-Ps%I)sAnhabWQp;JBk27trywVPnsU}; zAvU6o$)w{eB9ShPH)3Ni_M@Ke4w&D$@`Iuba5CNcz(9<6`CwXDi&yP2$(N%2w}O&n z-t5E}N8YhHR_^`@f4_>@z-D%><}>&B;5~1@_$ngGclp&?PgBf?_b&t2@J@)ZJ-6SE z^9!=_InQJpkN1BB3ZjHer32G8B8+~X!4$kftkzE+rY}i5nW`2h1#a5EREv~%zDpFf={cDzvTip@0JO=;rDXI0|&e=}waBCGUio2T*Ncj^t( z>k@6M+tE2R*S8QQBE6!stejxydt0_pyPHylW{CIJ43TCRhA5X_ZDO9EfW_Kh@JEo$ zwA%A3P&XBQD&!SMI};Hf@5vtL6wu#eh?nGnpq`smd)1W*?RWgS6KZ|5r8L=_&5@z< z3xJU6tolx(f1Dxs#QnnDvDJvGP9d9<#X;X`Bg|F}FJn2^fbXM+4S53{)$Db* zs`BT>bwYUnd8HtHtMdN&EoGI_9gX3-AqqYg zLp$9u|7qi-p6Rz6l2>hqRo@KkbFaa|`Z6A|V^av;?rxMQav}19xL{akB6Ts3o;JPo z7%8z3s9u|{6PH`!-W2{98u#ywk|f3Zr+PM5>IjmZWc(G`{F?7DR!ae{R2|AFgT?Xa z$Bzl}e`kXIyzZV^+$~>pqyM>bO3a)$O>r{BZoig2N5|Rb_7qcWsf~$D=nROHKy#aH zvkO5?nTb`GD&}6Xj1JsAknXrrZSWv6&C)aEojaXGGDp#k{d9nva-`7thQd&BPbC-) zrv=;UEm-B<+t((qS17XWEf3sC=Zhr2Bnhv%e{sN1=>_ z6mq|X4dqi)_p<%0SIorFVsYj`#@w-Uo*cBOzR#3iOI8^GmTtE^?RpY7e!cLPG&NyE ze}Inscc0GFTfJhr%!{TMY!aKH5b~K_<*!tBH!bSBuVM_n@{)==9?esX<-b$0=OJTC z(j5^DmepPxw=s&Xl;a9n!A!8_vsYI-?Ru8Uzj|IjH*N6X!+YL&_gB!mt<|^N!+ixC zL0|f|qv&}#M1-XX?)j-dJW;_@;*gxOe>i;iI`q|-)$eExIja>}V|9uOW_PGaS)QNv zYr}97Pv8DL`x7kUW#>j?PoJ^bN3-_&c6UKNlESJZn#o)VE0rg=?C=kMkhTm&%%Hy3 zlX9@@&O@)M;Jd`e7uME44#HBk&fg5hT;9xaJ)VY;!bz{vFQn(GAmnHzrjb$&f8_U% z8+{p~Qc>OWOBqh5`;bC6HF6pw=PS1lTDZ+aegxK!25@~ed~ze`BvfNuOOMv?Y{*)f zfy}LJH<3hM&N^&YAnV+l2U-#7JyA_kp-IW(>G>h&ajY4ri=W0TCTJ|Ql93D4a<+`< z8El-ZCaCfKbl*@t!sk&D%E(hJf22LBZ{G4y_4aYz+K(GQ?n;^>R)$<2y-U;A3eNQm ztM>h&Q{mVc_ip{imA0J9H6qe=P}NEK4Y$2ACV$(B0@gLMgio@h_k{N^9j(ZBg>Ls> z7uHXowsygA6DVwuQ-c=A42MiAHJT3#H9^Wr25p7ndp|unEMkEA%NpUwfA(KW70J7% zukP_gH>SpVjFuX*Q1fyXHjzvM*&Fq2NotGn`sR+QN)TcmvgMO_Tx~y$lzZdsO`rvi!1}Uw6dK;}~E)=AL{eT{%;cMBF zx?5KM9!#jhH=a+J3BN+Yf8ITOF>J*-CN)7E@|yNU)w}Z$Z=+9Z`r9q0KFNveP#1&j zryV)>7xsh{p6#pVzPyT^PeR->hQ{DohS`ut9*!tZa%k3NqZQlkgOl#n7#!L~4~ugN z*OP9Zd9(Ee?Tf`9uvjMeer|aKQ-*O|-8#nu%RK*lQtTu@{Jx>(fB3eL$SW}hA6YzE zSM`;kukD?>lvf#NEoDKVh(cqN>AP9&;OmMLUu%|N9mg8XW6KkfVdy_YDWX397lHrw{KRlf5j$-c&* zq)RdoIeFx%W}gGLe?pmgd+Xwt=QVHBwbJYpi0pncCs?-tmRdOYhjGB>Anr-A5~;6Dvespd@|h#?D}-E8;z4tT8q2=Gi?f8my>BFY2UEb+Z|tjiF4 zomh4e%t#{nQrbOmw0-|eUnxc=DhqWTQEl(~wf2a*hSv=7-u;98yA@xy5=sLRja+~d zX!+~g7GD3f!Rr3oodK>=SdH7tV!h1VE(M&l$s)afSgC0x>eT=o|wU zI07*=mQ@245H&dpFHB`_XLM*XAUHEMGM6Dy0u%%}HZnGofe0vnjkg6!)PC82beC?H5kiGaX6{RV!2|NlAfdv?zD zTz6mh=e|GB87rf%0gt>5+!~?+hq>_x@bXIn6txTu1q1>7{6f6^{DQcwtcFlGC&)i? zTvlTU0ttn~B>%8~P((mLZYY@&$PHzv1&0CD-JJjeLI43#NdYlQetv);KflC3hH!)= zKndglwE<}H0@UF!2ojf75$@uRfZEx+q4NCm2w(?u00bl?#JGRE1LU0{2q+i?189NV z>>7!u{;4zqzE0I2K$12s*6 zjtd0#m$Bww2Hb$Zn*$Kw75F#Y-`>9hL1Dk0L0~Z4*#!jihQjOswooSsKu1NB*Uig~ z8vugY{4xZ8IU(UFe~IAYz8T>9C1W=LJ1AtHr|K^7TBcLvBNM0n==@&=7Utv&f zR)*Oq!kwKVFgGObulkgr2nZO}cW=JG57!X}_k{WWgKVKN8{1zrY}{S=j9^e#cZizO z-!>=_?v2b2;sy}m=jRs}5&}S60T3^+J>RbchTblJkl#vyUt$yoKVKKP3&0jd1L6m@ zg`j?LeUTs!2*3^D4)OE-Q}JJfDmtws3&NU&W%j^iRPae@_7W-|N8v_-`z2IBISo0QQ?Pf5b1s z4@P|n{67!-Zg4^m1!`{H-B3HA1xIZH z?0-#7Ab%~c7Q_bX?)<-6H8&7y7vy1fPX9K45{guTdO>V-p>AON-?Qt+G)2=Md&j}En5U`N#LfkchuZxsZ!JO9n73J1VE@IAOeLI6Is8-&WBd4o_HwEjin`~W`P8-(&RxIw}I zKEoS?iemgPl0ZcP-BgCM1i9TPQDs}-AXM4le~}1E2ZlSLdjF5)*Aej9`~gwKAUF1? zw0|A{jRA_Q?TrDdW?QJoA0EHdaCgLi9}XxJyFVbRTKj*YFzPhyyY0k-Ld3f^vnShU^bLiY@%67?d;oKWY>imm4*z5Es-2 z!~bZP0E*2YTm?|2BX9EkLJ*HXxQU?5k*I6#-;k(^kWL_^{U0GvS#H{mxTM6KdK`1kt_0`Y=?ap$JtV5wk-+Thk7)$+8SJnQcTg$eo&Bad-RJ}XmuZ0ZFm zN}nAG6-3R-cTR3Zovo656)e4fZgPA`!S86X@+3V!Kl8#{{qX8i%rW4Q?qN^aMK^{K2aRcW^7%F_z_eQaRk4lh2&`nq=+KYpwAvj-iw1_!cRBZqb4i-c)x4G){X7P;#%1qULUcWXaC`0(?ik0O{XP&lruea|ro=2?UagIPU6Cff>S~1&Ig8d->J3!=pbQp3gox&vTK>R#&ZS z{4_{-gu3z%PY#(o2S2{JSY{vhTDxs+n#OOt&k*)7fjx zc-+B@KTlKBqP~4h)E>T%$*4)(c%Hpho%yheHtYk{!X4MJ6nh_6GlwG5+(&ha?v)ri zWBH$Ax9rLK9<368!}<9J&W@#Q2{<2BGg-@}0XOzir?1BDS2N4AXb9+{yA>mjLV|5G zf?e?fX29Y32Fns(2qn$F12bDnChm@bSIF*cwrd@3G+Z8+7$Z&=*WGcZX*6>s%mdy1 zXkW44VT(mH1!%?v9@l$FU4ATV_5*PR!aM^F4RF!&$M!gX_;MB={A8O^u2-SH3foe# zu_n#sPRn1YlJauxOFcPPFCf;&1>kx*?!0-<#iRO}sZpA|@+@2@mG7+2rFZQ`;3J0q zb@|?8|NT^&#G&@=H@0jmlA0ogHN6|VZ3<~BO^R=~yYpC6^^s(kL7lH~sd!ck969kGGYTAxO zt1ap(*TN*0;+y?Ss{h@lw&OD&ZgO4NH&`tCvi(qh7t5`jJj{`}xdLER+yI?V(oXK% zH|F!A)LXYqFhZ;3o?Fg33_eNQ{=BZ;8{5=WC}Jx>b1#eBsP-jwfRmA^#FGnj{fUKi zE1u2>*0$Fghq=kVp*&yM;S!4CY*iUX*|^nK4l2~F*{!?j7kvHSuYJ}LG_IA{iWBw> z*+brcrIRID0q;Vu>g@f5?(FZ-)AAag#CilFfX`JIS(N$TI6qs`VCez!wQV0hQTgEm z&!+c|ba|DS6=fSb6t*rz+rc+TA8%50NVA@6hRcGLo!28E6P49~yDG^Tgl14!&|FIM z=xC|O$*-u5Najnpqb8S&x15X*UmmSTzuT05W941z_aDKwDv;-2$6V50qj$9gY0#bC z`a&6~bM}t0WM%fju1DKz^%qfe@x_#;#=Y9~?1)qr!^8Kh3rW6yXD$F9vXu@qW^BfF zO5FvzBYRcvJ2IZeB@sM~N;Jvaj(b6bSHXKG3SKZ<+FXo(=ZH^g9t{$L4!S zxuxxn*T>geqF>-C($e+g#bUL+P>jugAs(*HQ7{XZHKJn2ZZ`)%** zne=uJhsOBU&2r-pLAbs6;UY!$ZV2g`ipBEIHx*-ehaBkv%t7X37|!!9<|!9{MWcG^ z$+9-pS_$i|rplF0o*UD{4PR^+zHjioed91SLbDsPE7K}FS!XW$77g#d{kZC9X(Js{ zCRj~GH}XDnl%>YusS7Jh3B;z8EeJD_$ilkRs{hH#TY>k){ahwkoo-gi=Pr5Pq_zGX z!{Hyw-HbRqxyXO40c9hcSEVC=2{A#7p5ew_0dhfYIuA#6T6Mnogw;no{S52y|a2 zMOFhj>(hy!mh?hHF^9C)(f+f;&&oBQWG#woc?DqJ4^Eh|ewSi~KC@$g+u08SKZuL* z^?eu!orzffnPbo5ja`2`MV4L2+QvCImE!TkvXf71M)YSfZ1}I!^Gd!{_RZwFn(vt; zeVtSx>W{!*tc_BptTf~4Nr95!D>EZWP9z7388$h!X(FW={g!Q;3J(UABsUrEIxeD( zwqeTPw2>7 z+M({V7qaNN^)@rvOZy2fuSjyO9UcFvY!&Be!CA_FCVG`W#AB7(_sI1QrQ zTl1hZ_d5DXukK>^XEN5s&r(4;xi4}!sD7wKu$r2XMMO^IwOz1UbfsH- zO2%gw$(|O;{iK+G6WQo=i=iQ%lhkL&m;aH<`?#OdEKmvev(fzxfw3#CaRl{Y9Vnn_ zg7On-#+(rGvXV$tlMh}}a;CDIf~o0uj!Elfam1IAd1)IAD+ihyxdX|PJl(t3%6@_N zvYPhO;E8(_``TMv+T)y|Cpf1yYT-Jt`8nC-JDVdwaN+LMb zfCt-le$Uh4n@4_%CRN z4Ib7Onr;n$b!mUp+QTNCOLTY}J$4|fqFPSSqQ4DfOGA)8ze zyTUGL-@`m8?e~`vM6bfXKM)G5SjC3y_I+a=jf!GsTPLE}!^QvNAeiCd!$K69arCB{ zzTPU4abe_o^iwf)HgpDgQ1v8Ggfs2Mi3{m#qrQEA6^_ts{qnJ`5)f-D#B+|J+Q8>2wqsyT|mi_AcROo2djUypF{ue~KCU^%u-uIdM z(QpC{*_*|_uWqBeF$a^+iND8$6&^kKo>clFdibL^PKZsiJnNomzy867Cytjf+T69k zcwke1%FB;rYQ*z);_+Ole;m@r>rjL-;MU!|9~jIl zN~s=E?psg$oW9tu6qR(b5O;RE zY+-dlJli{7abb0;1h_YveY+yb$piFb-|z>ormq%aG+J#SaNI-DykR2=AG`uh3opnrBq#=${2_OqM%;zr3Ka$ZZI$?N2a0_Tt!AQWlOJ zq258xt*&_Ujbcdbu+ogL%Z|Qm z_c5*m>xyXq+fNmDlL04n>|w2XdY&Zk(OO)`dL$g0nr+^oS0V0)K1Xrf!qs0 ziJqw^6V~Z-?p+Rs`tQSk>qPtc<|v$k=?IWG zBDvOsFX&II>n`$m?(*055k**S#UyD@_@>UfEEh}_iS1)Isp3G`WBpypAtnRK=R?ad z`pWI-Ru^?w==Ck=DDB(L;B`szCwd$unIKhblj?|RC2PA5zr}2hW=p}5W3Y}`##h|% zTvynSj(0&c6N*lMB9XLdINjZ9CQ;_&M1p}|LSTynyl@Uied^|xL??GV>7_CMuK6{$ z@T6_20dZVCA4iUc=jMX(4oVdu46m`v@;&sqQsNqMBk29y9v54Ety3BpYmit=Dwgs&i<9lZf5)90IHg8f4d4Yj;XYJ8rk~7;a zWP%a+B#d~VfURd(^9uvsv`iW6>$!qkj?ln41EJ(JiOf7&3(DfLCM3}d=e)b*BMC4r zEXts%CleKaOqg7?2e|9+c#3~E5TtXIGzhRg+hn2@Bo~PS>Sm)kS!Pb|7mrtC=E9X@`p@Ky+XODta4?uijMufbIy zOzW+F1Yd`u^QXJ7y)DtXCGhHB+b>IfM7Xe9bZY2{e3;vPN{FU4`)<^#U!ePu7W@Il zBk~7-Zuq7e+}d6topPBtb%F7b?a{{`B$Z0EkW#mpzJ!#4o47?K!b28ZOThgpVU zKg}GB70GSo<=*<|%Xn!CN)I7vjTVH`pcr~QHt_n*tTXpZ(dV0aS+X2IXrSpDA21;w zgzn&^Bh)v@-BseVgwE+(ltcIBDq`+1EIvzrcye~1cL|U5NYye0o#Hjau+wGskD`!@VpC}ySeUSW&pyswhan|gA zKoumb_+{psSoGTS@E+V#Sz3~9-00JigV8Y&1-!YV&v%XjMHgkXmWLC_Sk(7@h*)I<{VLF!=MW9c~x`pW>#eJVLc!9Jd{iUAy- z)X-6&wtt@kG1SwCe#0pANan$|OqDs8MT2k>{w_g@ZB_Y!Fn-R$m92fJmUg&f<#p`i zuj=PqI7$4Kx(~mX?=GxHy{)=V-hM?c^)V}xl;fSdmzv`-2~kdFZwJmP2HXjMLf*={ zN$94VYn+GFEfJ8GD*+4to}~qW`^#w(w^oe1MrsVp zAGSanRJ|tiR^B|%0GGa0dJOAxEz{q$8{PWWuPo%9fQAf6iXy94$o8dwtVrzfl|B+$ zSoHR)ikkhLWzGUSfwhr;_JI$guQ;RWHGkCf72hg)Oo$=Rt(7!G(cp56Vcl%@M;+1R zn(%}6m#YU`$@)H7BGi2cPF!?a3gq<#`W!5z>3$%%ueboFX>fe9SB(A7GDMqffObbw zd!90Oy1yi}doc}o`z55+>@xRNFO_MLXhZpw5R8s>UnXCr1U+_tvW6Je;|DglqR)Dd z$q-)R!}LWg@4YAvtssSm1evf5V9WYmFcA$pwz!w5uB}0@g_LyLy$kdV&QWQ6M$?SZ zGsX<&lR~wpEbe!bpv#^|(R$g%@O5K1AI8(bc^760={KDQuCm@Rsg}B8+)qYgY?lRz zXH3fgdQV@xl>`-k4(^W)Jbj~@z0q34{*<;SHOcrX3840JgY59vcKjHl5&bT06hBJc z)+Gz*`0;!6slwT#V=i=Uyw>u;gFAiWUUcS5Pg1}rSsUFjoxXwAcLfo$; zm+7>*Ka+H)LJ~henI%6VImjSUML(XIx0zgh_W7IV#|Ex{Ch3A&0M|qBYDHDNy*|np zWVf`l^K-p33b^&)4um#bLZMFXb%%HkkAIx6(Z<*4XhHM&j;ySNrkUSeeC=Al&8FH< z`K2IjF`&(DQ=J0Ld59KOO`>-1@#lM7f&`g&T{S&S=t%6k!P&Bb=#vuzsh833iGb|$ z7*x?{s^x)yR7T%Rp~K*(*+a`#2lbjZP4@{Enh+1hOS0+Th9_{!ED*7{2u*sd8(Xhc zczi`FR`MtuLH|yKCX}~5-~O%g&0}oa0-)m9oRxtW z;)9azs6{CFU_3%cPwwazSV50*NJqqj1Oxc#?zb3!RZ5TyX>ty`Jrak5)JwAz!_wP1 zwe-OlB_Nr6CzII{{*E(#C3Z{6=EyPfwD#3p?n%q&dnqRC7xG zgvDjQ8EbfkZA-LHHzc+ED0A^Ey6PpP4kvv_CN^k2`&tAF#5N)DUq~3!qPRLCXB*-^ zNXc&7J0b@J;UjIHQ2AbF-hZ^129*h;hc+c)Jss40wUsN-C5{0fwhH&!Bl=dd;1su?`geV6wS*f? zZf+gW(?!+8R<-+1vuYyev{!(|5?Ga3NSaMymvGX%DcR>?M1%py!*IOKX-}_}7su2q z8}#CnNm>5`->gyE96;wJkC#+^FXk(WMPUy^3mad?Sm4n(Q-=aw8o=;b26~?g@b#_9 z{LMFl5-%LmEp~KfLlUoleFe`}jcD!3=~Yp_)8Y+_|$hf=9=1TnY#27>7f=w3ZhLhHD#tW4xdC;$gDf-Cb!= zYlA+JUvxiXN@V*!y+DT_M<3_dFZ!OdJoCF4@(6>T5Eau%O&9ASJNl z8^I@hM%3(*A(}{{^xS7)OE7xwTn`sWgii_-W4?aOd<-ymA>$E}B58VpO?bkls>avu zG(Z-Ng9&cPg*wcC>>18W%q6USW4gWQ8 z4}TX=e)uJC5JJ+N;(QyoRyq5kq(GyfrqFzwtWg%>r1HXE5(l3Sw6zoOgDJ%O1`mA( zR+0GdB=y5`<%IYt{A`&~CwNpZzTmp>HWkN&)<+gO>_k$=2xBp(`rbPI!0Y}$LZ`g&E=fUci^)F+LjA_P#XEX$q%dppLBnUn|2P*+@i5kVuaP1dyLWR z4MgRcGvTA$7mQ`N((HWspoKTrnMIz0Bs;~FhVJcF5Qg!(;8$CwbTV;! zQ?fgnrs7}v>q@_Wa_x3Ht@`HqOlD$}^C1`FwkW;- zyY#tNk@N2_Rz4sw2z$}%-Q(!+vFb@;VOfJ#ug(`bFYeVE6kyR&2hlThxY?kGFeMa) zWWof>W(jV&%fWir0yri`+h-cDYhvbI6oEbX>OeJGZ{*}L+JuSJBnY-DfI4sPPR+e;rY>-7Beh3iG0kWCeK_1 zrZi(PBKgKv*K@bGTXlv`Y(2C+8OB|*-Y_R9>JBacrnI+`0dp)1~GxgS9F3@)HxU_ zi3#E#AXOvfuhu};xMe0rsvEu@Kry;BRplhrI?dyn5rv~>Cv2OdX&%oc;vj!{odJP= z_%6X|`A)uPE9HgcG`?cw`p#Ja)7U>>9aSWaOtQ&TdJpC&wC1OLnrwy`rH)FK z=;!i+4wTq^0>i%4>$G&cF}DOARNv+_A)eZLPIEuUf{Vb+5s@6GKjLlt%5=MR_jZE^ zlnF+XVdMVdl=x*e#!8S$AuH?V6uP9-%BLM}8}6N_i!lyv|oL&jj0esvc1e zkkY$;cq1H-AkCJNZ<210-Pszia|U{P%Lom#b>|ufb;Bu$IV4h2%Lc~>D_&Jx#D#r* z4(jQvm{}HY+O8rc1s6ltT7rw7USOWbJR`Ax(i&>~y{7+~By=%@dhgCw@3FIgy75KM z9rGa?GZ9To-Bmg^0l8`{o=%(ZmsA?j>L2JE$*CV|gop*fJ3B5G_EvXZ5brT$t06Y~ ze2LW6GSCX=i@_hd>6gKFxIzRZiR5{fy)o>h(VE=EK{)a}5XY*#>Zweg?F6U+8^+U` z?phWb>z~iY@;z&2S;*A84v$7-#*?%REq?T=Hh0H~eh}DxtR{x$Io-lF8d$;NWwo@% z9Z&daKaAFVjF6n3piG?o2jsFM9HpFt5afSgC0x>qXSbPK12m>-VGM8zF0~?co9t#jTGBXMv^18up-7pe zym5a0_Se7+F~f^R*-xiVDgrT$2DVAvlEKgS$(RK>`HVK=8rc;pf}^cVD;eL-*}E)m?S_sZZTLN1ZhT zQedqhl<~t_0m}>&B1EFW;X%j!B8E-vs>EPKFWdgJpUP&~}4!w5!4bSV(ax>m4h$z2E?=tt=}N1V=T`Cb)xbtJDfv-d$eb8{y*+MS_K4YORIx z4rYTsku)1D$7>Eit1l$SK1n1%nLuH`U`Gbv5uAsp@ksENc+z?2PA75a!* z@}AokhRn)s^CC$sVZ0Q?z=g;s-JnrW-0LBQU%cTom2sv=>M++Eoy(B>pqWtx!NN#T z?@i~!-<7@KLRhCFB{3zK`s7r+tCpF_c!&~GPk;ZFzCug(Hv=s1a%ifs|8;&9gD&f0 zc769Mt3X*pDsr8AWiMw%{$&lf;Iv7r}Ii>I=BK zmcNYIcGvEhQi($eR!xsHkzxDwwLAaCfR|b1w4?p0x`2P*DtRIIvZKulq9+w_?9Tsv z909#_0_LpkM8?b@Y;U_fM9C9WNcqG4Dm*R*pC5JzA0asX%xUr=qw*@;A;#7}f{honsZ0+ffD%K~v|}Ncmb({~kYgF2eB8fGC-N>(~hi7hAB^8?&l)j2qu)tS+rs{7ytbn3)STk(0Mq{k9cT5g@uUnTZo?30$O z`QGM(P_c(;q2mC{81w4aDFH!O@CK1-q0_IIPvr8p3FoK&jb%rx5Ex?6oo50*ph>Oy zudqGoE=9w|em!`5dYtrp3X0bvr!5Y-X)ZJ7hrn1r9~tG{fCOgbQ;u7l8YuTT9y9AT z)K(jO7W0#x>fN8_MKy+x5-y6aY}``_{cNw5G=kfU7<|Z1A18%BIuZx&e$7XGP@A7r zeX7TJ*!_pGKX0ZCQT_Km6)iaBB+up|Ys$Li7)EhBS}dwxHk;1LoqQj>7llz?blWbL zU~nPB#?erdm0VA--?0~bwEIJi*K=jg>~Z;Qojld3JIky}L)W@_D6#HeASo-y0EH9u1qQdPHTK zqiHP14~f8a2zgxC2ZS4l>ca8QABQT1L2!;|sBFuEio+I+1?~KH&vq? z!}pGyrC&V;+7hBo;YlAd5|EjgvW35*M82I=%%Y)^b@69a zH;on+TN(QrRrX1rj;`L&d}o{M^}v^R!a`{Pv{Va*T}XsZe`1)+yg(sR)u@h9$14t> zG0P>Y5nK&hgE?31TG&=v*?}&(t7Pjy(kfprQr?AhzEb&H5iMT|6|&bNX%bRzqRAL? z5^s^k3g4)an7mv3X*(4&$es4uMIIi>MG*A*gQ`nLLE_s9g7XIcl5o6OLYtz+KdiZd zxO?lJ(o2v^jK0SDS|hzh!6UOcx|0HOxg4=`N^*I5jVNe8|Gk3H%$I=xn7AJp_hl#nH$;Wu z{9W577yc48V_$wDW?fh>-lNjckI~D&I+Qtqg^1v^c^oiwuj+YY9qEBq3KWWvvX87c zxg|Z==$}2+9a07!6WrAm9Q14;iUyS}``BhaOk#9rp^1i#D7kC5n3oD5PM-uR5%h$4 zzIKR};wJ_nqp@$pl%JJ(dQKK-*FOD5J+XB;rm9E;!Vn%A-#;n8Js6y>Kjy{IF8wRo|8WH} ziQWgMrL= zHU{5#R*A_~M(9yQ27PR@9EbC$(V>HBOuf}9O)%CDcbTKU8i;9sM@GZFh2r4SL;2qA zfJfI+cTla;fjki^TaLCyx=YxuANe4|RJ2qwpNTyGsdCzXyboEd^ZiAe!VG5>soGH9 z7;x3#S~hk&)spEoB zIL{QxrPJ81QgFK5?%Qg}v*z=7bXFpC+6a4?qoBIN#`LbSk1H_NO(+%3EW3P{-%jqe z93}1M^5cCK6jvj*2rmU}k*2gtY`E!8Bhdflo>n0c*dLztub$JHUACR{V_tR2&|ue> z7KPRKT9@j}3mj#UqYR+R}KXFw-*v{&DGe}U;VZP^)X;fiAicyU(eaJ;cs zka0hJF3EZxD>HVUjtJXlo{Tuk>DpGIjIbl@lg-O_)uAPs`q@MFgp~x8FWrqdEIQ)& zt?o(TAl#XPJaQ&-B1rWxhuwsp;KRbJfC^iC)joPt&tIR!IT{yA(3$IG`*q+7~*<;*ryZJUnyK?INIn`<> zuv_`hnJCAHA)b~vLkB3)@}&7}>+;z4?>7uE`&T;Evc^6m82cGa8XfPb!~x?+J4o6WfLOL!FxCUdf$J!JU=~DAy%mN%Y?nrRA;au7$;99olPPRmTb$U2?&gh^4tvgEt)90 zmzFf{z>xy3m|k3Q@|0iQxsB%J8Bk|z>zJ7;0Ws(E=BJPThFN>}oQnALHNxdFUkpZi zS^JraNt9aEx)B?K{8!_$Yj3%u3aSe6zgUx9gf7+q;{r~BX}^x^fP~iv`+<*nfm-6* zK(pN0afC4fGBdtUdO33EuzIeyGA+{bqzlQKc+Snd+j#I*&z&Q^?1z@eSbkDPi0|73 z=#3*5%x9<1eN_&%17_G6&w1q>7W^4Iw>GPMwW~Nulp&f`PrOxC`{S1oL>B^h`SS{Z-LYd}_Ujvcb)@~UdE|;yv`YQ7=0M$8 z)l8dn7Y~SQ;Ai7F(lK0B8pd1G;13V=>A6O^mA@HB%J*&*JK8(jDL3p6ly*1rU-xWm z!r>iF-H?}&Q)h}NGEwb_t+MF4WsyoorT5oDcDe|F#t*M5aMk!ASu5c09oTQq{!lG<=qCxOTl36jR(jgh=~+k?OWPZc6^!4M zD9f3}s-H#tM!UmB1AmpuM<0h};tRXnN0}f@AzUAj!?UlO_FgY+$5@RUi{FMe-kRw& zlTEfpe*IfJ#%W@>yg`Z2f`z9!QGSQ7B86Ejo=GfKa_IJo__fw%NlHSuB9$Af+++=t3$zCFyZwEi^9UKkcaNYrP9C+}%c#Ov@> zd?BB1U{BfXr;IdMelW@PHq+$oGu+dK(LaO5`=@+MQRB(}g$H2WXG5E}w%?RZKO#+L zT-f?{*dO{{nGP=>N@sBK`M3#wxT_j3OVM>iVBU-cp;E4|{;&f6ZMTp=5vG(Z#*9V( zi}#B{IAmoV^Aj{4a2H=q9#n;JvC`B%&w|vQ3WYVlV^zETgRzmffwEOfGej~Qz;a8pt zLta61rl_T&W1%FBHIOE(X_`l0NJ|&oo%Z!R+kOhw30oQg3zxU+uB_9k{wq5^d zIrS{&E$cIyk0a&R_Jt0KyqelP_g)-*F+$ruN89M_2pg%_<9wSob$h$%EzXz?7`dyoII83=j4+yScwuaz5N{__pGmcj-WMGLrUT%GiNiOkhN7z`(Bh0znK z`#z}X#_N#(z5?7~XFZFpfm#p4x^lAlW48AECl8SB^0c0J*Atv*o~{y-A<%18&(4wR z8LH4OgUOq4UG3&F7gU`%yArQe#SwcnsL{qlb5{aYY12L4esq+1>HUU9Usprhsbx`< z!&4sxyPkLsdm7u2MY%Y|4#r2g{3T6t-AsSdbKs82X&)&w>Z$tc{!lv(>du9zxyTrr zPII#8IqB1a#Mes=y&d9)ked@ZaUw_X6{nvcH2lW9`KG7Cn|THQXL;wkLvXu*;oVxR zPzrTd?e(9dZRfpEVFP2KCrA4(cTI&Z*-<=BZ0S7;6{~*Ewn&3!qb?pA?7z-JZ*kTe zF})x$&c{%=^a2H^l`jR9;Q-;h-ly-zEOvzB$ubO!?E8t2-4g5!VY$YNM0Kxq`nDZ0 zGo#kLitkVE@I*2RVpop%2M+jsMD(%`1@*l2DTU+C$jUv+JplYuoF*5nD3rW2S5g?i z>l55Z32}puh-GJSlzZ&~q!9|aLXQ@)^|S%4>0(oYXHOtH^C7$9&>tG`d^U#jIu)Tc zk~?B@S7_`5h9tyq2ceRGaToSXpNi(!VsX}0siRqjNLxVCJ_8PP4I2+^rUrmrq`a2O zIFygH1u?2Te!m|l9orec=9SH`Z7VGU&y<^OujwlCS_t#*1i^UgptD_T#~J85Z-L1p zXzX7aIg>3N)d7*>meHbZp81AMMAWM+Py!G`ua~=VI%FT-3-2F$*P(8gdCk z&M%EOiF*?Kw&SCJK#cZ!MWF_fuossR1EL8kPhWh`){9+Sa|%R3bu!kyh`N{czNmHi zpZ!acdWd#pqpM#U3-^?PeU;FzHjQsXf37x0HLih0E!oVuiMDU`;(kwUT?n6F?@yVc zxH)>1F{Axc3?*mKDeJ3WRe82eM+fr(vsylo-ftR&xuJo%BRM(K4 ze$N`S8hii_+G`5#bDR9FJfAUpB4vaK(m?7|%BBM*vp9f^G&(Pc$%?&^ef-?Y!58@W zWCgr@zdzEayp9caeVzNgLI(XQ8e7BLyDSXc$fZ_p@uDbSfMc>g63*bS1|nx#^k>l_NaWSXOT+f$F(^9Jr)>~WuaK)@2xtkwkYdNAUMSH*G9?&^qiuZYT`Bt} zc;pQt(g!^N`5*9ih*C)pzT(Cm5%vh=5$HhZ`o7%LYxFuIefkI#@Up?mTb3Y_oROMK z!mI%0CX0LOpOI{#8x^~^hR3hmQ?W{?(q0!WZV8wcG<{$)S*%$2k&M(9mkY87-{N^r z=6Iv61RsH=Y)TfKhdW^cAa9msx@Z#YaBR_7c}njk3_TwzFT5myIyXi*U*Pza?h@?W zJK-4(nKT&s-^?bj0F*Nv6~YeY0ds+QLA)RiE-(`p7ZVFMP}#{`#?;*cz$_!g!^OkH z^}i74LO5zX3NP<}F`;Dq*g!cOS2uS6FZh4iP#zuuz<;r!0f~|^y<7z0$G608)1k`A zr7&bd8K>6riX)p-0)7RFC9@6F=SP*rJyYVaZuyAo)%I8F(%4_g$>?JxzlWrUauU@b zIn<}Cgd5(vp^BGiCP9#G`=^!6Ek|88C5IZiZY6&A)C$FU_N?|1pf_|h%i!Ugf$m9j z>RL=x(u>}IIdjz-UpF%OI61aKmLQB~oQlP$7XtK4RQz&Pxw%yM({6}BJoe4q`cso9 zfD|-u=RxSI_*3D>q7G(@Pgpr{DQb@~_ll${-D|Gox#YJ>Ah?n=8Rogweu%_)*z}0Z zG4CY*|LZ6S4g7`*fbjBh8DRr8ZG0^NFH>xw4#0pLzzg7hS=4ZHdMOew_(eQf0~`3k z(bDN9`;Vvbe=-?>frubCFF&sUh(}ObP==RVhDVB@Uxu4YmK)3~#VaWzBO?a*KOy*H zF9#`EI9j<|1Hgh@|9uGzsc;o7Nur4TWyfvw44<6*?^foY@+8q~WYNrX-a#3Ulnh%^ z!Os1&D?Uhb?LlHer+DtBN=izJ$5!WCSk?cU z6X*f4Htw$;tOZW(*F$K<=E7P&0Gh0C4w}S4y~5ryS4oYS^g|FcbD(vMB$i&*f#8dy zSQ2rf!(>{pYpqnRRXuTDQbjDgv;kTfd>EedXEsQ`7Q0q?QAka7R^ckF5t%QkA<0TkeGwi3|70U9!L)$6@MV!Rk&c4FApa0u)LhYgq`o6Bfn7PR z=nwSzCj*ePy=@s z(V-+6f#v*1>1ezpV)iUwbOeU|D#O=}hv(_z&*C>Yj~VQGdn;DEw>kSq{ zWBb|U1*&SQGbS9G_lFQk4XJ+TLDVc*>fU&KJ@; z+@pjLI3fI}9-#tK00~I&e?ng%?WQn%Ztfj^A7!7Rag=GiX}nHnZ(auNg^TBpIuEJ< z$^c3?O1G@=GtFw-^~1*$K$07oppa0dV8kt!$@*iGl{JnHnupe&{U6gpexbUPKtu8M z>2sVi%4(CEr{mVdS3)7~+q;RSuORw8wrZ%hl2j!acoM~`Z><}h8}d%|*>AwwFp|K^ za$8=ur^2!o`@@ggkqeo2+S!)ImRcTqT0+IF7V52^w3}oXD$1y1#5y%+>?U^%v;#<1 zmLc+8vd>Q?@2Yj>JIf@iG2KCjp{UN7@j+LyPqY`%yyqadyp~~ Date: Wed, 17 Aug 2022 21:19:27 -0700 Subject: [PATCH 04/95] Change logic limb size to 32 bits (#674) * Change logic limb size to 32 bits * Remove unnecessary columns (thx Daniel!) --- evm/src/cpu/columns/general.rs | 11 +- evm/src/cpu/columns/mod.rs | 3 - evm/src/cpu/simple_logic/eq_iszero.rs | 172 +++++++++++++------------- evm/src/cpu/simple_logic/not.rs | 2 +- evm/src/logic.rs | 2 +- 5 files changed, 93 insertions(+), 97 deletions(-) diff --git a/evm/src/cpu/columns/general.rs b/evm/src/cpu/columns/general.rs index db7436ba..affd676d 100644 --- a/evm/src/cpu/columns/general.rs +++ b/evm/src/cpu/columns/general.rs @@ -107,10 +107,13 @@ pub(crate) struct CpuArithmeticView { #[derive(Copy, Clone)] pub(crate) struct CpuLogicView { - // Assuming a limb size of 16 bits. This can be changed, but it must be <= 28 bits. - pub(crate) input0: [T; 16], - pub(crate) input1: [T; 16], - pub(crate) output: [T; 16], + // Assuming a limb size of 32 bits. + pub(crate) input0: [T; 8], + pub(crate) input1: [T; 8], + pub(crate) output: [T; 8], + + // Pseudoinverse of `(input0 - input1)`. Used prove that they are unequal. + pub(crate) diff_pinv: [T; 8], } #[derive(Copy, Clone)] diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 824ae13d..3016b2fd 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -158,9 +158,6 @@ pub struct CpuColumnsView { pub(crate) general: CpuGeneralColumnsView, - pub simple_logic_diff: T, - pub simple_logic_diff_inv: T, - pub(crate) clock: T, /// 1 if this row includes a memory operation in the `i`th channel of the memory bus, otherwise /// 0. diff --git a/evm/src/cpu/simple_logic/eq_iszero.rs b/evm/src/cpu/simple_logic/eq_iszero.rs index 75bb8bb6..e1b33dc9 100644 --- a/evm/src/cpu/simple_logic/eq_iszero.rs +++ b/evm/src/cpu/simple_logic/eq_iszero.rs @@ -1,3 +1,4 @@ +use itertools::izip; use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; use plonky2::hash::hash_types::RichField; @@ -6,8 +7,6 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::CpuColumnsView; -const LIMB_SIZE: usize = 16; - pub fn generate(lv: &mut CpuColumnsView) { let logic = lv.general.logic_mut(); let eq_filter = lv.is_eq.to_canonical_u64(); @@ -16,34 +15,36 @@ pub fn generate(lv: &mut CpuColumnsView) { assert!(iszero_filter <= 1); assert!(eq_filter + iszero_filter <= 1); - if eq_filter != 1 && iszero_filter != 1 { + if eq_filter + iszero_filter == 0 { return; } - let diffs = if eq_filter == 1 { - logic - .input0 - .into_iter() - .zip(logic.input1) - .map(|(in0, in1)| { - assert_eq!(in0.to_canonical_u64() >> LIMB_SIZE, 0); - assert_eq!(in1.to_canonical_u64() >> LIMB_SIZE, 0); - let diff = in0 - in1; - diff.square() - }) - .sum() - } else if iszero_filter == 1 { - logic.input0.into_iter().sum() - } else { - panic!() - }; + if iszero_filter != 0 { + for limb in logic.input1.iter_mut() { + *limb = F::ZERO; + } + } - lv.simple_logic_diff = diffs; - lv.simple_logic_diff_inv = diffs.try_inverse().unwrap_or(F::ZERO); + let num_unequal_limbs = izip!(logic.input0, logic.input1) + .map(|(limb0, limb1)| (limb0 != limb1) as usize) + .sum(); + let equal = num_unequal_limbs == 0; - logic.output[0] = F::from_bool(diffs == F::ZERO); - for out_limb_ref in logic.output[1..].iter_mut() { - *out_limb_ref = F::ZERO; + logic.output[0] = F::from_bool(equal); + for limb in &mut logic.output[1..] { + *limb = F::ZERO; + } + + // Form `diff_pinv`. + // Let `diff = input0 - input1`. Consider `x[i] = diff[i]^-1` if `diff[i] != 0` and 0 otherwise. + // Then `diff @ x = num_unequal_limbs`, where `@` denotes the dot product. We set + // `diff_pinv = num_unequal_limbs^-1 * x` if `num_unequal_limbs != 0` and 0 otherwise. We have + // `diff @ diff_pinv = 1 - equal` as desired. + let num_unequal_limbs_inv = F::from_canonical_usize(num_unequal_limbs) + .try_inverse() + .unwrap_or(F::ZERO); + for (limb_pinv, limb0, limb1) in izip!(logic.diff_pinv.iter_mut(), logic.input0, logic.input1) { + *limb_pinv = (limb0 - limb1).try_inverse().unwrap_or(F::ZERO) * num_unequal_limbs_inv; } } @@ -56,36 +57,35 @@ pub fn eval_packed( let iszero_filter = lv.is_iszero; let eq_or_iszero_filter = eq_filter + iszero_filter; - let ls_bit = logic.output[0]; + let equal = logic.output[0]; + let unequal = P::ONES - equal; - // Handle EQ and ISZERO. Most limbs of the output are 0, but the least-significant one is + // Handle `EQ` and `ISZERO`. Most limbs of the output are 0, but the least-significant one is // either 0 or 1. - yield_constr.constraint(eq_or_iszero_filter * ls_bit * (ls_bit - P::ONES)); - - for &bit in &logic.output[1..] { - yield_constr.constraint(eq_or_iszero_filter * bit); + yield_constr.constraint(eq_or_iszero_filter * equal * unequal); + for &limb in &logic.output[1..] { + yield_constr.constraint(eq_or_iszero_filter * limb); } - // Check SIMPLE_LOGIC_DIFF - let diffs = lv.simple_logic_diff; - let diffs_inv = lv.simple_logic_diff_inv; - { - let input0_sum: P = logic.input0.into_iter().sum(); - yield_constr.constraint(iszero_filter * (diffs - input0_sum)); - - let sum_squared_diffs: P = logic - .input0 - .into_iter() - .zip(logic.input1) - .map(|(in0, in1)| (in0 - in1).square()) - .sum(); - yield_constr.constraint(eq_filter * (diffs - sum_squared_diffs)); + // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) as EQ(x, 0). + for limb in logic.input1 { + yield_constr.constraint(iszero_filter * limb); } - // diffs != 0 => ls_bit == 0 - yield_constr.constraint(eq_or_iszero_filter * diffs * ls_bit); - // ls_bit == 0 => diffs != 0 (we provide a diffs_inv) - yield_constr.constraint(eq_or_iszero_filter * (diffs * diffs_inv + ls_bit - P::ONES)); + // `equal` implies `input0[i] == input1[i]` for all `i`. + for (limb0, limb1) in izip!(logic.input0, logic.input1) { + let diff = limb0 - limb1; + yield_constr.constraint(eq_or_iszero_filter * equal * diff); + } + + // `input0[i] == input1[i]` for all `i` implies `equal`. + // If `unequal`, find `diff_pinv` such that `(input0 - input1) @ diff_pinv == 1`, where `@` + // denotes the dot product (there will be many such `diff_pinv`). This can only be done if + // `input0 != input1`. + let dot: P = izip!(logic.input0, logic.input1, logic.diff_pinv) + .map(|(limb0, limb1, diff_pinv_el)| (limb0 - limb1) * diff_pinv_el) + .sum(); + yield_constr.constraint(eq_or_iszero_filter * (dot - unequal)); } pub fn eval_ext_circuit, const D: usize>( @@ -93,61 +93,57 @@ pub fn eval_ext_circuit, const D: usize>( lv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { + let zero = builder.zero_extension(); + let one = builder.one_extension(); + let logic = lv.general.logic(); let eq_filter = lv.is_eq; let iszero_filter = lv.is_iszero; let eq_or_iszero_filter = builder.add_extension(eq_filter, iszero_filter); - let ls_bit = logic.output[0]; + let equal = logic.output[0]; + let unequal = builder.sub_extension(one, equal); - // Handle EQ and ISZERO. Most limbs of the output are 0, but the least-significant one is + // Handle `EQ` and `ISZERO`. Most limbs of the output are 0, but the least-significant one is // either 0 or 1. { - let constr = builder.mul_sub_extension(ls_bit, ls_bit, ls_bit); + let constr = builder.mul_extension(equal, unequal); + let constr = builder.mul_extension(eq_or_iszero_filter, constr); + yield_constr.constraint(builder, constr); + } + for &limb in &logic.output[1..] { + let constr = builder.mul_extension(eq_or_iszero_filter, limb); + yield_constr.constraint(builder, constr); + } + + // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) as EQ(x, 0). + for limb in logic.input1 { + let constr = builder.mul_extension(iszero_filter, limb); + yield_constr.constraint(builder, constr); + } + + // `equal` implies `input0[i] == input1[i]` for all `i`. + for (limb0, limb1) in izip!(logic.input0, logic.input1) { + let diff = builder.sub_extension(limb0, limb1); + let constr = builder.mul_extension(equal, diff); let constr = builder.mul_extension(eq_or_iszero_filter, constr); yield_constr.constraint(builder, constr); } - for &bit in &logic.output[1..] { - let constr = builder.mul_extension(eq_or_iszero_filter, bit); - yield_constr.constraint(builder, constr); - } - - // Check SIMPLE_LOGIC_DIFF - let diffs = lv.simple_logic_diff; - let diffs_inv = lv.simple_logic_diff_inv; + // `input0[i] == input1[i]` for all `i` implies `equal`. + // If `unequal`, find `diff_pinv` such that `(input0 - input1) @ diff_pinv == 1`, where `@` + // denotes the dot product (there will be many such `diff_pinv`). This can only be done if + // `input0 != input1`. { - let input0_sum = builder.add_many_extension(logic.input0); - { - let constr = builder.sub_extension(diffs, input0_sum); - let constr = builder.mul_extension(iszero_filter, constr); - yield_constr.constraint(builder, constr); - } - - let sum_squared_diffs = logic.input0.into_iter().zip(logic.input1).fold( - builder.zero_extension(), - |acc, (in0, in1)| { - let diff = builder.sub_extension(in0, in1); - builder.mul_add_extension(diff, diff, acc) + let dot: ExtensionTarget = izip!(logic.input0, logic.input1, logic.diff_pinv).fold( + zero, + |cumul, (limb0, limb1, diff_pinv_el)| { + let diff = builder.sub_extension(limb0, limb1); + builder.mul_add_extension(diff, diff_pinv_el, cumul) }, ); - { - let constr = builder.sub_extension(diffs, sum_squared_diffs); - let constr = builder.mul_extension(eq_filter, constr); - yield_constr.constraint(builder, constr); - } - } - - { - // diffs != 0 => ls_bit == 0 - let constr = builder.mul_extension(diffs, ls_bit); + let constr = builder.sub_extension(dot, unequal); let constr = builder.mul_extension(eq_or_iszero_filter, constr); yield_constr.constraint(builder, constr); } - { - // ls_bit == 0 => diffs != 0 (we provide a diffs_inv) - let constr = builder.mul_add_extension(diffs, diffs_inv, ls_bit); - let constr = builder.mul_sub_extension(eq_or_iszero_filter, constr, eq_or_iszero_filter); - yield_constr.constraint(builder, constr); - } } diff --git a/evm/src/cpu/simple_logic/not.rs b/evm/src/cpu/simple_logic/not.rs index efbf51a6..bcff3344 100644 --- a/evm/src/cpu/simple_logic/not.rs +++ b/evm/src/cpu/simple_logic/not.rs @@ -7,7 +7,7 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::CpuColumnsView; -const LIMB_SIZE: usize = 16; +const LIMB_SIZE: usize = 32; const ALL_1_LIMB: u64 = (1 << LIMB_SIZE) - 1; pub fn generate(lv: &mut CpuColumnsView) { diff --git a/evm/src/logic.rs b/evm/src/logic.rs index bde5d645..119c3d32 100644 --- a/evm/src/logic.rs +++ b/evm/src/logic.rs @@ -17,7 +17,7 @@ use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; // Total number of bits per input/output. const VAL_BITS: usize = 256; // Number of bits stored per field element. Ensure that this fits; it is not checked. -pub(crate) const PACKED_LIMB_BITS: usize = 16; +pub(crate) const PACKED_LIMB_BITS: usize = 32; // Number of field elements needed to store each input/output at the specified packing. const PACKED_LEN: usize = (VAL_BITS + PACKED_LIMB_BITS - 1) / PACKED_LIMB_BITS; From cbfc13c33f368f1051607f142251c1263621f1cc Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 18 Aug 2022 08:50:49 -0700 Subject: [PATCH 05/95] Minor paper update --- plonky2/plonky2.pdf | Bin 235072 -> 235098 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/plonky2/plonky2.pdf b/plonky2/plonky2.pdf index 8e99f30127a0507f5a82e18dd6bb35a2be2bbb00..ad0cef0228082e7ce26f6c059dd66618c0e43235 100644 GIT binary patch delta 5904 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 mbI+N3&ZAa%I#_z<7cavtSVN0 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 T(U&on10V@CF$yImMNdWw_+164 From 2fd5fbbe0145f5105e8dbde6577175b6fe013e5f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 19 Aug 2022 09:21:10 -0700 Subject: [PATCH 06/95] Change `from_biguint`'s behavior with extension fields These appear to be unused for extension fields, so we're free to change the mapping without breaking anything. As the TODO says, the mapping that's currently implemented doesn't seem natural or useful. It seems more natural to treat the `BigUint` as a base field element, potentially in a non-canonical form. --- ecdsa/src/curve/curve_msm.rs | 8 ++--- ecdsa/src/curve/curve_types.rs | 4 +-- ecdsa/src/curve/glv.rs | 8 ++--- ecdsa/src/curve/secp256k1.rs | 2 +- ecdsa/src/gadgets/curve_fixed_base.rs | 2 +- ecdsa/src/gadgets/curve_msm.rs | 2 +- ecdsa/src/gadgets/curve_windowed_mul.rs | 2 +- ecdsa/src/gadgets/glv.rs | 2 +- ecdsa/src/gadgets/nonnative.rs | 40 ++++++++++++++++++++----- field/src/extension/quadratic.rs | 6 ++-- field/src/extension/quartic.rs | 13 ++------ field/src/extension/quintic.rs | 4 +-- field/src/goldilocks_field.rs | 2 +- field/src/secp256k1_base.rs | 10 +++---- field/src/secp256k1_scalar.rs | 10 +++---- field/src/types.rs | 5 ++-- 16 files changed, 66 insertions(+), 54 deletions(-) diff --git a/ecdsa/src/curve/curve_msm.rs b/ecdsa/src/curve/curve_msm.rs index 6d07c097..f681deb2 100644 --- a/ecdsa/src/curve/curve_msm.rs +++ b/ecdsa/src/curve/curve_msm.rs @@ -207,7 +207,7 @@ mod tests { 0b00001111111111111111111111111111, 0b11111111111111111111111111111111, ]; - let x = Secp256K1Scalar::from_biguint(BigUint::from_slice(&x_canonical)); + let x = Secp256K1Scalar::from_noncanonical_biguint(BigUint::from_slice(&x_canonical)); assert_eq!(x.to_canonical_biguint().to_u32_digits(), x_canonical); assert_eq!( to_digits::(&x, 17), @@ -240,13 +240,13 @@ mod tests { let generator_2 = generator_1 + generator_1; let generator_3 = generator_1 + generator_2; - let scalar_1 = Secp256K1Scalar::from_biguint(BigUint::from_slice(&[ + let scalar_1 = Secp256K1Scalar::from_noncanonical_biguint(BigUint::from_slice(&[ 11111111, 22222222, 33333333, 44444444, ])); - let scalar_2 = Secp256K1Scalar::from_biguint(BigUint::from_slice(&[ + let scalar_2 = Secp256K1Scalar::from_noncanonical_biguint(BigUint::from_slice(&[ 22222222, 22222222, 33333333, 44444444, ])); - let scalar_3 = Secp256K1Scalar::from_biguint(BigUint::from_slice(&[ + let scalar_3 = Secp256K1Scalar::from_noncanonical_biguint(BigUint::from_slice(&[ 33333333, 22222222, 33333333, 44444444, ])); diff --git a/ecdsa/src/curve/curve_types.rs b/ecdsa/src/curve/curve_types.rs index bbf66d65..96821672 100644 --- a/ecdsa/src/curve/curve_types.rs +++ b/ecdsa/src/curve/curve_types.rs @@ -277,9 +277,9 @@ impl Neg for ProjectivePoint { } pub fn base_to_scalar(x: C::BaseField) -> C::ScalarField { - C::ScalarField::from_biguint(x.to_canonical_biguint()) + C::ScalarField::from_noncanonical_biguint(x.to_canonical_biguint()) } pub fn scalar_to_base(x: C::ScalarField) -> C::BaseField { - C::BaseField::from_biguint(x.to_canonical_biguint()) + C::BaseField::from_noncanonical_biguint(x.to_canonical_biguint()) } diff --git a/ecdsa/src/curve/glv.rs b/ecdsa/src/curve/glv.rs index 05ecea44..c58032ec 100644 --- a/ecdsa/src/curve/glv.rs +++ b/ecdsa/src/curve/glv.rs @@ -45,14 +45,14 @@ pub fn decompose_secp256k1_scalar( ) .round() .to_integer(); - let c1 = Secp256K1Scalar::from_biguint(c1_biguint); + let c1 = Secp256K1Scalar::from_noncanonical_biguint(c1_biguint); let c2_biguint = Ratio::new( MINUS_B1.to_canonical_biguint() * k.to_canonical_biguint(), p.clone(), ) .round() .to_integer(); - let c2 = Secp256K1Scalar::from_biguint(c2_biguint); + let c2 = Secp256K1Scalar::from_noncanonical_biguint(c2_biguint); let k1_raw = k - c1 * A1 - c2 * A2; let k2_raw = c1 * MINUS_B1 - c2 * B2; @@ -61,13 +61,13 @@ pub fn decompose_secp256k1_scalar( let two = BigUint::from_slice(&[2]); let k1_neg = k1_raw.to_canonical_biguint() > p.clone() / two.clone(); let k1 = if k1_neg { - Secp256K1Scalar::from_biguint(p.clone() - k1_raw.to_canonical_biguint()) + Secp256K1Scalar::from_noncanonical_biguint(p.clone() - k1_raw.to_canonical_biguint()) } else { k1_raw }; let k2_neg = k2_raw.to_canonical_biguint() > p.clone() / two; let k2 = if k2_neg { - Secp256K1Scalar::from_biguint(p - k2_raw.to_canonical_biguint()) + Secp256K1Scalar::from_noncanonical_biguint(p - k2_raw.to_canonical_biguint()) } else { k2_raw }; diff --git a/ecdsa/src/curve/secp256k1.rs b/ecdsa/src/curve/secp256k1.rs index e46fbb3d..8f7bccf3 100644 --- a/ecdsa/src/curve/secp256k1.rs +++ b/ecdsa/src/curve/secp256k1.rs @@ -71,7 +71,7 @@ mod tests { #[test] fn test_g1_multiplication() { - let lhs = Secp256K1Scalar::from_biguint(BigUint::from_slice(&[ + let lhs = Secp256K1Scalar::from_noncanonical_biguint(BigUint::from_slice(&[ 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, ])); assert_eq!( diff --git a/ecdsa/src/gadgets/curve_fixed_base.rs b/ecdsa/src/gadgets/curve_fixed_base.rs index d99d5760..44dc9488 100644 --- a/ecdsa/src/gadgets/curve_fixed_base.rs +++ b/ecdsa/src/gadgets/curve_fixed_base.rs @@ -30,7 +30,7 @@ pub fn fixed_base_curve_mul_circuit, cons let limbs = builder.split_nonnative_to_4_bit_limbs(scalar); let hash_0 = KeccakHash::<32>::hash_no_pad(&[F::ZERO]); - let hash_0_scalar = C::ScalarField::from_biguint(BigUint::from_bytes_le( + let hash_0_scalar = C::ScalarField::from_noncanonical_biguint(BigUint::from_bytes_le( &GenericHashOut::::to_bytes(&hash_0), )); let rando = (CurveScalar(hash_0_scalar) * C::GENERATOR_PROJECTIVE).to_affine(); diff --git a/ecdsa/src/gadgets/curve_msm.rs b/ecdsa/src/gadgets/curve_msm.rs index 1265d399..e059638c 100644 --- a/ecdsa/src/gadgets/curve_msm.rs +++ b/ecdsa/src/gadgets/curve_msm.rs @@ -29,7 +29,7 @@ pub fn curve_msm_circuit, const D: usize> let num_limbs = limbs_n.len(); let hash_0 = KeccakHash::<32>::hash_no_pad(&[F::ZERO]); - let hash_0_scalar = C::ScalarField::from_biguint(BigUint::from_bytes_le( + let hash_0_scalar = C::ScalarField::from_noncanonical_biguint(BigUint::from_bytes_le( &GenericHashOut::::to_bytes(&hash_0), )); let rando = (CurveScalar(hash_0_scalar) * C::GENERATOR_PROJECTIVE).to_affine(); diff --git a/ecdsa/src/gadgets/curve_windowed_mul.rs b/ecdsa/src/gadgets/curve_windowed_mul.rs index d9dcc734..bc4e1caf 100644 --- a/ecdsa/src/gadgets/curve_windowed_mul.rs +++ b/ecdsa/src/gadgets/curve_windowed_mul.rs @@ -131,7 +131,7 @@ impl, const D: usize> CircuitBuilderWindowedMul, ) -> AffinePointTarget { let hash_0 = KeccakHash::<25>::hash_no_pad(&[F::ZERO]); - let hash_0_scalar = C::ScalarField::from_biguint(BigUint::from_bytes_le( + let hash_0_scalar = C::ScalarField::from_noncanonical_biguint(BigUint::from_bytes_le( &GenericHashOut::::to_bytes(&hash_0), )); let starting_point = CurveScalar(hash_0_scalar) * C::GENERATOR_PROJECTIVE; diff --git a/ecdsa/src/gadgets/glv.rs b/ecdsa/src/gadgets/glv.rs index 746d661f..8e62e906 100644 --- a/ecdsa/src/gadgets/glv.rs +++ b/ecdsa/src/gadgets/glv.rs @@ -116,7 +116,7 @@ impl, const D: usize> SimpleGenerator } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let k = Secp256K1Scalar::from_biguint(witness_get_biguint_target( + let k = Secp256K1Scalar::from_noncanonical_biguint(witness_get_biguint_target( witness, self.k.value.clone(), )); diff --git a/ecdsa/src/gadgets/nonnative.rs b/ecdsa/src/gadgets/nonnative.rs index 3c2e2ed6..393aac75 100644 --- a/ecdsa/src/gadgets/nonnative.rs +++ b/ecdsa/src/gadgets/nonnative.rs @@ -467,8 +467,14 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_biguint(witness_get_biguint_target(witness, self.a.value.clone())); - let b = FF::from_biguint(witness_get_biguint_target(witness, self.b.value.clone())); + let a = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.a.value.clone(), + )); + let b = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.b.value.clone(), + )); let a_biguint = a.to_canonical_biguint(); let b_biguint = b.to_canonical_biguint(); let sum_biguint = a_biguint + b_biguint; @@ -508,7 +514,10 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat .summands .iter() .map(|summand| { - FF::from_biguint(witness_get_biguint_target(witness, summand.value.clone())) + FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + summand.value.clone(), + )) }) .collect(); let summand_biguints: Vec<_> = summands @@ -553,8 +562,14 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_biguint(witness_get_biguint_target(witness, self.a.value.clone())); - let b = FF::from_biguint(witness_get_biguint_target(witness, self.b.value.clone())); + let a = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.a.value.clone(), + )); + let b = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.b.value.clone(), + )); let a_biguint = a.to_canonical_biguint(); let b_biguint = b.to_canonical_biguint(); @@ -594,8 +609,14 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_biguint(witness_get_biguint_target(witness, self.a.value.clone())); - let b = FF::from_biguint(witness_get_biguint_target(witness, self.b.value.clone())); + let a = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.a.value.clone(), + )); + let b = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.b.value.clone(), + )); let a_biguint = a.to_canonical_biguint(); let b_biguint = b.to_canonical_biguint(); @@ -625,7 +646,10 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let x = FF::from_biguint(witness_get_biguint_target(witness, self.x.value.clone())); + let x = FF::from_noncanonical_biguint(witness_get_biguint_target( + witness, + self.x.value.clone(), + )); let inv = x.inverse(); let x_biguint = x.to_canonical_biguint(); diff --git a/field/src/extension/quadratic.rs b/field/src/extension/quadratic.rs index d68df42e..278abba9 100644 --- a/field/src/extension/quadratic.rs +++ b/field/src/extension/quadratic.rs @@ -3,7 +3,6 @@ use std::iter::{Product, Sum}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use num::bigint::BigUint; -use num::Integer; use serde::{Deserialize, Serialize}; use crate::extension::{Extendable, FieldExtension, Frobenius, OEF}; @@ -89,9 +88,8 @@ impl> Field for QuadraticExtension { )) } - fn from_biguint(n: BigUint) -> Self { - let (high, low) = n.div_rem(&F::order()); - Self([F::from_biguint(low), F::from_biguint(high)]) + fn from_noncanonical_biguint(n: BigUint) -> Self { + F::from_noncanonical_biguint(n).into() } fn from_canonical_u64(n: u64) -> Self { diff --git a/field/src/extension/quartic.rs b/field/src/extension/quartic.rs index fc0cbcf8..6df39903 100644 --- a/field/src/extension/quartic.rs +++ b/field/src/extension/quartic.rs @@ -4,7 +4,6 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi use num::bigint::BigUint; use num::traits::Pow; -use num::Integer; use serde::{Deserialize, Serialize}; use crate::extension::{Extendable, FieldExtension, Frobenius, OEF}; @@ -94,16 +93,8 @@ impl> Field for QuarticExtension { )) } - fn from_biguint(n: BigUint) -> Self { - let (rest, first) = n.div_rem(&F::order()); - let (rest, second) = rest.div_rem(&F::order()); - let (rest, third) = rest.div_rem(&F::order()); - Self([ - F::from_biguint(first), - F::from_biguint(second), - F::from_biguint(third), - F::from_biguint(rest), - ]) + fn from_noncanonical_biguint(n: BigUint) -> Self { + F::from_noncanonical_biguint(n).into() } fn from_canonical_u64(n: u64) -> Self { diff --git a/field/src/extension/quintic.rs b/field/src/extension/quintic.rs index 564674c3..6680ebc7 100644 --- a/field/src/extension/quintic.rs +++ b/field/src/extension/quintic.rs @@ -99,8 +99,8 @@ impl> Field for QuinticExtension { Some(FieldExtension::<5>::scalar_mul(&f, g.inverse())) } - fn from_biguint(n: BigUint) -> Self { - Self([F::from_biguint(n), F::ZERO, F::ZERO, F::ZERO, F::ZERO]) + fn from_noncanonical_biguint(n: BigUint) -> Self { + F::from_noncanonical_biguint(n).into() } fn from_canonical_u64(n: u64) -> Self { diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index c5075b5d..c1bb60b0 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -90,7 +90,7 @@ impl Field for GoldilocksField { try_inverse_u64(self) } - fn from_biguint(n: BigUint) -> Self { + fn from_noncanonical_biguint(n: BigUint) -> Self { Self(n.mod_floor(&Self::order()).to_u64_digits()[0]) } diff --git a/field/src/secp256k1_base.rs b/field/src/secp256k1_base.rs index 9e39b982..504d63d7 100644 --- a/field/src/secp256k1_base.rs +++ b/field/src/secp256k1_base.rs @@ -106,7 +106,7 @@ impl Field for Secp256K1Base { Some(self.exp_biguint(&(Self::order() - BigUint::one() - BigUint::one()))) } - fn from_biguint(val: BigUint) -> Self { + fn from_noncanonical_biguint(val: BigUint) -> Self { Self( val.to_u64_digits() .into_iter() @@ -135,7 +135,7 @@ impl Field for Secp256K1Base { #[cfg(feature = "rand")] fn rand_from_rng(rng: &mut R) -> Self { use num::bigint::RandBigInt; - Self::from_biguint(rng.gen_biguint_below(&Self::order())) + Self::from_noncanonical_biguint(rng.gen_biguint_below(&Self::order())) } } @@ -157,7 +157,7 @@ impl Neg for Secp256K1Base { if self.is_zero() { Self::ZERO } else { - Self::from_biguint(Self::order() - self.to_canonical_biguint()) + Self::from_noncanonical_biguint(Self::order() - self.to_canonical_biguint()) } } } @@ -171,7 +171,7 @@ impl Add for Secp256K1Base { if result >= Self::order() { result -= Self::order(); } - Self::from_biguint(result) + Self::from_noncanonical_biguint(result) } } @@ -210,7 +210,7 @@ impl Mul for Secp256K1Base { #[inline] fn mul(self, rhs: Self) -> Self { - Self::from_biguint( + Self::from_noncanonical_biguint( (self.to_canonical_biguint() * rhs.to_canonical_biguint()).mod_floor(&Self::order()), ) } diff --git a/field/src/secp256k1_scalar.rs b/field/src/secp256k1_scalar.rs index eea67fab..e70b154d 100644 --- a/field/src/secp256k1_scalar.rs +++ b/field/src/secp256k1_scalar.rs @@ -115,7 +115,7 @@ impl Field for Secp256K1Scalar { Some(self.exp_biguint(&(Self::order() - BigUint::one() - BigUint::one()))) } - fn from_biguint(val: BigUint) -> Self { + fn from_noncanonical_biguint(val: BigUint) -> Self { Self( val.to_u64_digits() .into_iter() @@ -144,7 +144,7 @@ impl Field for Secp256K1Scalar { #[cfg(feature = "rand")] fn rand_from_rng(rng: &mut R) -> Self { use num::bigint::RandBigInt; - Self::from_biguint(rng.gen_biguint_below(&Self::order())) + Self::from_noncanonical_biguint(rng.gen_biguint_below(&Self::order())) } } @@ -166,7 +166,7 @@ impl Neg for Secp256K1Scalar { if self.is_zero() { Self::ZERO } else { - Self::from_biguint(Self::order() - self.to_canonical_biguint()) + Self::from_noncanonical_biguint(Self::order() - self.to_canonical_biguint()) } } } @@ -180,7 +180,7 @@ impl Add for Secp256K1Scalar { if result >= Self::order() { result -= Self::order(); } - Self::from_biguint(result) + Self::from_noncanonical_biguint(result) } } @@ -219,7 +219,7 @@ impl Mul for Secp256K1Scalar { #[inline] fn mul(self, rhs: Self) -> Self { - Self::from_biguint( + Self::from_noncanonical_biguint( (self.to_canonical_biguint() * rhs.to_canonical_biguint()).mod_floor(&Self::order()), ) } diff --git a/field/src/types.rs b/field/src/types.rs index 87fd8dd4..ac94bcfa 100644 --- a/field/src/types.rs +++ b/field/src/types.rs @@ -270,9 +270,8 @@ pub trait Field: subgroup.into_iter().map(|x| x * shift).collect() } - // TODO: The current behavior for composite fields doesn't seem natural or useful. - // Rename to `from_noncanonical_biguint` and have it return `n % Self::characteristic()`. - fn from_biguint(n: BigUint) -> Self; + /// Returns `n % Self::characteristic()`. + fn from_noncanonical_biguint(n: BigUint) -> Self; /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < Self::order()`. // TODO: Should probably be unsafe. From 831a6718726f74a19102a570deb30522378ca060 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 19 Aug 2022 10:35:02 -0700 Subject: [PATCH 07/95] Tweak comments --- field/src/goldilocks_extensions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/field/src/goldilocks_extensions.rs b/field/src/goldilocks_extensions.rs index e684c7cb..2175494f 100644 --- a/field/src/goldilocks_extensions.rs +++ b/field/src/goldilocks_extensions.rs @@ -112,14 +112,14 @@ impl Mul for QuinticExtension { * result coefficient is necessary. */ -/// Return a, b such that a + b*2^128 = 3*x with a < 2^128 and b < 2^32. +/// Return `a`, `b` such that `a + b*2^128 = 3*(x + y*2^128)` with `a < 2^128` and `b < 2^32`. #[inline(always)] fn u160_times_3(x: u128, y: u32) -> (u128, u32) { let (s, cy) = x.overflowing_add(x << 1); (s, 3 * y + (x >> 127) as u32 + cy as u32) } -/// Return a, b such that a + b*2^128 = 7*x with a < 2^128 and b < 2^32. +/// Return `a`, `b` such that `a + b*2^128 = 7*(x + y*2^128)` with `a < 2^128` and `b < 2^32`. #[inline(always)] fn u160_times_7(x: u128, y: u32) -> (u128, u32) { let (d, br) = (x << 3).overflowing_sub(x); From ff961a34a3cef534b7e32bf39aa20c33d65dac55 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Fri, 19 Aug 2022 17:39:55 -0400 Subject: [PATCH 08/95] fix lost evals when P::WIDTH > 0 --- starky/src/constraint_consumer.rs | 6 +----- starky/src/prover.rs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/starky/src/constraint_consumer.rs b/starky/src/constraint_consumer.rs index c9368ba3..1a061c20 100644 --- a/starky/src/constraint_consumer.rs +++ b/starky/src/constraint_consumer.rs @@ -44,12 +44,8 @@ impl ConstraintConsumer

{ } } - // TODO: Do this correctly. - pub fn accumulators(self) -> Vec { + pub fn accumulators(self) -> Vec

{ self.constraint_accs - .into_iter() - .map(|acc| acc.as_slice()[0]) - .collect() } /// Add one constraint valid on all rows except the last. diff --git a/starky/src/prover.rs b/starky/src/prover.rs index 24593b45..974ead74 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -258,7 +258,7 @@ where let quotient_values = (0..size) .into_par_iter() .step_by(P::WIDTH) - .map(|i_start| { + .flat_map_iter(|i_start| { let i_next_start = (i_start + next_step) % size; let i_range = i_start..i_start + P::WIDTH; @@ -292,13 +292,22 @@ where permutation_check_data, &mut consumer, ); + let mut constraints_evals = consumer.accumulators(); // We divide the constraints evaluations by `Z_H(x)`. - let denominator_inv = z_h_on_coset.eval_inverse_packed(i_start); + let denominator_inv: P = z_h_on_coset.eval_inverse_packed(i_start); + for eval in &mut constraints_evals { *eval *= denominator_inv; } - constraints_evals + + let num_challenges = alphas.len(); + + (0..P::WIDTH) + .into_iter() + .map(move |i| { + (0..num_challenges).map(|j| constraints_evals[j].as_slice()[i]).collect() + }) }) .collect::>(); From 3eadc27be5ede02bbe6556757cbf16ce8abafaa0 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Fri, 19 Aug 2022 17:53:12 -0400 Subject: [PATCH 09/95] add fix to evm --- evm/src/constraint_consumer.rs | 6 +----- evm/src/prover.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/evm/src/constraint_consumer.rs b/evm/src/constraint_consumer.rs index ebe0637a..49dc018c 100644 --- a/evm/src/constraint_consumer.rs +++ b/evm/src/constraint_consumer.rs @@ -44,12 +44,8 @@ impl ConstraintConsumer

{ } } - // TODO: Do this correctly. - pub fn accumulators(self) -> Vec { + pub fn accumulators(self) -> Vec

{ self.constraint_accs - .into_iter() - .map(|acc| acc.as_slice()[0]) - .collect() } /// Add one constraint valid on all rows except the last. diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 8be39b6c..020e2451 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -388,7 +388,7 @@ where let quotient_values = (0..size) .into_par_iter() .step_by(P::WIDTH) - .map(|i_start| { + .flat_map_iter(|i_start| { let i_next_start = (i_start + next_step) % size; let i_range = i_start..i_start + P::WIDTH; @@ -444,11 +444,18 @@ where ); let mut constraints_evals = consumer.accumulators(); // We divide the constraints evaluations by `Z_H(x)`. - let denominator_inv = z_h_on_coset.eval_inverse_packed(i_start); + let denominator_inv: P = z_h_on_coset.eval_inverse_packed(i_start); for eval in &mut constraints_evals { *eval *= denominator_inv; } - constraints_evals + + let num_challenges = alphas.len(); + + (0..P::WIDTH) + .into_iter() + .map(move |i| { + (0..num_challenges).map(|j| constraints_evals[j].as_slice()[i]).collect() + }) }) .collect::>(); From ca3550266004e39a748fc73bf2a7bb600c05d836 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Fri, 19 Aug 2022 17:54:48 -0400 Subject: [PATCH 10/95] fmt --- evm/src/prover.rs | 10 +++++----- starky/src/prover.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 020e2451..15b83173 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -451,11 +451,11 @@ where let num_challenges = alphas.len(); - (0..P::WIDTH) - .into_iter() - .map(move |i| { - (0..num_challenges).map(|j| constraints_evals[j].as_slice()[i]).collect() - }) + (0..P::WIDTH).into_iter().map(move |i| { + (0..num_challenges) + .map(|j| constraints_evals[j].as_slice()[i]) + .collect() + }) }) .collect::>(); diff --git a/starky/src/prover.rs b/starky/src/prover.rs index 974ead74..0d291cf3 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -303,11 +303,11 @@ where let num_challenges = alphas.len(); - (0..P::WIDTH) - .into_iter() - .map(move |i| { - (0..num_challenges).map(|j| constraints_evals[j].as_slice()[i]).collect() - }) + (0..P::WIDTH).into_iter().map(move |i| { + (0..num_challenges) + .map(|j| constraints_evals[j].as_slice()[i]) + .collect() + }) }) .collect::>(); From 3c3997b726dccb8543ca0a7d6cd1958f6be1447d Mon Sep 17 00:00:00 2001 From: BGluth Date: Sat, 20 Aug 2022 14:26:03 -0600 Subject: [PATCH 11/95] Added caching for GitHub Workflows --- .github/workflows/continuous-integration-workflow.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 1f1db14b..188539d8 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -23,6 +23,9 @@ jobs: toolchain: nightly override: true + - name: rust-cache + uses: Swatinem/rust-cache@v2 + - name: Run cargo test uses: actions-rs/cargo@v1 with: @@ -48,6 +51,9 @@ jobs: override: true components: rustfmt, clippy + - name: rust-cache + uses: Swatinem/rust-cache@v2 + - name: Run cargo fmt uses: actions-rs/cargo@v1 with: From 2191296639abff9209b054b27e87c05357698e6f Mon Sep 17 00:00:00 2001 From: qope Date: Mon, 22 Aug 2022 19:46:44 +0900 Subject: [PATCH 12/95] Change visibility of `ECDSASecretKeyTarget` and `ECDSAPublicKeyTarget`'s fields --- ecdsa/src/gadgets/ecdsa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ecdsa/src/gadgets/ecdsa.rs b/ecdsa/src/gadgets/ecdsa.rs index b287ff05..3ed6342d 100644 --- a/ecdsa/src/gadgets/ecdsa.rs +++ b/ecdsa/src/gadgets/ecdsa.rs @@ -13,10 +13,10 @@ use crate::gadgets::glv::CircuitBuilderGlv; use crate::gadgets::nonnative::{CircuitBuilderNonNative, NonNativeTarget}; #[derive(Clone, Debug)] -pub struct ECDSASecretKeyTarget(NonNativeTarget); +pub struct ECDSASecretKeyTarget(pub NonNativeTarget); #[derive(Clone, Debug)] -pub struct ECDSAPublicKeyTarget(AffinePointTarget); +pub struct ECDSAPublicKeyTarget(pub AffinePointTarget); #[derive(Clone, Debug)] pub struct ECDSASignatureTarget { From 464b23297c19f0f6d53d0dda80aba086957453de Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 22 Aug 2022 11:15:06 -0700 Subject: [PATCH 13/95] Check each `PolynomialValues` len I.e. that it is the length of some power-of-two subgroup. --- field/src/fft.rs | 2 +- field/src/interpolation.rs | 4 +--- field/src/polynomial/mod.rs | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/field/src/fft.rs b/field/src/fft.rs index 7e9deae5..6ede8af6 100644 --- a/field/src/fft.rs +++ b/field/src/fft.rs @@ -61,7 +61,7 @@ pub fn fft_with_options( ) -> PolynomialValues { let PolynomialCoeffs { coeffs: mut buffer } = poly; fft_dispatch(&mut buffer, zero_factor, root_table); - PolynomialValues { values: buffer } + PolynomialValues::new(buffer) } #[inline] diff --git a/field/src/interpolation.rs b/field/src/interpolation.rs index d0675715..8f64e9d7 100644 --- a/field/src/interpolation.rs +++ b/field/src/interpolation.rs @@ -19,9 +19,7 @@ pub fn interpolant(points: &[(F, F)]) -> PolynomialCoeffs { .map(|x| interpolate(points, x, &barycentric_weights)) .collect(); - let mut coeffs = ifft(PolynomialValues { - values: subgroup_evals, - }); + let mut coeffs = ifft(PolynomialValues::new(subgroup_evals)); coeffs.trim(); coeffs } diff --git a/field/src/polynomial/mod.rs b/field/src/polynomial/mod.rs index 82c4a41c..6577dc52 100644 --- a/field/src/polynomial/mod.rs +++ b/field/src/polynomial/mod.rs @@ -24,6 +24,8 @@ pub struct PolynomialValues { impl PolynomialValues { pub fn new(values: Vec) -> Self { + // Check that a subgroup exists of this size, which should be a power of two. + debug_assert!(log2_strict(values.len()) <= F::TWO_ADICITY); PolynomialValues { values } } From e87392bdba90372497eb7f572a986269bed37d67 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 22 Aug 2022 11:32:36 -0700 Subject: [PATCH 14/95] comment --- field/src/polynomial/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/field/src/polynomial/mod.rs b/field/src/polynomial/mod.rs index 6577dc52..20f1c318 100644 --- a/field/src/polynomial/mod.rs +++ b/field/src/polynomial/mod.rs @@ -118,6 +118,7 @@ impl PolynomialCoeffs { PolynomialCoeffs { coeffs } } + /// The empty list of coefficients, which is the smallest encoding of the zero polynomial. pub fn empty() -> Self { Self::new(Vec::new()) } From 47753e08d574fb64b9538f432717c043b5c2a99e Mon Sep 17 00:00:00 2001 From: BGluth Date: Sun, 21 Aug 2022 14:32:35 -0600 Subject: [PATCH 15/95] Testing forcing caching of local packages --- .../continuous-integration-workflow.yml | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 188539d8..38231892 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -17,6 +17,7 @@ jobs: uses: actions/checkout@v2 - name: Install nightly toolchain + id: rustc-toolchain uses: actions-rs/toolchain@v1 with: profile: minimal @@ -24,7 +25,15 @@ jobs: override: true - name: rust-cache - uses: Swatinem/rust-cache@v2 + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: rustc-test-${{ steps.rustc-toolchain.outputs.rustc_hash }}-cargo-${{ hashFiles('**/Cargo.toml') }} - name: Run cargo test uses: actions-rs/cargo@v1 @@ -33,7 +42,7 @@ jobs: args: --all env: RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 -Cprefer-dynamic=y - CARGO_INCREMENTAL: 0 + CARGO_INCREMENTAL: 1 lints: name: Formatting and Clippy @@ -44,6 +53,7 @@ jobs: uses: actions/checkout@v2 - name: Install nightly toolchain + id: rustc-toolchain uses: actions-rs/toolchain@v1 with: profile: minimal @@ -52,17 +62,29 @@ jobs: components: rustfmt, clippy - name: rust-cache - uses: Swatinem/rust-cache@v2 + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: rustc-lints-${{ steps.rustc-toolchain.outputs.rustc_hash }}-cargo-${{ hashFiles('**/Cargo.toml') }} - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check + env: + CARGO_INCREMENTAL: 1 - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: --all-features --all-targets -- -D warnings -A incomplete-features + env: + CARGO_INCREMENTAL: 1 From a37dec9881e2d6ff4d02cf998f1da19c40b0150e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 22 Aug 2022 11:07:39 -0700 Subject: [PATCH 16/95] Support accessing previous row in CTLs --- evm/src/all_stark.rs | 14 ++- evm/src/cpu/cpu_stark.rs | 10 +- evm/src/cross_table_lookup.rs | 186 ++++++++++++++++++++++------------ 3 files changed, 137 insertions(+), 73 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index e2a11ba2..2c6cb9cd 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -145,6 +145,7 @@ mod tests { use crate::cpu::cpu_stark::CpuStark; use crate::cpu::kernel::aggregator::KERNEL; use crate::cross_table_lookup::testutils::check_ctls; + use crate::cross_table_lookup::Column; use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS}; use crate::logic::{self, LogicStark, Operation}; use crate::memory::memory_stark::tests::generate_random_memory_ops; @@ -216,8 +217,10 @@ mod tests { .map(|i| { (0..2 * NUM_INPUTS) .map(|j| { - keccak::columns::reg_input_limb(j) - .eval_table(keccak_trace, (i + 1) * NUM_ROUNDS - 1) + // There's an extra -1 because the argument to eval_table is the local row, + // but the inputs/outputs live in the next row. + let local_row = (i + 1) * NUM_ROUNDS - 1 - 1; + keccak::columns::reg_input_limb(j).eval_table(keccak_trace, local_row) }) .collect::>() .try_into() @@ -228,8 +231,11 @@ mod tests { .map(|i| { (0..2 * NUM_INPUTS) .map(|j| { - keccak_trace[keccak::columns::reg_output_limb(j)].values - [(i + 1) * NUM_ROUNDS - 1] + let out_limb = Column::single(keccak::columns::reg_output_limb(j)); + // There's an extra -1 because the argument to eval_table is the local row, + // but the inputs/outputs live in the next row. + let local_row = (i + 1) * NUM_ROUNDS - 1 - 1; + out_limb.eval_table(keccak_trace, local_row) }) .collect::>() .try_into() diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 918f7d9b..9036d163 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -10,7 +10,7 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::{bootstrap_kernel, control_flow, decode, jumps, simple_logic, syscalls}; -use crate::cross_table_lookup::Column; +use crate::cross_table_lookup::{Column, WeightedColumn}; use crate::memory::NUM_CHANNELS; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -50,10 +50,14 @@ pub fn ctl_data_memory(channel: usize) -> Vec> { .collect_vec(); cols.extend(Column::singles(COL_MAP.mem_value[channel])); - let scalar = F::from_canonical_usize(NUM_CHANNELS); + let weight = F::from_canonical_usize(NUM_CHANNELS); let addend = F::from_canonical_usize(channel); cols.push(Column::linear_combination_with_constant( - vec![(COL_MAP.clock, scalar)], + vec![WeightedColumn { + column: COL_MAP.clock, + next: true, + weight, + }], addend, )); diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 4097df7b..7f55c40a 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -1,5 +1,3 @@ -use std::iter::repeat; - use anyhow::{ensure, Result}; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -24,16 +22,30 @@ use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; /// Represent a linear combination of columns. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Column { - linear_combination: Vec<(usize, F)>, + linear_combination: Vec>, constant: F, } +#[derive(Clone, Debug)] +pub(crate) struct WeightedColumn { + /// The index of the column. + pub(crate) column: usize, + /// True if this column refers to a column in the next row, rather than the local row. + /// Most CTLs consist of only "next" columns. + pub(crate) next: bool, + pub(crate) weight: F, +} + impl Column { - pub fn single(c: usize) -> Self { + pub fn single(column: usize) -> Self { Self { - linear_combination: vec![(c, F::ONE)], + linear_combination: vec![WeightedColumn { + column, + next: true, + weight: F::ONE, + }], constant: F::ZERO, } } @@ -42,14 +54,17 @@ impl Column { cs.into_iter().map(Self::single) } - pub fn linear_combination_with_constant>( + pub(crate) fn linear_combination_with_constant>>( iter: I, constant: F, ) -> Self { let v = iter.into_iter().collect::>(); assert!(!v.is_empty()); debug_assert_eq!( - v.iter().map(|(c, _)| c).unique().count(), + v.iter() + .map(|weighted_col| weighted_col.column) + .unique() + .count(), v.len(), "Duplicate columns." ); @@ -59,35 +74,65 @@ impl Column { } } - pub fn linear_combination>(iter: I) -> Self { + pub(crate) fn linear_combination>>(iter: I) -> Self { Self::linear_combination_with_constant(iter, F::ZERO) } pub fn le_bits>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(F::TWO.powers())) + Self::linear_combination(cs.into_iter().zip(F::TWO.powers()).map(|(column, weight)| { + WeightedColumn { + column, + next: true, + weight, + } + })) } pub fn sum>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(repeat(F::ONE))) + Self::linear_combination(cs.into_iter().map(|column| WeightedColumn { + column, + next: true, + weight: F::ONE, + })) } - pub fn eval(&self, v: &[P]) -> P + pub fn eval(&self, local_values: &[P], next_values: &[P]) -> P where FE: FieldExtension, P: PackedField, { self.linear_combination .iter() - .map(|&(c, f)| v[c] * FE::from_basefield(f)) + .map(|weighted_col| { + let values = if weighted_col.next { + next_values + } else { + local_values + }; + values[weighted_col.column] * FE::from_basefield(weighted_col.weight) + }) .sum::

() + FE::from_basefield(self.constant) } /// Evaluate on an row of a table given in column-major form. - pub fn eval_table(&self, table: &[PolynomialValues], row: usize) -> F { + pub fn eval_table(&self, table: &[PolynomialValues], local_row: usize) -> F { + let mut next_row = local_row + 1; + if next_row == table[0].len() { + next_row = 0; + } + self.linear_combination .iter() - .map(|&(c, f)| table[c].values[row] * f) + .map(|weighted_col| { + let row = if weighted_col.next { + next_row + } else { + local_row + }; + let poly = &table[weighted_col.column]; + poly.values[row] * weighted_col.weight + }) .sum::() + self.constant } @@ -95,7 +140,8 @@ impl Column { pub fn eval_circuit( &self, builder: &mut CircuitBuilder, - v: &[ExtensionTarget], + local_values: &[ExtensionTarget], + next_values: &[ExtensionTarget], ) -> ExtensionTarget where F: RichField + Extendable, @@ -103,10 +149,15 @@ impl Column { let pairs = self .linear_combination .iter() - .map(|&(c, f)| { + .map(|weighted_col| { + let values = if weighted_col.next { + next_values + } else { + local_values + }; ( - v[c], - builder.constant_extension(F::Extension::from_basefield(f)), + values[weighted_col.column], + builder.constant_extension(F::Extension::from_basefield(weighted_col.weight)), ) }) .collect::>(); @@ -115,7 +166,7 @@ impl Column { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TableWithColumns { table: Table, columns: Vec>, @@ -132,7 +183,7 @@ impl TableWithColumns { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct CrossTableLookup { looking_tables: Vec>, looked_table: TableWithColumns, @@ -279,16 +330,22 @@ fn partial_products( let mut partial_prod = F::ONE; let degree = trace[0].len(); let mut res = Vec::with_capacity(degree); - for i in 0..degree { + for next_row in 0..degree { + let local_row = if next_row == 0 { + degree - 1 + } else { + next_row - 1 + }; + let filter = if let Some(column) = filter_column { - column.eval_table(trace, i) + column.eval_table(trace, local_row) } else { F::ONE }; if filter.is_one() { let evals = columns .iter() - .map(|c| c.eval_table(trace, i)) + .map(|c| c.eval_table(trace, local_row)) .collect::>(); partial_prod *= challenge.combine(evals.iter()); } else { @@ -386,27 +443,23 @@ pub(crate) fn eval_cross_table_lookup_checks P { - let evals = columns.iter().map(|c| c.eval(v)).collect::>(); - challenges.combine(evals.iter()) + // TODO: Avoid collecting here. + let evals = columns + .iter() + .map(|c| c.eval(vars.local_values, vars.next_values)) + .collect::>(); + let combined = challenges.combine(evals.iter()); + let filter = if let Some(column) = filter_column { + column.eval(vars.local_values, vars.next_values) + } else { + P::ONES }; - let filter = |v: &[P]| -> P { - if let Some(column) = filter_column { - column.eval(v) - } else { - P::ONES - } - }; - let local_filter = filter(vars.local_values); - let next_filter = filter(vars.next_values); - let select = |filter, x| filter * x + P::ONES - filter; + let multiplier = filter * combined + P::ONES - filter; // Check value of `Z(1)` - consumer.constraint_first_row(*local_z - select(local_filter, combine(vars.local_values))); + consumer.constraint_last_row(*next_z - multiplier); // Check `Z(gw) = combination * Z(w)` - consumer.constraint_transition( - *next_z - *local_z * select(next_filter, combine(vars.next_values)), - ); + consumer.constraint_transition(*next_z - *local_z * multiplier); } } @@ -491,16 +544,12 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< } = lookup_vars; let one = builder.one_extension(); - let local_filter = if let Some(column) = filter_column { - column.eval_circuit(builder, vars.local_values) - } else { - one - }; - let next_filter = if let Some(column) = filter_column { - column.eval_circuit(builder, vars.next_values) + let filter = if let Some(column) = filter_column { + column.eval_circuit(builder, vars.local_values, vars.next_values) } else { one }; + // TODO: Can use builder.select_ext_generalized. fn select, const D: usize>( builder: &mut CircuitBuilder, filter: ExtensionTarget, @@ -512,23 +561,17 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< } // Check value of `Z(1)` - let local_columns_eval = columns + let evals = columns .iter() - .map(|c| c.eval_circuit(builder, vars.local_values)) + .map(|c| c.eval_circuit(builder, vars.local_values, vars.next_values)) .collect::>(); - let combined_local = challenges.combine_circuit(builder, &local_columns_eval); - let selected_local = select(builder, local_filter, combined_local); - let first_row = builder.sub_extension(*local_z, selected_local); - consumer.constraint_first_row(builder, first_row); + let combined = challenges.combine_circuit(builder, &evals); + let multiplier = select(builder, filter, combined); + let first_row = builder.sub_extension(*next_z, multiplier); + consumer.constraint_last_row(builder, first_row); // Check `Z(gw) = combination * Z(w)` - let next_columns_eval = columns - .iter() - .map(|c| c.eval_circuit(builder, vars.next_values)) - .collect::>(); - let combined_next = challenges.combine_circuit(builder, &next_columns_eval); - let selected_next = select(builder, next_filter, combined_next); - let mut transition = builder.mul_extension(*local_z, selected_next); - transition = builder.sub_extension(*next_z, transition); + let product = builder.mul_extension(*local_z, multiplier); + let transition = builder.sub_extension(*next_z, product); consumer.constraint_transition(builder, transition); } } @@ -746,9 +789,17 @@ pub(crate) mod testutils { multiset: &mut MultiSet, ) { let trace = &trace_poly_values[table.table as usize]; - for i in 0..trace[0].len() { + let degree = trace[0].len(); + + for next_row in 0..trace[0].len() { + let local_row = if next_row == 0 { + degree - 1 + } else { + next_row - 1 + }; + let filter = if let Some(column) = &table.filter_column { - column.eval_table(trace, i) + column.eval_table(trace, local_row) } else { F::ONE }; @@ -756,9 +807,12 @@ pub(crate) mod testutils { let row = table .columns .iter() - .map(|c| c.eval_table(trace, i)) + .map(|c| c.eval_table(trace, local_row)) .collect::>(); - multiset.entry(row).or_default().push((table.table, i)); + multiset + .entry(row) + .or_default() + .push((table.table, local_row)); } else { assert_eq!(filter, F::ZERO, "Non-binary filter?") } From 00081890f361f5a67cdfcf3f38e118e171e337a1 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 23 Aug 2022 10:23:28 -0700 Subject: [PATCH 17/95] feedback --- evm/src/cross_table_lookup.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 7f55c40a..ef781848 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -117,10 +117,9 @@ impl Column { /// Evaluate on an row of a table given in column-major form. pub fn eval_table(&self, table: &[PolynomialValues], local_row: usize) -> F { - let mut next_row = local_row + 1; - if next_row == table[0].len() { - next_row = 0; - } + let degree = table[0].len(); + debug_assert!(degree.is_power_of_two()); + let next_row = (local_row + 1) & (degree - 1); // Equivalent to % degree. self.linear_combination .iter() @@ -331,11 +330,8 @@ fn partial_products( let degree = trace[0].len(); let mut res = Vec::with_capacity(degree); for next_row in 0..degree { - let local_row = if next_row == 0 { - degree - 1 - } else { - next_row - 1 - }; + debug_assert!(degree.is_power_of_two()); + let local_row = (next_row + degree - 1) & (degree - 1); // Equivalent to % degree. let filter = if let Some(column) = filter_column { column.eval_table(trace, local_row) @@ -792,11 +788,8 @@ pub(crate) mod testutils { let degree = trace[0].len(); for next_row in 0..trace[0].len() { - let local_row = if next_row == 0 { - degree - 1 - } else { - next_row - 1 - }; + debug_assert!(degree.is_power_of_two()); + let local_row = (next_row + degree - 1) & (degree - 1); // Equivalent to % degree. let filter = if let Some(column) = &table.filter_column { column.eval_table(trace, local_row) From 1b9c4778d8663ecb030bcd59c8bd05c05f3e8130 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 23 Aug 2022 11:23:41 -0700 Subject: [PATCH 18/95] Enumerate `constants_to_targets` in a deterministic order Fixes #684. --- plonky2/src/plonk/circuit_builder.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 9f97251a..9da07a2e 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -2,6 +2,7 @@ use std::cmp::max; use std::collections::{BTreeMap, HashMap, HashSet}; use std::time::Instant; +use itertools::Itertools; use log::{debug, info, Level}; use plonky2_field::cosets::get_unique_coset_shifts; use plonky2_field::extension::{Extendable, FieldExtension}; @@ -95,9 +96,9 @@ impl, const D: usize> CircuitBuilder { context_log: ContextTree::new(), generators: Vec::new(), constants_to_targets: HashMap::new(), + targets_to_constants: HashMap::new(), base_arithmetic_results: HashMap::new(), arithmetic_results: HashMap::new(), - targets_to_constants: HashMap::new(), current_slots: HashMap::new(), constant_generators: Vec::new(), }; @@ -665,6 +666,9 @@ impl, const D: usize> CircuitBuilder { .constants_to_targets .clone() .into_iter() + // We need to enumerate constants_to_targets in some deterministic order to ensure that + // building a circuit is deterministic. + .sorted_by_key(|(c, _t)| c.to_canonical_u64()) .zip(self.constant_generators.clone()) { // Set the constant in the constant polynomial. From 782d7d0e18e4d429ffb1da8bc4b962e8490bbaa4 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 23 Aug 2022 12:22:54 -0700 Subject: [PATCH 19/95] Revert "Support accessing local row in CTLs" --- evm/src/all_stark.rs | 14 +-- evm/src/cpu/cpu_stark.rs | 10 +- evm/src/cross_table_lookup.rs | 179 +++++++++++++--------------------- 3 files changed, 73 insertions(+), 130 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 2c6cb9cd..e2a11ba2 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -145,7 +145,6 @@ mod tests { use crate::cpu::cpu_stark::CpuStark; use crate::cpu::kernel::aggregator::KERNEL; use crate::cross_table_lookup::testutils::check_ctls; - use crate::cross_table_lookup::Column; use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS}; use crate::logic::{self, LogicStark, Operation}; use crate::memory::memory_stark::tests::generate_random_memory_ops; @@ -217,10 +216,8 @@ mod tests { .map(|i| { (0..2 * NUM_INPUTS) .map(|j| { - // There's an extra -1 because the argument to eval_table is the local row, - // but the inputs/outputs live in the next row. - let local_row = (i + 1) * NUM_ROUNDS - 1 - 1; - keccak::columns::reg_input_limb(j).eval_table(keccak_trace, local_row) + keccak::columns::reg_input_limb(j) + .eval_table(keccak_trace, (i + 1) * NUM_ROUNDS - 1) }) .collect::>() .try_into() @@ -231,11 +228,8 @@ mod tests { .map(|i| { (0..2 * NUM_INPUTS) .map(|j| { - let out_limb = Column::single(keccak::columns::reg_output_limb(j)); - // There's an extra -1 because the argument to eval_table is the local row, - // but the inputs/outputs live in the next row. - let local_row = (i + 1) * NUM_ROUNDS - 1 - 1; - out_limb.eval_table(keccak_trace, local_row) + keccak_trace[keccak::columns::reg_output_limb(j)].values + [(i + 1) * NUM_ROUNDS - 1] }) .collect::>() .try_into() diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 9036d163..918f7d9b 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -10,7 +10,7 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::{bootstrap_kernel, control_flow, decode, jumps, simple_logic, syscalls}; -use crate::cross_table_lookup::{Column, WeightedColumn}; +use crate::cross_table_lookup::Column; use crate::memory::NUM_CHANNELS; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -50,14 +50,10 @@ pub fn ctl_data_memory(channel: usize) -> Vec> { .collect_vec(); cols.extend(Column::singles(COL_MAP.mem_value[channel])); - let weight = F::from_canonical_usize(NUM_CHANNELS); + let scalar = F::from_canonical_usize(NUM_CHANNELS); let addend = F::from_canonical_usize(channel); cols.push(Column::linear_combination_with_constant( - vec![WeightedColumn { - column: COL_MAP.clock, - next: true, - weight, - }], + vec![(COL_MAP.clock, scalar)], addend, )); diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index ef781848..4097df7b 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -1,3 +1,5 @@ +use std::iter::repeat; + use anyhow::{ensure, Result}; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -22,30 +24,16 @@ use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; /// Represent a linear combination of columns. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Column { - linear_combination: Vec>, + linear_combination: Vec<(usize, F)>, constant: F, } -#[derive(Clone, Debug)] -pub(crate) struct WeightedColumn { - /// The index of the column. - pub(crate) column: usize, - /// True if this column refers to a column in the next row, rather than the local row. - /// Most CTLs consist of only "next" columns. - pub(crate) next: bool, - pub(crate) weight: F, -} - impl Column { - pub fn single(column: usize) -> Self { + pub fn single(c: usize) -> Self { Self { - linear_combination: vec![WeightedColumn { - column, - next: true, - weight: F::ONE, - }], + linear_combination: vec![(c, F::ONE)], constant: F::ZERO, } } @@ -54,17 +42,14 @@ impl Column { cs.into_iter().map(Self::single) } - pub(crate) fn linear_combination_with_constant>>( + pub fn linear_combination_with_constant>( iter: I, constant: F, ) -> Self { let v = iter.into_iter().collect::>(); assert!(!v.is_empty()); debug_assert_eq!( - v.iter() - .map(|weighted_col| weighted_col.column) - .unique() - .count(), + v.iter().map(|(c, _)| c).unique().count(), v.len(), "Duplicate columns." ); @@ -74,64 +59,35 @@ impl Column { } } - pub(crate) fn linear_combination>>(iter: I) -> Self { + pub fn linear_combination>(iter: I) -> Self { Self::linear_combination_with_constant(iter, F::ZERO) } pub fn le_bits>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(F::TWO.powers()).map(|(column, weight)| { - WeightedColumn { - column, - next: true, - weight, - } - })) + Self::linear_combination(cs.into_iter().zip(F::TWO.powers())) } pub fn sum>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().map(|column| WeightedColumn { - column, - next: true, - weight: F::ONE, - })) + Self::linear_combination(cs.into_iter().zip(repeat(F::ONE))) } - pub fn eval(&self, local_values: &[P], next_values: &[P]) -> P + pub fn eval(&self, v: &[P]) -> P where FE: FieldExtension, P: PackedField, { self.linear_combination .iter() - .map(|weighted_col| { - let values = if weighted_col.next { - next_values - } else { - local_values - }; - values[weighted_col.column] * FE::from_basefield(weighted_col.weight) - }) + .map(|&(c, f)| v[c] * FE::from_basefield(f)) .sum::

() + FE::from_basefield(self.constant) } /// Evaluate on an row of a table given in column-major form. - pub fn eval_table(&self, table: &[PolynomialValues], local_row: usize) -> F { - let degree = table[0].len(); - debug_assert!(degree.is_power_of_two()); - let next_row = (local_row + 1) & (degree - 1); // Equivalent to % degree. - + pub fn eval_table(&self, table: &[PolynomialValues], row: usize) -> F { self.linear_combination .iter() - .map(|weighted_col| { - let row = if weighted_col.next { - next_row - } else { - local_row - }; - let poly = &table[weighted_col.column]; - poly.values[row] * weighted_col.weight - }) + .map(|&(c, f)| table[c].values[row] * f) .sum::() + self.constant } @@ -139,8 +95,7 @@ impl Column { pub fn eval_circuit( &self, builder: &mut CircuitBuilder, - local_values: &[ExtensionTarget], - next_values: &[ExtensionTarget], + v: &[ExtensionTarget], ) -> ExtensionTarget where F: RichField + Extendable, @@ -148,15 +103,10 @@ impl Column { let pairs = self .linear_combination .iter() - .map(|weighted_col| { - let values = if weighted_col.next { - next_values - } else { - local_values - }; + .map(|&(c, f)| { ( - values[weighted_col.column], - builder.constant_extension(F::Extension::from_basefield(weighted_col.weight)), + v[c], + builder.constant_extension(F::Extension::from_basefield(f)), ) }) .collect::>(); @@ -165,7 +115,7 @@ impl Column { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct TableWithColumns { table: Table, columns: Vec>, @@ -182,7 +132,7 @@ impl TableWithColumns { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct CrossTableLookup { looking_tables: Vec>, looked_table: TableWithColumns, @@ -329,19 +279,16 @@ fn partial_products( let mut partial_prod = F::ONE; let degree = trace[0].len(); let mut res = Vec::with_capacity(degree); - for next_row in 0..degree { - debug_assert!(degree.is_power_of_two()); - let local_row = (next_row + degree - 1) & (degree - 1); // Equivalent to % degree. - + for i in 0..degree { let filter = if let Some(column) = filter_column { - column.eval_table(trace, local_row) + column.eval_table(trace, i) } else { F::ONE }; if filter.is_one() { let evals = columns .iter() - .map(|c| c.eval_table(trace, local_row)) + .map(|c| c.eval_table(trace, i)) .collect::>(); partial_prod *= challenge.combine(evals.iter()); } else { @@ -439,23 +386,27 @@ pub(crate) fn eval_cross_table_lookup_checks>(); - let combined = challenges.combine(evals.iter()); - let filter = if let Some(column) = filter_column { - column.eval(vars.local_values, vars.next_values) - } else { - P::ONES + let combine = |v: &[P]| -> P { + let evals = columns.iter().map(|c| c.eval(v)).collect::>(); + challenges.combine(evals.iter()) }; - let multiplier = filter * combined + P::ONES - filter; + let filter = |v: &[P]| -> P { + if let Some(column) = filter_column { + column.eval(v) + } else { + P::ONES + } + }; + let local_filter = filter(vars.local_values); + let next_filter = filter(vars.next_values); + let select = |filter, x| filter * x + P::ONES - filter; // Check value of `Z(1)` - consumer.constraint_last_row(*next_z - multiplier); + consumer.constraint_first_row(*local_z - select(local_filter, combine(vars.local_values))); // Check `Z(gw) = combination * Z(w)` - consumer.constraint_transition(*next_z - *local_z * multiplier); + consumer.constraint_transition( + *next_z - *local_z * select(next_filter, combine(vars.next_values)), + ); } } @@ -540,12 +491,16 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< } = lookup_vars; let one = builder.one_extension(); - let filter = if let Some(column) = filter_column { - column.eval_circuit(builder, vars.local_values, vars.next_values) + let local_filter = if let Some(column) = filter_column { + column.eval_circuit(builder, vars.local_values) + } else { + one + }; + let next_filter = if let Some(column) = filter_column { + column.eval_circuit(builder, vars.next_values) } else { one }; - // TODO: Can use builder.select_ext_generalized. fn select, const D: usize>( builder: &mut CircuitBuilder, filter: ExtensionTarget, @@ -557,17 +512,23 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< } // Check value of `Z(1)` - let evals = columns + let local_columns_eval = columns .iter() - .map(|c| c.eval_circuit(builder, vars.local_values, vars.next_values)) + .map(|c| c.eval_circuit(builder, vars.local_values)) .collect::>(); - let combined = challenges.combine_circuit(builder, &evals); - let multiplier = select(builder, filter, combined); - let first_row = builder.sub_extension(*next_z, multiplier); - consumer.constraint_last_row(builder, first_row); + let combined_local = challenges.combine_circuit(builder, &local_columns_eval); + let selected_local = select(builder, local_filter, combined_local); + let first_row = builder.sub_extension(*local_z, selected_local); + consumer.constraint_first_row(builder, first_row); // Check `Z(gw) = combination * Z(w)` - let product = builder.mul_extension(*local_z, multiplier); - let transition = builder.sub_extension(*next_z, product); + let next_columns_eval = columns + .iter() + .map(|c| c.eval_circuit(builder, vars.next_values)) + .collect::>(); + let combined_next = challenges.combine_circuit(builder, &next_columns_eval); + let selected_next = select(builder, next_filter, combined_next); + let mut transition = builder.mul_extension(*local_z, selected_next); + transition = builder.sub_extension(*next_z, transition); consumer.constraint_transition(builder, transition); } } @@ -785,14 +746,9 @@ pub(crate) mod testutils { multiset: &mut MultiSet, ) { let trace = &trace_poly_values[table.table as usize]; - let degree = trace[0].len(); - - for next_row in 0..trace[0].len() { - debug_assert!(degree.is_power_of_two()); - let local_row = (next_row + degree - 1) & (degree - 1); // Equivalent to % degree. - + for i in 0..trace[0].len() { let filter = if let Some(column) = &table.filter_column { - column.eval_table(trace, local_row) + column.eval_table(trace, i) } else { F::ONE }; @@ -800,12 +756,9 @@ pub(crate) mod testutils { let row = table .columns .iter() - .map(|c| c.eval_table(trace, local_row)) + .map(|c| c.eval_table(trace, i)) .collect::>(); - multiset - .entry(row) - .or_default() - .push((table.table, local_row)); + multiset.entry(row).or_default().push((table.table, i)); } else { assert_eq!(filter, F::ZERO, "Non-binary filter?") } From 8e220ac623ea745a30e3775c383f77e409851177 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 23 Aug 2022 23:20:20 -0700 Subject: [PATCH 20/95] Fix for CTL challenges See this line - ```rust challenges: ctl_data.challenges.challenges[i % config.num_challenges], ``` This doesn't work if we have multiple lookers from the same table; then `zs_columns` will contain multiple contiguous entries for the same challenge. We could fix the index calculation, but it seems a bit error-prone. Seems easier to store the specific challenge as part of `zs_columns`. --- evm/src/cross_table_lookup.rs | 55 +++++++++++++++++++---------------- evm/src/prover.rs | 36 ++++++++++------------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 4097df7b..b4b8d6fb 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -168,23 +168,21 @@ impl CrossTableLookup { } /// Cross-table lookup data for one table. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct CtlData { - /// Challenges used in the argument. - pub(crate) challenges: GrandProductChallengeSet, - /// Vector of `(Z, columns, filter_columns)` where `Z` is a Z-polynomial for a lookup - /// on columns `columns` with filter columns `filter_columns`. - pub zs_columns: Vec<(PolynomialValues, Vec>, Option>)>, + pub(crate) zs_columns: Vec>, +} + +/// Cross-table lookup data associated with one Z(x) polynomial. +#[derive(Clone)] +pub(crate) struct CtlZData { + pub(crate) z: PolynomialValues, + pub(crate) challenge: GrandProductChallenge, + pub(crate) columns: Vec>, + pub(crate) filter_column: Option>, } impl CtlData { - pub(crate) fn new(challenges: GrandProductChallengeSet) -> Self { - Self { - challenges, - zs_columns: vec![], - } - } - pub fn len(&self) -> usize { self.zs_columns.len() } @@ -194,7 +192,10 @@ impl CtlData { } pub fn z_polys(&self) -> Vec> { - self.zs_columns.iter().map(|(p, _, _)| p.clone()).collect() + self.zs_columns + .iter() + .map(|zs_columns| zs_columns.z.clone()) + .collect() } } @@ -205,7 +206,7 @@ pub fn cross_table_lookup_data, const D challenger: &mut Challenger, ) -> Vec> { let challenges = get_grand_product_challenge_set(challenger, config.num_challenges); - let mut ctl_data_per_table = vec![CtlData::new(challenges.clone()); trace_poly_values.len()]; + let mut ctl_data_per_table = vec![CtlData::default(); trace_poly_values.len()]; for CrossTableLookup { looking_tables, looked_table, @@ -252,19 +253,23 @@ pub fn cross_table_lookup_data, const D ); for (table, z) in looking_tables.iter().zip(zs_looking) { - ctl_data_per_table[table.table as usize].zs_columns.push(( - z, - table.columns.clone(), - table.filter_column.clone(), - )); + ctl_data_per_table[table.table as usize] + .zs_columns + .push(CtlZData { + z, + challenge, + columns: table.columns.clone(), + filter_column: table.filter_column.clone(), + }); } ctl_data_per_table[looked_table.table as usize] .zs_columns - .push(( - z_looked, - looked_table.columns.clone(), - looked_table.filter_column.clone(), - )); + .push(CtlZData { + z: z_looked, + challenge, + columns: looked_table.columns.clone(), + filter_column: looked_table.filter_column.clone(), + }); } } ctl_data_per_table diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 15b83173..e93ad754 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -422,17 +422,15 @@ where .zs_columns .iter() .enumerate() - .map( - |(i, (_, columns, filter_column))| CtlCheckVars:: { - local_z: permutation_ctl_zs_commitment.get_lde_values_packed(i_start, step) - [num_permutation_zs + i], - next_z: permutation_ctl_zs_commitment - .get_lde_values_packed(i_next_start, step)[num_permutation_zs + i], - challenges: ctl_data.challenges.challenges[i % config.num_challenges], - columns, - filter_column, - }, - ) + .map(|(i, zs_columns)| CtlCheckVars:: { + local_z: permutation_ctl_zs_commitment.get_lde_values_packed(i_start, step) + [num_permutation_zs + i], + next_z: permutation_ctl_zs_commitment.get_lde_values_packed(i_next_start, step) + [num_permutation_zs + i], + challenges: zs_columns.challenge, + columns: &zs_columns.columns, + filter_column: &zs_columns.filter_column, + }) .collect::>(); eval_vanishing_poly::( stark, @@ -547,15 +545,13 @@ fn check_constraints<'a, F, C, S, const D: usize>( .zs_columns .iter() .enumerate() - .map( - |(iii, (_, columns, filter_column))| CtlCheckVars:: { - local_z: permutation_ctl_zs_subgroup_evals[i][num_permutation_zs + iii], - next_z: permutation_ctl_zs_subgroup_evals[i_next][num_permutation_zs + iii], - challenges: ctl_data.challenges.challenges[iii % config.num_challenges], - columns, - filter_column, - }, - ) + .map(|(iii, zs_columns)| CtlCheckVars:: { + local_z: permutation_ctl_zs_subgroup_evals[i][num_permutation_zs + iii], + next_z: permutation_ctl_zs_subgroup_evals[i_next][num_permutation_zs + iii], + challenges: zs_columns.challenge, + columns: &zs_columns.columns, + filter_column: &zs_columns.filter_column, + }) .collect::>(); eval_vanishing_poly::( stark, From c38a98f9e40390a012ea2db89626a32b70771433 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 23 Aug 2022 17:24:35 -0700 Subject: [PATCH 21/95] Simpler CPU <-> memory CTL --- evm/src/all_stark.rs | 30 ++++++++-------- evm/src/generation/state.rs | 6 ++-- evm/src/memory/columns.rs | 13 +++---- evm/src/memory/memory_stark.rs | 62 ++++++++++++---------------------- evm/src/memory/mod.rs | 1 + 5 files changed, 47 insertions(+), 65 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index e2a11ba2..1131d529 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -75,9 +75,7 @@ impl Table { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn all_cross_table_lookups() -> Vec> { - let mut cross_table_lookups = vec![ctl_keccak(), ctl_logic()]; - cross_table_lookups.extend((0..NUM_CHANNELS).map(ctl_memory)); - cross_table_lookups + vec![ctl_keccak(), ctl_logic(), ctl_memory()] } fn ctl_keccak() -> CrossTableLookup { @@ -108,17 +106,21 @@ fn ctl_logic() -> CrossTableLookup { ) } -fn ctl_memory(channel: usize) -> CrossTableLookup { +fn ctl_memory() -> CrossTableLookup { CrossTableLookup::new( - vec![TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_memory(channel), - Some(cpu_stark::ctl_filter_memory(channel)), - )], + (0..NUM_CHANNELS) + .map(|channel| { + TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_memory(channel), + Some(cpu_stark::ctl_filter_memory(channel)), + ) + }) + .collect(), TableWithColumns::new( Table::Memory, memory_stark::ctl_data(), - Some(memory_stark::ctl_filter(channel)), + Some(memory_stark::ctl_filter()), ), None, ) @@ -298,11 +300,11 @@ mod tests { let clock = mem_timestamp / NUM_CHANNELS; let channel = mem_timestamp % NUM_CHANNELS; - let is_padding_row = (0..NUM_CHANNELS) - .map(|c| memory_trace[memory::columns::is_channel(c)].values[i]) - .all(|x| x == F::ZERO); + let filter = memory_trace[memory::columns::FILTER].values[i]; + assert!(filter.is_one() || filter.is_zero()); + let is_actual_op = filter.is_one(); - if !is_padding_row { + if is_actual_op { let row: &mut cpu::columns::CpuColumnsView = cpu_trace_rows[clock].borrow_mut(); row.mem_channel_used[channel] = F::ONE; diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index c7f1003e..04ab4016 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -59,11 +59,12 @@ impl GenerationState { segment: Segment, virt: usize, ) -> U256 { + self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; let timestamp = self.cpu_rows.len(); let context = self.current_context; let value = self.memory.contexts[context].segments[segment as usize].get(virt); self.memory.log.push(MemoryOp { - channel_index: Some(channel_index), + filter: true, timestamp, is_read: true, context, @@ -82,10 +83,11 @@ impl GenerationState { virt: usize, value: U256, ) { + self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; let timestamp = self.cpu_rows.len(); let context = self.current_context; self.memory.log.push(MemoryOp { - channel_index: Some(channel_index), + filter: true, timestamp, is_read: false, context, diff --git a/evm/src/memory/columns.rs b/evm/src/memory/columns.rs index 7229a834..91cc8754 100644 --- a/evm/src/memory/columns.rs +++ b/evm/src/memory/columns.rs @@ -3,7 +3,9 @@ use crate::memory::{NUM_CHANNELS, VALUE_LIMBS}; // Columns for memory operations, ordered by (addr, timestamp). -pub(crate) const TIMESTAMP: usize = 0; +/// 1 if this is an actual memory operation, or 0 if it's a padding row. +pub(crate) const FILTER: usize = 0; +pub(crate) const TIMESTAMP: usize = FILTER + 1; pub(crate) const IS_READ: usize = TIMESTAMP + 1; pub(crate) const ADDR_CONTEXT: usize = IS_READ + 1; pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; @@ -25,15 +27,8 @@ pub(crate) const CONTEXT_FIRST_CHANGE: usize = VALUE_START + VALUE_LIMBS; pub(crate) const SEGMENT_FIRST_CHANGE: usize = CONTEXT_FIRST_CHANGE + 1; pub(crate) const VIRTUAL_FIRST_CHANGE: usize = SEGMENT_FIRST_CHANGE + 1; -// Flags to indicate if this operation came from the `i`th channel of the memory bus. -const IS_CHANNEL_START: usize = VIRTUAL_FIRST_CHANGE + 1; -pub(crate) const fn is_channel(channel: usize) -> usize { - debug_assert!(channel < NUM_CHANNELS); - IS_CHANNEL_START + channel -} - // We use a range check to enforce the ordering. -pub(crate) const RANGE_CHECK: usize = IS_CHANNEL_START + NUM_CHANNELS; +pub(crate) const RANGE_CHECK: usize = VIRTUAL_FIRST_CHANGE + NUM_CHANNELS; // The counter column (used for the range check) starts from 0 and increments. pub(crate) const COUNTER: usize = RANGE_CHECK + 1; // Helper columns for the permutation argument used to enforce the range check. diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 5a17ed20..8ed52ebb 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -16,12 +16,12 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cross_table_lookup::Column; use crate::lookup::{eval_lookups, eval_lookups_circuit, permuted_cols}; use crate::memory::columns::{ - is_channel, value_limb, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, CONTEXT_FIRST_CHANGE, - COUNTER, COUNTER_PERMUTED, IS_READ, NUM_COLUMNS, RANGE_CHECK, RANGE_CHECK_PERMUTED, + value_limb, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, CONTEXT_FIRST_CHANGE, COUNTER, + COUNTER_PERMUTED, FILTER, IS_READ, NUM_COLUMNS, RANGE_CHECK, RANGE_CHECK_PERMUTED, SEGMENT_FIRST_CHANGE, TIMESTAMP, VIRTUAL_FIRST_CHANGE, }; use crate::memory::segments::Segment; -use crate::memory::{NUM_CHANNELS, VALUE_LIMBS}; +use crate::memory::VALUE_LIMBS; use crate::permutation::PermutationPair; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -36,8 +36,8 @@ pub fn ctl_data() -> Vec> { res } -pub fn ctl_filter(channel: usize) -> Column { - Column::single(is_channel(channel)) +pub fn ctl_filter() -> Column { + Column::single(FILTER) } #[derive(Copy, Clone, Default)] @@ -47,8 +47,8 @@ pub struct MemoryStark { #[derive(Clone, Debug)] pub(crate) struct MemoryOp { - /// The channel this operation came from, or `None` if it's a dummy operation for padding. - pub channel_index: Option, + /// true if this is an actual memory operation, or false if it's a padding row. + pub filter: bool, pub timestamp: usize, pub is_read: bool, pub context: usize, @@ -64,9 +64,7 @@ impl MemoryOp { /// trace has been transposed into column-major form. fn to_row(&self) -> [F; NUM_COLUMNS] { let mut row = [F::ZERO; NUM_COLUMNS]; - if let Some(channel) = self.channel_index { - row[is_channel(channel)] = F::ONE; - } + row[FILTER] = F::from_bool(self.filter); row[TIMESTAMP] = F::from_canonical_usize(self.timestamp); row[IS_READ] = F::from_bool(self.is_read); row[ADDR_CONTEXT] = F::from_canonical_usize(self.context); @@ -178,12 +176,12 @@ impl, const D: usize> MemoryStark { // We essentially repeat the last operation until our operation list has the desired size, // with a few changes: - // - We change its channel to `None` to indicate that this is a dummy operation. + // - We change its filter to 0 to indicate that this is a dummy operation. // - We increment its timestamp in order to pass the ordering check. // - We make sure it's a read, sine dummy operations must be reads. for i in 0..to_pad { memory_ops.push(MemoryOp { - channel_index: None, + filter: false, timestamp: last_op.timestamp + i + 1, is_read: true, ..last_op @@ -245,21 +243,13 @@ impl, const D: usize> Stark for MemoryStark = (0..8).map(|i| vars.next_values[value_limb(i)]).collect(); - // Each `is_channel` value must be 0 or 1. - for c in 0..NUM_CHANNELS { - let is_channel = vars.local_values[is_channel(c)]; - yield_constr.constraint(is_channel * (is_channel - P::ONES)); - } + // The filter must be 0 or 1. + let filter = vars.local_values[FILTER]; + yield_constr.constraint(filter * (filter - P::ONES)); - // The sum of `is_channel` flags, `has_channel`, must also be 0 or 1. - let has_channel: P = (0..NUM_CHANNELS) - .map(|c| vars.local_values[is_channel(c)]) - .sum(); - yield_constr.constraint(has_channel * (has_channel - P::ONES)); - - // If this is a dummy row (with no channel), it must be a read. This means the prover can + // If this is a dummy row (filter is off), it must be a read. This means the prover can // insert reads which never appear in the CPU trace (which are harmless), but not writes. - let is_dummy = P::ONES - has_channel; + let is_dummy = P::ONES - filter; let is_write = P::ONES - vars.local_values[IS_READ]; yield_constr.constraint(is_dummy * is_write); @@ -330,22 +320,14 @@ impl, const D: usize> Stark for MemoryStark Date: Sun, 14 Aug 2022 16:36:07 -0700 Subject: [PATCH 22/95] Keccak memory stark --- evm/Cargo.toml | 1 + evm/src/all_stark.rs | 101 ++++++-- evm/src/cpu/columns/mod.rs | 5 +- evm/src/cpu/cpu_stark.rs | 22 +- evm/src/cross_table_lookup.rs | 19 +- evm/src/generation/state.rs | 79 +++++- evm/src/keccak_memory/columns.rs | 29 +++ evm/src/keccak_memory/keccak_memory_stark.rs | 240 +++++++++++++++++++ evm/src/keccak_memory/mod.rs | 2 + evm/src/lib.rs | 1 + evm/src/prover.rs | 24 +- evm/src/recursive_verifier.rs | 33 ++- evm/src/verifier.rs | 11 + 13 files changed, 539 insertions(+), 28 deletions(-) create mode 100644 evm/src/keccak_memory/columns.rs create mode 100644 evm/src/keccak_memory/keccak_memory_stark.rs create mode 100644 evm/src/keccak_memory/mod.rs diff --git a/evm/Cargo.toml b/evm/Cargo.toml index e844da3a..6ddde22e 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -23,6 +23,7 @@ rand_chacha = "0.3.1" rlp = "0.5.1" keccak-rust = { git = "https://github.com/npwardberkeley/keccak-rust" } keccak-hash = "0.9.0" +tiny-keccak = "2.0.2" [dev-dependencies] criterion = "0.3.5" diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 1131d529..64d9235a 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -8,6 +8,8 @@ use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns}; use crate::keccak::keccak_stark; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; @@ -18,6 +20,7 @@ use crate::stark::Stark; pub struct AllStark, const D: usize> { pub cpu_stark: CpuStark, pub keccak_stark: KeccakStark, + pub keccak_memory_stark: KeccakMemoryStark, pub logic_stark: LogicStark, pub memory_stark: MemoryStark, pub cross_table_lookups: Vec>, @@ -28,6 +31,7 @@ impl, const D: usize> Default for AllStark { Self { cpu_stark: CpuStark::default(), keccak_stark: KeccakStark::default(), + keccak_memory_stark: KeccakMemoryStark::default(), logic_stark: LogicStark::default(), memory_stark: MemoryStark::default(), cross_table_lookups: all_cross_table_lookups(), @@ -40,6 +44,7 @@ impl, const D: usize> AllStark { let ans = vec![ self.cpu_stark.num_permutation_batches(config), self.keccak_stark.num_permutation_batches(config), + self.keccak_memory_stark.num_permutation_batches(config), self.logic_stark.num_permutation_batches(config), self.memory_stark.num_permutation_batches(config), ]; @@ -51,6 +56,7 @@ impl, const D: usize> AllStark { let ans = vec![ self.cpu_stark.permutation_batch_size(), self.keccak_stark.permutation_batch_size(), + self.keccak_memory_stark.permutation_batch_size(), self.logic_stark.permutation_batch_size(), self.memory_stark.permutation_batch_size(), ]; @@ -63,8 +69,9 @@ impl, const D: usize> AllStark { pub enum Table { Cpu = 0, Keccak = 1, - Logic = 2, - Memory = 3, + KeccakMemory = 2, + Logic = 3, + Memory = 4, } impl Table { @@ -75,16 +82,22 @@ impl Table { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn all_cross_table_lookups() -> Vec> { - vec![ctl_keccak(), ctl_logic(), ctl_memory()] + vec![ctl_keccak(), ctl_logic(), ctl_memory(), ctl_keccak_memory()] } fn ctl_keccak() -> CrossTableLookup { + let cpu_looking = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_keccak(), + Some(cpu_stark::ctl_filter_keccak()), + ); + let keccak_memory_looking = TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looking_keccak(), + Some(keccak_memory_stark::ctl_filter()), + ); CrossTableLookup::new( - vec![TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_keccak(), - Some(cpu_stark::ctl_filter_keccak()), - )], + vec![cpu_looking, keccak_memory_looking], TableWithColumns::new( Table::Keccak, keccak_stark::ctl_data(), @@ -94,6 +107,22 @@ fn ctl_keccak() -> CrossTableLookup { ) } +fn ctl_keccak_memory() -> CrossTableLookup { + CrossTableLookup::new( + vec![TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_keccak_memory(), + Some(cpu_stark::ctl_filter_keccak_memory()), + )], + TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looked_data(), + Some(keccak_memory_stark::ctl_filter()), + ), + None, + ) +} + fn ctl_logic() -> CrossTableLookup { CrossTableLookup::new( vec![TableWithColumns::new( @@ -107,16 +136,33 @@ fn ctl_logic() -> CrossTableLookup { } fn ctl_memory() -> CrossTableLookup { + let cpu_memory_ops = (0..NUM_CHANNELS).map(|channel| { + TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_memory(channel), + Some(cpu_stark::ctl_filter_memory(channel)), + ) + }); + let keccak_memory_reads = (0..200).map(|i| { + TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looking_memory_read(i), + Some(keccak_memory_stark::ctl_filter()), + ) + }); + let keccak_memory_writes = (0..200).map(|i| { + TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looking_memory_write(i), + Some(keccak_memory_stark::ctl_filter()), + ) + }); + let all_lookers = cpu_memory_ops + .chain(keccak_memory_reads) + .chain(keccak_memory_writes) + .collect(); CrossTableLookup::new( - (0..NUM_CHANNELS) - .map(|channel| { - TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_memory(channel), - Some(cpu_stark::ctl_filter_memory(channel)), - ) - }) - .collect(), + all_lookers, TableWithColumns::new( Table::Memory, memory_stark::ctl_data(), @@ -142,12 +188,13 @@ mod tests { use plonky2::util::timing::TimingTree; use rand::{thread_rng, Rng}; - use crate::all_stark::AllStark; + use crate::all_stark::{AllStark, Table}; use crate::config::StarkConfig; use crate::cpu::cpu_stark::CpuStark; use crate::cpu::kernel::aggregator::KERNEL; use crate::cross_table_lookup::testutils::check_ctls; use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS}; + use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::{self, LogicStark, Operation}; use crate::memory::memory_stark::tests::generate_random_memory_ops; use crate::memory::memory_stark::MemoryStark; @@ -177,6 +224,13 @@ mod tests { keccak_stark.generate_trace(keccak_inputs) } + fn make_keccak_memory_trace( + keccak_memory_stark: &KeccakMemoryStark, + config: &StarkConfig, + ) -> Vec> { + keccak_memory_stark.generate_trace(vec![], config.fri_config.cap_height) + } + fn make_logic_trace( num_rows: usize, logic_stark: &LogicStark, @@ -607,6 +661,7 @@ mod tests { let num_keccak_perms = 2; let keccak_trace = make_keccak_trace(num_keccak_perms, &all_stark.keccak_stark, &mut rng); + let keccak_memory_trace = make_keccak_memory_trace(&all_stark.keccak_memory_stark, config); let logic_trace = make_logic_trace(num_logic_rows, &all_stark.logic_stark, &mut rng); let mem_trace = make_memory_trace(num_memory_ops, &all_stark.memory_stark, &mut rng); let mut memory_trace = mem_trace.0; @@ -621,14 +676,20 @@ mod tests { &mut memory_trace, ); - let traces = vec![cpu_trace, keccak_trace, logic_trace, memory_trace]; + let traces = vec![ + cpu_trace, + keccak_trace, + keccak_memory_trace, + logic_trace, + memory_trace, + ]; check_ctls(&traces, &all_stark.cross_table_lookups); let proof = prove::( &all_stark, config, traces, - vec![vec![]; 4], + vec![vec![]; Table::num_tables()], &mut TimingTree::default(), )?; diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 3016b2fd..4864bddc 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -153,9 +153,12 @@ pub struct CpuColumnsView { /// If CPU cycle: the opcode, broken up into bits in little-endian order. pub opcode_bits: [T; 8], - /// Filter. 1 iff a Keccak permutation is computed on this row. + /// Filter. 1 iff a Keccak lookup is performed on this row. pub is_keccak: T, + /// Filter. 1 iff a Keccak memory lookup is performed on this row. + pub is_keccak_memory: T, + pub(crate) general: CpuGeneralColumnsView, pub(crate) clock: T, diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 918f7d9b..0478c609 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -22,10 +22,30 @@ pub fn ctl_data_keccak() -> Vec> { res } +pub fn ctl_data_keccak_memory() -> Vec> { + // When executing KECCAK_GENERAL, the memory channels are used as follows: + // channel 0: instruction + // channel 1: stack[-1] = context + // channel 2: stack[-2] = segment + // channel 3: stack[-3] = virtual + let context = Column::single(COL_MAP.mem_value[1][0]); + let segment = Column::single(COL_MAP.mem_value[2][0]); + let virt = Column::single(COL_MAP.mem_value[3][0]); + + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + let clock = Column::linear_combination([(COL_MAP.clock, num_channels)]); + + vec![context, segment, virt, clock] +} + pub fn ctl_filter_keccak() -> Column { Column::single(COL_MAP.is_keccak) } +pub fn ctl_filter_keccak_memory() -> Column { + Column::single(COL_MAP.is_keccak_memory) +} + pub fn ctl_data_logic() -> Vec> { let mut res = Column::singles([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]).collect_vec(); let logic = COL_MAP.general.logic(); @@ -53,7 +73,7 @@ pub fn ctl_data_memory(channel: usize) -> Vec> { let scalar = F::from_canonical_usize(NUM_CHANNELS); let addend = F::from_canonical_usize(channel); cols.push(Column::linear_combination_with_constant( - vec![(COL_MAP.clock, scalar)], + [(COL_MAP.clock, scalar)], addend, )); diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index b4b8d6fb..6071f8ff 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -24,7 +24,7 @@ use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; /// Represent a linear combination of columns. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Column { linear_combination: Vec<(usize, F)>, constant: F, @@ -42,6 +42,17 @@ impl Column { cs.into_iter().map(Self::single) } + pub fn constant(constant: F) -> Self { + Self { + linear_combination: vec![], + constant, + } + } + + pub fn zero() -> Self { + Self::constant(F::ZERO) + } + pub fn linear_combination_with_constant>( iter: I, constant: F, @@ -67,6 +78,10 @@ impl Column { Self::linear_combination(cs.into_iter().zip(F::TWO.powers())) } + pub fn le_bytes>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().zip(F::from_canonical_u16(256).powers())) + } + pub fn sum>(cs: I) -> Self { Self::linear_combination(cs.into_iter().zip(repeat(F::ONE))) } @@ -115,7 +130,7 @@ impl Column { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TableWithColumns { table: Table, columns: Vec>, diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 04ab4016..72d404b8 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -2,11 +2,14 @@ use std::mem; use ethereum_types::U256; use plonky2::field::types::Field; +use tiny_keccak::keccakf; use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::generation::memory::MemoryState; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; use crate::memory::segments::Segment; +use crate::memory::NUM_CHANNELS; use crate::{keccak, logic}; #[derive(Debug)] @@ -18,6 +21,7 @@ pub(crate) struct GenerationState { pub(crate) memory: MemoryState, pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>, + pub(crate) keccak_memory_inputs: Vec, pub(crate) logic_ops: Vec, /// Non-deterministic inputs provided by the prover. @@ -58,10 +62,21 @@ impl GenerationState { channel_index: usize, segment: Segment, virt: usize, + ) -> U256 { + let context = self.current_context; + self.get_mem(channel_index, context, segment, virt) + } + + /// Read some memory, and log the operation. + pub(crate) fn get_mem( + &mut self, + channel_index: usize, + context: usize, + segment: Segment, + virt: usize, ) -> U256 { self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; let timestamp = self.cpu_rows.len(); - let context = self.current_context; let value = self.memory.contexts[context].segments[segment as usize].get(virt); self.memory.log.push(MemoryOp { filter: true, @@ -82,10 +97,23 @@ impl GenerationState { segment: Segment, virt: usize, value: U256, + ) { + let context = self.current_context; + self.set_mem(channel_index, context, segment, virt, value); + } + + /// Write some memory, and log the operation. + pub(crate) fn set_mem( + &mut self, + channel_index: usize, + context: usize, + segment: Segment, + virt: usize, + value: U256, ) { self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; let timestamp = self.cpu_rows.len(); - let context = self.current_context; + let timestamp = timestamp * NUM_CHANNELS + channel_index; self.memory.log.push(MemoryOp { filter: true, timestamp, @@ -98,6 +126,52 @@ impl GenerationState { self.memory.contexts[context].segments[segment as usize].set(virt, value) } + /// Evaluate the Keccak-f permutation in-place on some data in memory, and record the operations + /// for the purpose of witness generation. + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn keccak_memory( + &mut self, + context: usize, + segment: Segment, + virt: usize, + ) -> [u64; keccak::keccak_stark::NUM_INPUTS] { + let read_timestamp = self.cpu_rows.len() * NUM_CHANNELS; + let input = (0..25) + .map(|i| { + let bytes = [0, 1, 2, 3, 4, 5, 6, 7].map(|j| { + let virt = virt + i * 8 + j; + let byte = self.get_mem(0, context, segment, virt); + debug_assert!(byte.bits() <= 8); + byte.as_u32() as u8 + }); + u64::from_le_bytes(bytes) + }) + .collect::>() + .try_into() + .unwrap(); + let output = self.keccak(input); + self.keccak_memory_inputs.push(KeccakMemoryOp { + context, + segment, + virt, + read_timestamp, + input, + output, + }); + output + } + + /// Evaluate the Keccak-f permutation, and record the operation for the purpose of witness + /// generation. + pub(crate) fn keccak( + &mut self, + mut input: [u64; keccak::keccak_stark::NUM_INPUTS], + ) -> [u64; keccak::keccak_stark::NUM_INPUTS] { + self.keccak_inputs.push(input); + keccakf(&mut input); + input + } + pub(crate) fn commit_cpu_row(&mut self) { let mut swapped_row = [F::ZERO; NUM_CPU_COLUMNS].into(); mem::swap(&mut self.current_cpu_row, &mut swapped_row); @@ -115,6 +189,7 @@ impl Default for GenerationState { current_context: 0, memory: MemoryState::default(), keccak_inputs: vec![], + keccak_memory_inputs: vec![], logic_ops: vec![], prover_inputs: vec![], } diff --git a/evm/src/keccak_memory/columns.rs b/evm/src/keccak_memory/columns.rs new file mode 100644 index 00000000..92bdbf2b --- /dev/null +++ b/evm/src/keccak_memory/columns.rs @@ -0,0 +1,29 @@ +pub(crate) const KECCAK_WIDTH_BYTES: usize = 200; + +/// 1 if this row represents a real operation; 0 if it's a padding row. +pub(crate) const COL_IS_REAL: usize = 0; + +// The address at which we will read inputs and write outputs. +pub(crate) const COL_CONTEXT: usize = 1; +pub(crate) const COL_SEGMENT: usize = 2; +pub(crate) const COL_VIRTUAL: usize = 3; + +/// The timestamp at which inputs should be read from memory. +/// Outputs will be written at the following timestamp. +pub(crate) const COL_READ_TIMESTAMP: usize = 4; + +const START_INPUT_LIMBS: usize = 5; +/// A byte of the input. +pub(crate) fn col_input_byte(i: usize) -> usize { + debug_assert!(i < KECCAK_WIDTH_BYTES); + START_INPUT_LIMBS + i +} + +const START_OUTPUT_LIMBS: usize = START_INPUT_LIMBS + KECCAK_WIDTH_BYTES; +/// A byte of the output. +pub(crate) fn col_output_byte(i: usize) -> usize { + debug_assert!(i < KECCAK_WIDTH_BYTES); + START_OUTPUT_LIMBS + i +} + +pub const NUM_COLUMNS: usize = START_OUTPUT_LIMBS + KECCAK_WIDTH_BYTES; diff --git a/evm/src/keccak_memory/keccak_memory_stark.rs b/evm/src/keccak_memory/keccak_memory_stark.rs new file mode 100644 index 00000000..780f3f03 --- /dev/null +++ b/evm/src/keccak_memory/keccak_memory_stark.rs @@ -0,0 +1,240 @@ +use std::io::Read; +use std::marker::PhantomData; + +use itertools::Itertools; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::timed; +use plonky2::util::timing::TimingTree; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cross_table_lookup::Column; +use crate::keccak::keccak_stark::NUM_INPUTS; +use crate::keccak_memory::columns::*; +use crate::memory::segments::Segment; +use crate::stark::Stark; +use crate::util::trace_rows_to_poly_values; +use crate::vars::StarkEvaluationTargets; +use crate::vars::StarkEvaluationVars; + +const NUM_PUBLIC_INPUTS: usize = 0; + +pub(crate) fn ctl_looked_data() -> Vec> { + Column::singles([COL_CONTEXT, COL_SEGMENT, COL_VIRTUAL, COL_READ_TIMESTAMP]).collect() +} + +pub(crate) fn ctl_looking_keccak() -> Vec> { + let input_cols = (0..50).map(|i| { + Column::le_bytes((0..4).map(|j| { + let byte_index = i * 4 + j; + col_input_byte(byte_index) + })) + }); + let output_cols = (0..50).map(|i| { + Column::le_bytes((0..4).map(|j| { + let byte_index = i * 4 + j; + col_output_byte(byte_index) + })) + }); + input_cols.chain(output_cols).collect() +} + +pub(crate) fn ctl_looking_memory_read(i: usize) -> Vec> { + ctl_looking_memory(i, true) +} + +pub(crate) fn ctl_looking_memory_write(i: usize) -> Vec> { + ctl_looking_memory(i, false) +} + +fn ctl_looking_memory(i: usize, is_read: bool) -> Vec> { + let mut res = vec![Column::constant(F::from_bool(is_read))]; + res.extend(Column::singles([COL_CONTEXT, COL_SEGMENT, COL_VIRTUAL])); + + res.push(Column::single(col_input_byte(i))); + // Since we're reading or writing a single byte, the higher limbs must be zero. + res.extend((1..8).map(|_| Column::zero())); + + // Since COL_READ_TIMESTAMP is the read time, we add 1 if this is a write. + let is_write_f = F::from_bool(!is_read); + res.push(Column::linear_combination_with_constant( + [(COL_READ_TIMESTAMP, F::ONE)], + is_write_f, + )); + + assert_eq!( + res.len(), + crate::memory::memory_stark::ctl_data::().len() + ); + res +} + +/// CTL filter used for both directions (looked and looking). +pub(crate) fn ctl_filter() -> Column { + Column::single(COL_IS_REAL) +} + +/// Information about a Keccak memory operation needed for witness generation. +#[derive(Debug)] +pub(crate) struct KeccakMemoryOp { + // The address at which we will read inputs and write outputs. + pub(crate) context: usize, + pub(crate) segment: Segment, + pub(crate) virt: usize, + + /// The timestamp at which inputs should be read from memory. + /// Outputs will be written at the following timestamp. + pub(crate) read_timestamp: usize, + + /// The input that was read at that address. + pub(crate) input: [u64; NUM_INPUTS], + pub(crate) output: [u64; NUM_INPUTS], +} + +#[derive(Copy, Clone, Default)] +pub struct KeccakMemoryStark { + pub(crate) f: PhantomData, +} + +impl, const D: usize> KeccakMemoryStark { + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn generate_trace( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec> { + let mut timing = TimingTree::new("generate trace", log::Level::Debug); + + // Generate the witness row-wise. + let trace_rows = timed!( + &mut timing, + "generate trace rows", + self.generate_trace_rows(operations, min_rows) + ); + + let trace_polys = timed!( + &mut timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + + timing.print(); + trace_polys + } + + fn generate_trace_rows( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec<[F; NUM_COLUMNS]> { + let num_rows = operations.len().max(32).next_power_of_two(); + let mut rows = Vec::with_capacity(num_rows); + for op in operations { + rows.push(self.generate_row_for_op(op)); + } + + let padding_row = self.generate_padding_row(); + for _ in rows.len()..num_rows { + rows.push(padding_row); + } + rows + } + + fn generate_row_for_op(&self, op: KeccakMemoryOp) -> [F; NUM_COLUMNS] { + let mut row = [F::ZERO; NUM_COLUMNS]; + row[COL_IS_REAL] = F::ONE; + row[COL_CONTEXT] = F::from_canonical_usize(op.context); + row[COL_SEGMENT] = F::from_canonical_usize(op.segment as usize); + row[COL_VIRTUAL] = F::from_canonical_usize(op.virt); + row[COL_READ_TIMESTAMP] = F::from_canonical_usize(op.read_timestamp); + for i in 0..25 { + let input_u64 = op.input[i]; + let output_u64 = op.output[i]; + for j in 0..8 { + let byte_index = i * 8 + j; + row[col_input_byte(byte_index)] = F::from_canonical_u8(input_u64.to_le_bytes()[j]); + row[col_output_byte(byte_index)] = + F::from_canonical_u8(output_u64.to_le_bytes()[j]); + } + } + row + } + + fn generate_padding_row(&self) -> [F; NUM_COLUMNS] { + // We just need COL_IS_REAL to be zero, which it is by default. + // The other fields will have no effect. + [F::ZERO; NUM_COLUMNS] + } +} + +impl, const D: usize> Stark for KeccakMemoryStark { + const COLUMNS: usize = NUM_COLUMNS; + const PUBLIC_INPUTS: usize = NUM_PUBLIC_INPUTS; + + fn eval_packed_generic( + &self, + vars: StarkEvaluationVars, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + // is_real must be 0 or 1. + let is_real = vars.local_values[COL_IS_REAL]; + yield_constr.constraint(is_real * (is_real - P::ONES)); + } + + fn eval_ext_circuit( + &self, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: StarkEvaluationTargets, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + // is_real must be 0 or 1. + let is_real = vars.local_values[COL_IS_REAL]; + let constraint = builder.mul_sub_extension(is_real, is_real, is_real); + yield_constr.constraint(builder, constraint); + } + + fn constraint_degree(&self) -> usize { + 2 + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; + use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; + + #[test] + fn test_stark_degree() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakMemoryStark; + + let stark = S { + f: Default::default(), + }; + test_stark_low_degree(stark) + } + + #[test] + fn test_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakMemoryStark; + + let stark = S { + f: Default::default(), + }; + test_stark_circuit_constraints::(stark) + } +} diff --git a/evm/src/keccak_memory/mod.rs b/evm/src/keccak_memory/mod.rs new file mode 100644 index 00000000..7b5e3d01 --- /dev/null +++ b/evm/src/keccak_memory/mod.rs @@ -0,0 +1,2 @@ +pub mod columns; +pub mod keccak_memory_stark; diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 47335db2..0a31a7ba 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -13,6 +13,7 @@ pub mod cross_table_lookup; pub mod generation; mod get_challenges; pub mod keccak; +pub mod keccak_memory; pub mod logic; pub mod lookup; pub mod memory; diff --git a/evm/src/prover.rs b/evm/src/prover.rs index e93ad754..8c33c289 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -23,6 +23,7 @@ use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{cross_table_lookup_data, CtlCheckVars, CtlData}; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckVars; @@ -50,6 +51,8 @@ where [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakStark::::PUBLIC_INPUTS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, @@ -124,6 +127,19 @@ where &mut challenger, timing, )?; + let keccak_memory_proof = prove_single_table( + &all_stark.keccak_memory_stark, + config, + &trace_poly_values[Table::KeccakMemory as usize], + &trace_commitments[Table::KeccakMemory as usize], + &ctl_data_per_table[Table::KeccakMemory as usize], + public_inputs[Table::KeccakMemory as usize] + .clone() + .try_into() + .unwrap(), + &mut challenger, + timing, + )?; let logic_proof = prove_single_table( &all_stark.logic_stark, config, @@ -151,7 +167,13 @@ where timing, )?; - let stark_proofs = vec![cpu_proof, keccak_proof, logic_proof, memory_proof]; + let stark_proofs = vec![ + cpu_proof, + keccak_proof, + keccak_memory_proof, + logic_proof, + memory_proof, + ]; debug_assert_eq!(stark_proofs.len(), num_starks); Ok(AllProof { stark_proofs }) diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index b69a5519..6eed4717 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -17,6 +17,7 @@ use crate::constraint_consumer::RecursiveConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{verify_cross_table_lookups_circuit, CtlCheckVarsTarget}; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckDataTarget; @@ -43,6 +44,8 @@ pub fn verify_proof_circuit< [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakStark::::PUBLIC_INPUTS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, @@ -59,6 +62,7 @@ pub fn verify_proof_circuit< let AllStark { cpu_stark, keccak_stark, + keccak_memory_stark, logic_stark, memory_stark, cross_table_lookups, @@ -95,6 +99,18 @@ pub fn verify_proof_circuit< inner_config, ) ); + with_context!( + builder, + "verify Keccak memory proof", + verify_stark_proof_with_challenges_circuit::( + builder, + keccak_memory_stark, + &all_proof.stark_proofs[Table::KeccakMemory as usize], + &stark_challenges[Table::KeccakMemory as usize], + &ctl_vars_per_table[Table::KeccakMemory as usize], + inner_config, + ) + ); with_context!( builder, "verify logic proof", @@ -309,6 +325,21 @@ pub fn add_virtual_all_proof, const D: usize>( public_inputs, } }, + { + let proof = add_virtual_stark_proof( + builder, + all_stark.keccak_memory_stark, + config, + degree_bits[Table::KeccakMemory as usize], + nums_ctl_zs[Table::KeccakMemory as usize], + ); + let public_inputs = + builder.add_virtual_targets(KeccakMemoryStark::::PUBLIC_INPUTS); + StarkProofWithPublicInputsTarget { + proof, + public_inputs, + } + }, { let proof = add_virtual_stark_proof( builder, @@ -331,7 +362,7 @@ pub fn add_virtual_all_proof, const D: usize>( degree_bits[Table::Memory as usize], nums_ctl_zs[Table::Memory as usize], ); - let public_inputs = builder.add_virtual_targets(KeccakStark::::PUBLIC_INPUTS); + let public_inputs = builder.add_virtual_targets(MemoryStark::::PUBLIC_INPUTS); StarkProofWithPublicInputsTarget { proof, public_inputs, diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 1b46dc90..c8e3b8e6 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -12,6 +12,7 @@ use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{verify_cross_table_lookups, CtlCheckVars}; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckVars; @@ -32,6 +33,8 @@ where [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakStark::::PUBLIC_INPUTS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, @@ -48,6 +51,7 @@ where let AllStark { cpu_stark, keccak_stark, + keccak_memory_stark, logic_stark, memory_stark, cross_table_lookups, @@ -74,6 +78,13 @@ where &ctl_vars_per_table[Table::Keccak as usize], config, )?; + verify_stark_proof_with_challenges( + keccak_memory_stark, + &all_proof.stark_proofs[Table::KeccakMemory as usize], + &stark_challenges[Table::KeccakMemory as usize], + &ctl_vars_per_table[Table::KeccakMemory as usize], + config, + )?; verify_stark_proof_with_challenges( memory_stark, &all_proof.stark_proofs[Table::Memory as usize], From 748496442b6847537a2444aabd35f83160b60d2a Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 10:19:08 -0700 Subject: [PATCH 23/95] Include degree in circuit digest --- plonky2/src/plonk/circuit_builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 9da07a2e..6728551c 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -794,7 +794,10 @@ impl, const D: usize> CircuitBuilder { // TODO: This should also include an encoding of gate constraints. let circuit_digest_parts = [ constants_sigmas_cap.flatten(), - vec![/* Add other circuit data here */], + vec![ + F::from_canonical_usize(degree_bits), + /* Add other circuit data here */ + ], ]; let circuit_digest = C::Hasher::hash_no_pad(&circuit_digest_parts.concat()); From 74cb9074d678d5289b14077055a8d4a343a6f80b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 11:51:30 -0700 Subject: [PATCH 24/95] Minor fixes --- evm/src/keccak_memory/keccak_memory_stark.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/evm/src/keccak_memory/keccak_memory_stark.rs b/evm/src/keccak_memory/keccak_memory_stark.rs index 780f3f03..d12850e1 100644 --- a/evm/src/keccak_memory/keccak_memory_stark.rs +++ b/evm/src/keccak_memory/keccak_memory_stark.rs @@ -1,7 +1,5 @@ -use std::io::Read; use std::marker::PhantomData; -use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; @@ -130,7 +128,7 @@ impl, const D: usize> KeccakMemoryStark { operations: Vec, min_rows: usize, ) -> Vec<[F; NUM_COLUMNS]> { - let num_rows = operations.len().max(32).next_power_of_two(); + let num_rows = operations.len().max(min_rows).next_power_of_two(); let mut rows = Vec::with_capacity(num_rows); for op in operations { rows.push(self.generate_row_for_op(op)); From c140555f2b9ef3f3935d21b000cce0b230f397c3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 11:53:27 -0700 Subject: [PATCH 25/95] Fix --- evm/src/all_stark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 64d9235a..c251ce86 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -228,7 +228,7 @@ mod tests { keccak_memory_stark: &KeccakMemoryStark, config: &StarkConfig, ) -> Vec> { - keccak_memory_stark.generate_trace(vec![], config.fri_config.cap_height) + keccak_memory_stark.generate_trace(vec![], 1 << config.fri_config.cap_height) } fn make_logic_trace( From fb34b098884d578195c742f4f5c5da5817aa398a Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 15:35:52 -0700 Subject: [PATCH 26/95] Remove keccak_rust in favor of tiny-keccak `keccak_rust` doesn't seem to have much usage, and it treats `x` as the major axis of its 5x5 input. This is not exactly wrong, since Keccak itself doesn't have a notion of axis order. However, there is a convention for mapping bits of the cube to a flat list of bits, which is > The mapping between the bits of `s` and those of `a` is `s[w(5y + x) + z] = a[x][y][z]`. Obeying this convention would be awkward with `keccak_rust` - the words in memory would need to be transposed. --- evm/Cargo.toml | 1 - evm/src/keccak/columns.rs | 14 ++++++++++---- evm/src/keccak/keccak_stark.rs | 33 +++++++++++++-------------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index e844da3a..0f34afed 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -21,7 +21,6 @@ maybe_rayon = { path = "../maybe_rayon" } rand = "0.8.5" rand_chacha = "0.3.1" rlp = "0.5.1" -keccak-rust = { git = "https://github.com/npwardberkeley/keccak-rust" } keccak-hash = "0.9.0" [dev-dependencies] diff --git a/evm/src/keccak/columns.rs b/evm/src/keccak/columns.rs index 39116b4a..8313c676 100644 --- a/evm/src/keccak/columns.rs +++ b/evm/src/keccak/columns.rs @@ -15,8 +15,11 @@ pub const fn reg_step(i: usize) -> usize { pub fn reg_input_limb(i: usize) -> Column { debug_assert!(i < 2 * NUM_INPUTS); let i_u64 = i / 2; // The index of the 64-bit chunk. - let x = i_u64 / 5; - let y = i_u64 % 5; + + // The 5x5 state is treated as y-major, as per the Keccak spec. + let y = i_u64 / 5; + let x = i_u64 % 5; + let reg_low_limb = reg_a(x, y); let is_high_limb = i % 2; Column::single(reg_low_limb + is_high_limb) @@ -28,8 +31,11 @@ pub fn reg_input_limb(i: usize) -> Column { pub const fn reg_output_limb(i: usize) -> usize { debug_assert!(i < 2 * NUM_INPUTS); let i_u64 = i / 2; // The index of the 64-bit chunk. - let x = i_u64 / 5; - let y = i_u64 % 5; + + // The 5x5 state is treated as y-major, as per the Keccak spec. + let y = i_u64 / 5; + let x = i_u64 % 5; + let is_high_limb = i % 2; reg_a_prime_prime_prime(x, y) + is_high_limb } diff --git a/evm/src/keccak/keccak_stark.rs b/evm/src/keccak/keccak_stark.rs index 94fa795d..d405c0e8 100644 --- a/evm/src/keccak/keccak_stark.rs +++ b/evm/src/keccak/keccak_stark.rs @@ -76,7 +76,7 @@ impl, const D: usize> KeccakStark { for x in 0..5 { for y in 0..5 { - let input_xy = input[x * 5 + y]; + let input_xy = input[y * 5 + x]; let reg_lo = reg_a(x, y); let reg_hi = reg_lo + 1; rows[0][reg_lo] = F::from_canonical_u64(input_xy & 0xFFFFFFFF); @@ -547,9 +547,9 @@ impl, const D: usize> Stark for KeccakStark>(); - let mut keccak_input: [[u64; 5]; 5] = [ - input[0..5].try_into().unwrap(), - input[5..10].try_into().unwrap(), - input[10..15].try_into().unwrap(), - input[15..20].try_into().unwrap(), - input[20..25].try_into().unwrap(), - ]; - - let keccak = KeccakF::new(StateBitsWidth::F1600); - keccak.permutations(&mut keccak_input); - let expected: Vec<_> = keccak_input - .iter() - .flatten() - .map(|&x| F::from_canonical_u64(x)) - .collect(); + let expected = { + let mut state = input; + keccakf(&mut state); + state + }; assert_eq!(output, expected); From 4112065692ab2c94cfd5f4a3aa2d55707c58882e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 16:06:50 -0700 Subject: [PATCH 27/95] Fix --- evm/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 0f34afed..db774345 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -22,6 +22,7 @@ rand = "0.8.5" rand_chacha = "0.3.1" rlp = "0.5.1" keccak-hash = "0.9.0" +tiny-keccak = "2.0.2" [dev-dependencies] criterion = "0.3.5" From 095140fda59826ce5a729c2f485d0882ff55c452 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 20:10:58 -0700 Subject: [PATCH 28/95] Use KECCAK_WIDTH_BYTES --- evm/src/all_stark.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index c251ce86..24499a0b 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -8,6 +8,7 @@ use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns}; use crate::keccak::keccak_stark; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::columns::KECCAK_WIDTH_BYTES; use crate::keccak_memory::keccak_memory_stark; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic; @@ -143,14 +144,14 @@ fn ctl_memory() -> CrossTableLookup { Some(cpu_stark::ctl_filter_memory(channel)), ) }); - let keccak_memory_reads = (0..200).map(|i| { + let keccak_memory_reads = (0..KECCAK_WIDTH_BYTES).map(|i| { TableWithColumns::new( Table::KeccakMemory, keccak_memory_stark::ctl_looking_memory_read(i), Some(keccak_memory_stark::ctl_filter()), ) }); - let keccak_memory_writes = (0..200).map(|i| { + let keccak_memory_writes = (0..KECCAK_WIDTH_BYTES).map(|i| { TableWithColumns::new( Table::KeccakMemory, keccak_memory_stark::ctl_looking_memory_write(i), From ff228c9386a38c6856011070ad115f524c7f530c Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 24 Aug 2022 20:41:32 -0700 Subject: [PATCH 29/95] Have witness generation take a partial trie instead of Merkle proofs --- evm/src/generation/mod.rs | 42 ++++++++++++++---------------- evm/src/generation/partial_trie.rs | 24 +++++++++++++++++ evm/src/generation/state.rs | 4 --- evm/tests/transfer_to_new_addr.rs | 15 +++++------ 4 files changed, 50 insertions(+), 35 deletions(-) create mode 100644 evm/src/generation/partial_trie.rs diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 02c91d16..72b2be12 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,4 +1,4 @@ -use ethereum_types::U256; +use ethereum_types::Address; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; @@ -7,25 +7,33 @@ use plonky2::hash::hash_types::RichField; use crate::all_stark::AllStark; use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; use crate::cpu::columns::NUM_CPU_COLUMNS; +use crate::generation::partial_trie::PartialTrie; use crate::generation::state::GenerationState; use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; +pub mod partial_trie; pub(crate) mod state; -/// A piece of data which has been encoded using Recursive Length Prefix (RLP) serialization. -/// See https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ -pub type RlpBlob = Vec; - -/// Merkle proofs are encoded using an RLP blob for each node in the path. -pub type RlpMerkleProof = Vec; - #[allow(unused)] // TODO: Should be used soon. pub struct TransactionData { pub signed_txn: Vec, - /// A Merkle proof for each interaction with the state trie, ordered chronologically. - pub trie_proofs: Vec, + /// A partial version of the state trie prior to this transaction. It should include all nodes + /// that will be accessed by this transaction. + pub state_trie: PartialTrie, + + /// A partial version of the transaction trie prior to this transaction. It should include all + /// nodes that will be accessed by this transaction. + pub transaction_trie: PartialTrie, + + /// A partial version of the receipt trie prior to this transaction. It should include all nodes + /// that will be accessed by this transaction. + pub receipt_trie: PartialTrie, + + /// A partial version of each storage trie prior to this transaction. It should include all + /// storage tries, and nodes therein, that will be accessed by this transaction. + pub storage_tries: Vec<(Address, PartialTrie)>, } #[allow(unused)] // TODO: Should be used soon. @@ -47,11 +55,9 @@ pub fn generate_traces, const D: usize>( memory, keccak_inputs, logic_ops, - prover_inputs, .. } = state; assert_eq!(current_cpu_row, [F::ZERO; NUM_CPU_COLUMNS].into()); - assert_eq!(prover_inputs, vec![], "Not all prover inputs were consumed"); let cpu_trace = trace_rows_to_poly_values(cpu_rows); let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); @@ -60,14 +66,6 @@ pub fn generate_traces, const D: usize>( vec![cpu_trace, keccak_trace, logic_trace, memory_trace] } -fn generate_txn(state: &mut GenerationState, txn: &TransactionData) { - // TODO: Add transaction RLP to prover_input. - - // Supply Merkle trie proofs as prover inputs. - for proof in &txn.trie_proofs { - let proof = proof - .iter() - .flat_map(|node_rlp| node_rlp.iter().map(|byte| U256::from(*byte))); - state.prover_inputs.extend(proof); - } +fn generate_txn(_state: &mut GenerationState, _txn: &TransactionData) { + // TODO } diff --git a/evm/src/generation/partial_trie.rs b/evm/src/generation/partial_trie.rs new file mode 100644 index 00000000..509379aa --- /dev/null +++ b/evm/src/generation/partial_trie.rs @@ -0,0 +1,24 @@ +use ethereum_types::U256; + +/// A partial trie, or a sub-trie thereof. +pub enum PartialTrie { + /// An empty trie. + Empty, + /// The digest of trie whose data does not need to be stored. + Hash(U256), + /// A branch node, which consists of 16 children and an optional value. + Branch([Box; 16], Option), + /// An extension node, which consists of a list of nibbles and a single child. + Extension(Nibbles, Box), + /// A leaf node, which consists of a list of nibbles and a value. + Leaf(Nibbles, Vec), +} + +/// A sequence of nibbles. +pub struct Nibbles { + /// The number of nibbles in this sequence. + pub count: usize, + /// A packed encoding of these nibbles. Only the first (least significant) `4 * count` bits are + /// used. The rest are unused and should be zero. + pub packed: U256, +} diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 04ab4016..1dfaca05 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -19,9 +19,6 @@ pub(crate) struct GenerationState { pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>, pub(crate) logic_ops: Vec, - - /// Non-deterministic inputs provided by the prover. - pub(crate) prover_inputs: Vec, } impl GenerationState { @@ -116,7 +113,6 @@ impl Default for GenerationState { memory: MemoryState::default(), keccak_inputs: vec![], logic_ops: vec![], - prover_inputs: vec![], } } } diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index c30e7b7b..e59e5d85 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -4,6 +4,7 @@ use plonky2::plonk::config::PoseidonGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; +use plonky2_evm::generation::partial_trie::PartialTrie; use plonky2_evm::generation::{generate_traces, TransactionData}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; @@ -20,15 +21,11 @@ fn test_simple_transfer() -> anyhow::Result<()> { let txn = TransactionData { signed_txn: hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38").to_vec(), - trie_proofs: vec![ - vec![ - hex!("f874a1202f93d0dfb1562c03c825a33eec4438e468c17fff649ae844c004065985ae2945b850f84e058a152d02c7e14af6800000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(), - ], - vec![ - hex!("f8518080a0d36b8b6b60021940d5553689fb33e5d45e649dd8f4f211d26566238a83169da58080a0c62aa627943b70321f89a8b2fea274ecd47116e62042077dcdc0bdca7c1f66738080808080808080808080").to_vec(), - hex!("f873a03f93d0dfb1562c03c825a33eec4438e468c17fff649ae844c004065985ae2945b850f84e068a152d02c7e14af67ccb4ca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(), - ], - ] + // TODO: Add trie with sender account. + state_trie: PartialTrie::Empty, + transaction_trie: PartialTrie::Empty, + receipt_trie: PartialTrie::Empty, + storage_tries: vec![], }; let traces = generate_traces(&all_stark, &[txn]); From 6b3853592b2fa986fc2a46e26ea3f62285ade057 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 08:05:39 -0700 Subject: [PATCH 30/95] Feedback --- evm/src/generation/partial_trie.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/evm/src/generation/partial_trie.rs b/evm/src/generation/partial_trie.rs index 509379aa..96751310 100644 --- a/evm/src/generation/partial_trie.rs +++ b/evm/src/generation/partial_trie.rs @@ -1,17 +1,25 @@ use ethereum_types::U256; -/// A partial trie, or a sub-trie thereof. +/// A partial trie, or a sub-trie thereof. This mimics the structure of an Ethereum trie, except +/// with an additional `Hash` node type, representing a node whose data is not needed to process +/// our transaction. pub enum PartialTrie { /// An empty trie. Empty, /// The digest of trie whose data does not need to be stored. Hash(U256), /// A branch node, which consists of 16 children and an optional value. - Branch([Box; 16], Option), + Branch { + children: [Box; 16], + value: Option, + }, /// An extension node, which consists of a list of nibbles and a single child. - Extension(Nibbles, Box), + Extension { + nibbles: Nibbles, + child: Box, + }, /// A leaf node, which consists of a list of nibbles and a value. - Leaf(Nibbles, Vec), + Leaf { nibbles: Nibbles, value: Vec }, } /// A sequence of nibbles. From f1a5b7b2d19b641bf452d624af2ed342b9e97130 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Thu, 25 Aug 2022 11:56:25 -0500 Subject: [PATCH 31/95] Delete opcode column (#672) --- evm/src/all_stark.rs | 52 ++++++++++++++++++++++----------- evm/src/cpu/columns/mod.rs | 3 -- evm/src/cpu/decode.rs | 59 +++++++++++--------------------------- 3 files changed, 52 insertions(+), 62 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 1131d529..b687d48e 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -205,6 +205,19 @@ mod tests { (trace, num_ops) } + fn bits_from_opcode(opcode: u8) -> [F; 8] { + [ + F::from_bool(opcode & (1 << 0) != 0), + F::from_bool(opcode & (1 << 1) != 0), + F::from_bool(opcode & (1 << 2) != 0), + F::from_bool(opcode & (1 << 3) != 0), + F::from_bool(opcode & (1 << 4) != 0), + F::from_bool(opcode & (1 << 5) != 0), + F::from_bool(opcode & (1 << 6) != 0), + F::from_bool(opcode & (1 << 7) != 0), + ] + } + fn make_cpu_trace( num_keccak_perms: usize, num_logic_rows: usize, @@ -263,16 +276,21 @@ mod tests { [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; + // Since these are the first cycle rows, we must start with PC=route_txn then increment. row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"] + i); - row.opcode = [ - (logic::columns::IS_AND, 0x16), - (logic::columns::IS_OR, 0x17), - (logic::columns::IS_XOR, 0x18), - ] - .into_iter() - .map(|(col, opcode)| logic_trace[col].values[i] * F::from_canonical_u64(opcode)) - .sum(); + row.opcode_bits = bits_from_opcode( + if logic_trace[logic::columns::IS_AND].values[i] != F::ZERO { + 0x16 + } else if logic_trace[logic::columns::IS_OR].values[i] != F::ZERO { + 0x17 + } else if logic_trace[logic::columns::IS_XOR].values[i] != F::ZERO { + 0x18 + } else { + panic!() + }, + ); + let logic = row.general.logic_mut(); let input0_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT0); @@ -330,7 +348,7 @@ mod tests { let last_row: cpu::columns::CpuColumnsView = cpu_trace_rows[cpu_trace_rows.len() - 1].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0x0a); // `EXP` is implemented in software + row.opcode_bits = bits_from_opcode(0x0a); // `EXP` is implemented in software row.is_kernel_mode = F::ONE; row.program_counter = last_row.program_counter + F::ONE; row.general.syscalls_mut().output = [ @@ -352,7 +370,7 @@ mod tests { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0xf9); + row.opcode_bits = bits_from_opcode(0xf9); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_usize(KERNEL.global_labels["sys_exp"]); row.general.jumps_mut().input0 = [ @@ -374,7 +392,7 @@ mod tests { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0x56); + row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_u16(15682); row.general.jumps_mut().input0 = [ @@ -411,7 +429,7 @@ mod tests { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0xf9); + row.opcode_bits = bits_from_opcode(0xf9); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_u16(15106); row.general.jumps_mut().input0 = [ @@ -433,7 +451,7 @@ mod tests { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0x56); + row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(63064); row.general.jumps_mut().input0 = [ @@ -471,7 +489,7 @@ mod tests { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0x57); + row.opcode_bits = bits_from_opcode(0x57); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(3754); row.general.jumps_mut().input0 = [ @@ -509,7 +527,7 @@ mod tests { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0x57); + row.opcode_bits = bits_from_opcode(0x57); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(37543); row.general.jumps_mut().input0 = [ @@ -538,7 +556,7 @@ mod tests { let last_row: cpu::columns::CpuColumnsView = cpu_trace_rows[cpu_trace_rows.len() - 1].into(); row.is_cpu_cycle = F::ONE; - row.opcode = F::from_canonical_u8(0x56); + row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ZERO; row.program_counter = last_row.program_counter + F::ONE; row.general.jumps_mut().input0 = [ @@ -575,7 +593,7 @@ mod tests { for i in 0..cpu_trace_rows.len().next_power_of_two() - cpu_trace_rows.len() { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); - row.opcode = F::from_canonical_u8(0xff); + row.opcode_bits = bits_from_opcode(0xff); row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; row.program_counter = diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 3016b2fd..8f641db9 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -27,9 +27,6 @@ pub struct CpuColumnsView { /// If CPU cycle: We're in kernel (privileged) mode. pub is_kernel_mode: T, - /// If CPU cycle: The opcode being decoded, in {0, ..., 255}. - pub opcode: T, - // If CPU cycle: flags for EVM instructions. PUSHn, DUPn, and SWAPn only get one flag each. // Invalid opcodes are split between a number of flags for practical reasons. Exactly one of // these flags must be 1. diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index 4faf7925..e58b474d 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -1,6 +1,5 @@ use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; -use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; @@ -158,13 +157,16 @@ pub fn generate(lv: &mut CpuColumnsView) { // This assert is not _strictly_ necessary, but I include it as a sanity check. assert_eq!(cycle_filter, F::ONE, "cycle_filter should be 0 or 1"); - let opcode = lv.opcode.to_canonical_u64(); - assert!(opcode < 256, "opcode should be in {{0, ..., 255}}"); - let opcode = opcode as u8; - - for (i, bit) in lv.opcode_bits.iter_mut().enumerate() { - *bit = F::from_bool(opcode & (1 << i) != 0); + // Validate all opcode bits. + for bit in lv.opcode_bits.into_iter() { + assert!(bit.to_canonical_u64() <= 1); } + let opcode = lv + .opcode_bits + .into_iter() + .enumerate() + .map(|(i, bit)| bit.to_canonical_u64() << i) + .sum::() as u8; let top_bits: [u8; 9] = [ 0, @@ -217,23 +219,10 @@ pub fn eval_packed_generic( let kernel_mode = lv.is_kernel_mode; yield_constr.constraint(cycle_filter * kernel_mode * (kernel_mode - P::ONES)); - // Ensure that the opcode bits are valid: each has to be either 0 or 1, and they must match - // the opcode. Note that this also implicitly range-checks the opcode. - let bits = lv.opcode_bits; - // First check that the bits are either 0 or 1. - for bit in bits { + // Ensure that the opcode bits are valid: each has to be either 0 or 1. + for bit in lv.opcode_bits { yield_constr.constraint(cycle_filter * bit * (bit - P::ONES)); } - // Now check that they match the opcode. - { - let opcode = lv.opcode; - let reconstructed_opcode: P = bits - .into_iter() - .enumerate() - .map(|(i, bit)| bit * P::Scalar::from_canonical_u64(1 << i)) - .sum(); - yield_constr.constraint(cycle_filter * (opcode - reconstructed_opcode)); - } // Check that the instruction flags are valid. // First, check that they are all either 0 or 1. @@ -258,7 +247,8 @@ pub fn eval_packed_generic( Kernel => P::ONES - kernel_mode, }; // 0 if all the opcode bits match, and something in {1, ..., 8}, otherwise. - let opcode_mismatch: P = bits + let opcode_mismatch: P = lv + .opcode_bits .into_iter() .zip(bits_from_opcode(oc)) .rev() @@ -294,28 +284,12 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr.constraint(builder, constr); } - // Ensure that the opcode bits are valid: each has to be either 0 or 1, and they must match - // the opcode. Note that this also implicitly range-checks the opcode. - let bits = lv.opcode_bits; - // First check that the bits are either 0 or 1. - for bit in bits { + // Ensure that the opcode bits are valid: each has to be either 0 or 1. + for bit in lv.opcode_bits { let constr = builder.mul_sub_extension(bit, bit, bit); let constr = builder.mul_extension(cycle_filter, constr); yield_constr.constraint(builder, constr); } - // Now check that they match the opcode. - { - let opcode = lv.opcode; - let reconstructed_opcode = - bits.into_iter() - .enumerate() - .fold(builder.zero_extension(), |cumul, (i, bit)| { - builder.mul_const_add_extension(F::from_canonical_u64(1 << i), bit, cumul) - }); - let diff = builder.sub_extension(opcode, reconstructed_opcode); - let constr = builder.mul_extension(cycle_filter, diff); - yield_constr.constraint(builder, constr); - } // Check that the instruction flags are valid. // First, check that they are all either 0 or 1. @@ -346,7 +320,8 @@ pub fn eval_ext_circuit, const D: usize>( Kernel => builder.sub_extension(one, kernel_mode), }; // 0 if all the opcode bits match, and something in {1, ..., 8}, otherwise. - let opcode_mismatch = bits + let opcode_mismatch = lv + .opcode_bits .into_iter() .zip(bits_from_opcode(oc)) .rev() From d9c210b26c55f1a03e3644e3e1f94e60be8c3eeb Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 12:16:52 -0700 Subject: [PATCH 32/95] Remove compressed proofs in EVM crate Not needed since EVM proofs are wrapped in recursive proofs. --- evm/src/proof.rs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/evm/src/proof.rs b/evm/src/proof.rs index 4f81308d..18b89e7a 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -2,9 +2,7 @@ use itertools::Itertools; use maybe_rayon::*; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::fri::oracle::PolynomialBatch; -use plonky2::fri::proof::{ - CompressedFriProof, FriChallenges, FriChallengesTarget, FriProof, FriProofTarget, -}; +use plonky2::fri::proof::{FriChallenges, FriChallengesTarget, FriProof, FriProofTarget}; use plonky2::fri::structure::{ FriOpeningBatch, FriOpeningBatchTarget, FriOpenings, FriOpeningsTarget, }; @@ -114,28 +112,6 @@ pub struct StarkProofWithPublicInputsTarget { pub public_inputs: Vec, } -pub struct CompressedStarkProof< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, -> { - /// Merkle cap of LDEs of trace values. - pub trace_cap: MerkleCap, - /// Purported values of each polynomial at the challenge point. - pub openings: StarkOpeningSet, - /// A batch FRI argument for all openings. - pub opening_proof: CompressedFriProof, -} - -pub struct CompressedStarkProofWithPublicInputs< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, -> { - pub proof: CompressedStarkProof, - pub public_inputs: Vec, -} - pub(crate) struct StarkProofChallenges, const D: usize> { /// Randomness used in any permutation arguments. pub permutation_challenge_sets: Option>>, From 06e3545b79a42ac5e7b871ebe778e0bc772d8034 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 14:12:47 -0700 Subject: [PATCH 33/95] Move AssertLessThanGate to waksman crate --- plonky2/src/gates/assert_le.rs | 631 --------------------------------- plonky2/src/gates/mod.rs | 1 - waksman/src/gates/mod.rs | 1 + waksman/src/sorting.rs | 2 +- 4 files changed, 2 insertions(+), 633 deletions(-) delete mode 100644 plonky2/src/gates/assert_le.rs diff --git a/plonky2/src/gates/assert_le.rs b/plonky2/src/gates/assert_le.rs deleted file mode 100644 index 19bff044..00000000 --- a/plonky2/src/gates/assert_le.rs +++ /dev/null @@ -1,631 +0,0 @@ -use std::marker::PhantomData; - -use plonky2_field::extension::Extendable; -use plonky2_field::packed::PackedField; -use plonky2_field::types::{Field, Field64}; -use plonky2_util::{bits_u64, ceil_div_usize}; - -use crate::gates::gate::Gate; -use crate::gates::packed_util::PackedEvaluableBase; -use crate::gates::util::StridedConstraintConsumer; -use crate::hash::hash_types::RichField; -use crate::iop::ext_target::ExtensionTarget; -use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; -use crate::iop::target::Target; -use crate::iop::wire::Wire; -use crate::iop::witness::{PartitionWitness, Witness}; -use crate::plonk::circuit_builder::CircuitBuilder; -use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_circuit}; -use crate::plonk::vars::{ - EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, - EvaluationVarsBasePacked, -}; - -// TODO: replace/merge this gate with `ComparisonGate`. - -/// A gate for checking that one value is less than or equal to another. -#[derive(Clone, Debug)] -pub struct AssertLessThanGate, const D: usize> { - pub(crate) num_bits: usize, - pub(crate) num_chunks: usize, - _phantom: PhantomData, -} - -impl, const D: usize> AssertLessThanGate { - pub fn new(num_bits: usize, num_chunks: usize) -> Self { - debug_assert!(num_bits < bits_u64(F::ORDER)); - Self { - num_bits, - num_chunks, - _phantom: PhantomData, - } - } - - pub fn chunk_bits(&self) -> usize { - ceil_div_usize(self.num_bits, self.num_chunks) - } - - pub fn wire_first_input(&self) -> usize { - 0 - } - - pub fn wire_second_input(&self) -> usize { - 1 - } - - pub fn wire_most_significant_diff(&self) -> usize { - 2 - } - - pub fn wire_first_chunk_val(&self, chunk: usize) -> usize { - debug_assert!(chunk < self.num_chunks); - 3 + chunk - } - - pub fn wire_second_chunk_val(&self, chunk: usize) -> usize { - debug_assert!(chunk < self.num_chunks); - 3 + self.num_chunks + chunk - } - - pub fn wire_equality_dummy(&self, chunk: usize) -> usize { - debug_assert!(chunk < self.num_chunks); - 3 + 2 * self.num_chunks + chunk - } - - pub fn wire_chunks_equal(&self, chunk: usize) -> usize { - debug_assert!(chunk < self.num_chunks); - 3 + 3 * self.num_chunks + chunk - } - - pub fn wire_intermediate_value(&self, chunk: usize) -> usize { - debug_assert!(chunk < self.num_chunks); - 3 + 4 * self.num_chunks + chunk - } -} - -impl, const D: usize> Gate for AssertLessThanGate { - fn id(&self) -> String { - format!("{:?}", self, D) - } - - fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { - let mut constraints = Vec::with_capacity(self.num_constraints()); - - let first_input = vars.local_wires[self.wire_first_input()]; - let second_input = vars.local_wires[self.wire_second_input()]; - - // Get chunks and assert that they match - let first_chunks: Vec = (0..self.num_chunks) - .map(|i| vars.local_wires[self.wire_first_chunk_val(i)]) - .collect(); - let second_chunks: Vec = (0..self.num_chunks) - .map(|i| vars.local_wires[self.wire_second_chunk_val(i)]) - .collect(); - - let first_chunks_combined = reduce_with_powers( - &first_chunks, - F::Extension::from_canonical_usize(1 << self.chunk_bits()), - ); - let second_chunks_combined = reduce_with_powers( - &second_chunks, - F::Extension::from_canonical_usize(1 << self.chunk_bits()), - ); - - constraints.push(first_chunks_combined - first_input); - constraints.push(second_chunks_combined - second_input); - - let chunk_size = 1 << self.chunk_bits(); - - let mut most_significant_diff_so_far = F::Extension::ZERO; - - for i in 0..self.num_chunks { - // Range-check the chunks to be less than `chunk_size`. - let first_product = (0..chunk_size) - .map(|x| first_chunks[i] - F::Extension::from_canonical_usize(x)) - .product(); - let second_product = (0..chunk_size) - .map(|x| second_chunks[i] - F::Extension::from_canonical_usize(x)) - .product(); - constraints.push(first_product); - constraints.push(second_product); - - let difference = second_chunks[i] - first_chunks[i]; - let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)]; - let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)]; - - // Two constraints to assert that `chunks_equal` is valid. - constraints.push(difference * equality_dummy - (F::Extension::ONE - chunks_equal)); - constraints.push(chunks_equal * difference); - - // Update `most_significant_diff_so_far`. - let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)]; - constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far); - most_significant_diff_so_far = - intermediate_value + (F::Extension::ONE - chunks_equal) * difference; - } - - let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()]; - constraints.push(most_significant_diff - most_significant_diff_so_far); - - // Range check `most_significant_diff` to be less than `chunk_size`. - let product = (0..chunk_size) - .map(|x| most_significant_diff - F::Extension::from_canonical_usize(x)) - .product(); - constraints.push(product); - - constraints - } - - fn eval_unfiltered_base_one( - &self, - _vars: EvaluationVarsBase, - _yield_constr: StridedConstraintConsumer, - ) { - panic!("use eval_unfiltered_base_packed instead"); - } - - fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch) -> Vec { - self.eval_unfiltered_base_batch_packed(vars_base) - } - - fn eval_unfiltered_circuit( - &self, - builder: &mut CircuitBuilder, - vars: EvaluationTargets, - ) -> Vec> { - let mut constraints = Vec::with_capacity(self.num_constraints()); - - let first_input = vars.local_wires[self.wire_first_input()]; - let second_input = vars.local_wires[self.wire_second_input()]; - - // Get chunks and assert that they match - let first_chunks: Vec> = (0..self.num_chunks) - .map(|i| vars.local_wires[self.wire_first_chunk_val(i)]) - .collect(); - let second_chunks: Vec> = (0..self.num_chunks) - .map(|i| vars.local_wires[self.wire_second_chunk_val(i)]) - .collect(); - - let chunk_base = builder.constant(F::from_canonical_usize(1 << self.chunk_bits())); - let first_chunks_combined = - reduce_with_powers_ext_circuit(builder, &first_chunks, chunk_base); - let second_chunks_combined = - reduce_with_powers_ext_circuit(builder, &second_chunks, chunk_base); - - constraints.push(builder.sub_extension(first_chunks_combined, first_input)); - constraints.push(builder.sub_extension(second_chunks_combined, second_input)); - - let chunk_size = 1 << self.chunk_bits(); - - let mut most_significant_diff_so_far = builder.zero_extension(); - - let one = builder.one_extension(); - // Find the chosen chunk. - for i in 0..self.num_chunks { - // Range-check the chunks to be less than `chunk_size`. - let mut first_product = one; - let mut second_product = one; - for x in 0..chunk_size { - let x_f = builder.constant_extension(F::Extension::from_canonical_usize(x)); - let first_diff = builder.sub_extension(first_chunks[i], x_f); - let second_diff = builder.sub_extension(second_chunks[i], x_f); - first_product = builder.mul_extension(first_product, first_diff); - second_product = builder.mul_extension(second_product, second_diff); - } - constraints.push(first_product); - constraints.push(second_product); - - let difference = builder.sub_extension(second_chunks[i], first_chunks[i]); - let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)]; - let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)]; - - // Two constraints to assert that `chunks_equal` is valid. - let diff_times_equal = builder.mul_extension(difference, equality_dummy); - let not_equal = builder.sub_extension(one, chunks_equal); - constraints.push(builder.sub_extension(diff_times_equal, not_equal)); - constraints.push(builder.mul_extension(chunks_equal, difference)); - - // Update `most_significant_diff_so_far`. - let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)]; - let old_diff = builder.mul_extension(chunks_equal, most_significant_diff_so_far); - constraints.push(builder.sub_extension(intermediate_value, old_diff)); - - let not_equal = builder.sub_extension(one, chunks_equal); - let new_diff = builder.mul_extension(not_equal, difference); - most_significant_diff_so_far = builder.add_extension(intermediate_value, new_diff); - } - - let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()]; - constraints - .push(builder.sub_extension(most_significant_diff, most_significant_diff_so_far)); - - // Range check `most_significant_diff` to be less than `chunk_size`. - let mut product = builder.one_extension(); - for x in 0..chunk_size { - let x_f = builder.constant_extension(F::Extension::from_canonical_usize(x)); - let diff = builder.sub_extension(most_significant_diff, x_f); - product = builder.mul_extension(product, diff); - } - constraints.push(product); - - constraints - } - - fn generators(&self, row: usize, _local_constants: &[F]) -> Vec>> { - let gen = AssertLessThanGenerator:: { - row, - gate: self.clone(), - }; - vec![Box::new(gen.adapter())] - } - - fn num_wires(&self) -> usize { - self.wire_intermediate_value(self.num_chunks - 1) + 1 - } - - fn num_constants(&self) -> usize { - 0 - } - - fn degree(&self) -> usize { - 1 << self.chunk_bits() - } - - fn num_constraints(&self) -> usize { - 4 + 5 * self.num_chunks - } -} - -impl, const D: usize> PackedEvaluableBase - for AssertLessThanGate -{ - fn eval_unfiltered_base_packed>( - &self, - vars: EvaluationVarsBasePacked

, - mut yield_constr: StridedConstraintConsumer

, - ) { - let first_input = vars.local_wires[self.wire_first_input()]; - let second_input = vars.local_wires[self.wire_second_input()]; - - // Get chunks and assert that they match - let first_chunks: Vec<_> = (0..self.num_chunks) - .map(|i| vars.local_wires[self.wire_first_chunk_val(i)]) - .collect(); - let second_chunks: Vec<_> = (0..self.num_chunks) - .map(|i| vars.local_wires[self.wire_second_chunk_val(i)]) - .collect(); - - let first_chunks_combined = reduce_with_powers( - &first_chunks, - F::from_canonical_usize(1 << self.chunk_bits()), - ); - let second_chunks_combined = reduce_with_powers( - &second_chunks, - F::from_canonical_usize(1 << self.chunk_bits()), - ); - - yield_constr.one(first_chunks_combined - first_input); - yield_constr.one(second_chunks_combined - second_input); - - let chunk_size = 1 << self.chunk_bits(); - - let mut most_significant_diff_so_far = P::ZEROS; - - for i in 0..self.num_chunks { - // Range-check the chunks to be less than `chunk_size`. - let first_product = (0..chunk_size) - .map(|x| first_chunks[i] - F::from_canonical_usize(x)) - .product(); - let second_product = (0..chunk_size) - .map(|x| second_chunks[i] - F::from_canonical_usize(x)) - .product(); - yield_constr.one(first_product); - yield_constr.one(second_product); - - let difference = second_chunks[i] - first_chunks[i]; - let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)]; - let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)]; - - // Two constraints to assert that `chunks_equal` is valid. - yield_constr.one(difference * equality_dummy - (P::ONES - chunks_equal)); - yield_constr.one(chunks_equal * difference); - - // Update `most_significant_diff_so_far`. - let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)]; - yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far); - most_significant_diff_so_far = - intermediate_value + (P::ONES - chunks_equal) * difference; - } - - let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()]; - yield_constr.one(most_significant_diff - most_significant_diff_so_far); - - // Range check `most_significant_diff` to be less than `chunk_size`. - let product = (0..chunk_size) - .map(|x| most_significant_diff - F::from_canonical_usize(x)) - .product(); - yield_constr.one(product); - } -} - -#[derive(Debug)] -struct AssertLessThanGenerator, const D: usize> { - row: usize, - gate: AssertLessThanGate, -} - -impl, const D: usize> SimpleGenerator - for AssertLessThanGenerator -{ - fn dependencies(&self) -> Vec { - let local_target = |column| Target::wire(self.row, column); - - vec![ - local_target(self.gate.wire_first_input()), - local_target(self.gate.wire_second_input()), - ] - } - - fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let local_wire = |column| Wire { - row: self.row, - column, - }; - - let get_local_wire = |column| witness.get_wire(local_wire(column)); - - let first_input = get_local_wire(self.gate.wire_first_input()); - let second_input = get_local_wire(self.gate.wire_second_input()); - - let first_input_u64 = first_input.to_canonical_u64(); - let second_input_u64 = second_input.to_canonical_u64(); - - debug_assert!(first_input_u64 < second_input_u64); - - let chunk_size = 1 << self.gate.chunk_bits(); - let first_input_chunks: Vec = (0..self.gate.num_chunks) - .scan(first_input_u64, |acc, _| { - let tmp = *acc % chunk_size; - *acc /= chunk_size; - Some(F::from_canonical_u64(tmp)) - }) - .collect(); - let second_input_chunks: Vec = (0..self.gate.num_chunks) - .scan(second_input_u64, |acc, _| { - let tmp = *acc % chunk_size; - *acc /= chunk_size; - Some(F::from_canonical_u64(tmp)) - }) - .collect(); - - let chunks_equal: Vec = (0..self.gate.num_chunks) - .map(|i| F::from_bool(first_input_chunks[i] == second_input_chunks[i])) - .collect(); - let equality_dummies: Vec = first_input_chunks - .iter() - .zip(second_input_chunks.iter()) - .map(|(&f, &s)| if f == s { F::ONE } else { F::ONE / (s - f) }) - .collect(); - - let mut most_significant_diff_so_far = F::ZERO; - let mut intermediate_values = Vec::new(); - for i in 0..self.gate.num_chunks { - if first_input_chunks[i] != second_input_chunks[i] { - most_significant_diff_so_far = second_input_chunks[i] - first_input_chunks[i]; - intermediate_values.push(F::ZERO); - } else { - intermediate_values.push(most_significant_diff_so_far); - } - } - let most_significant_diff = most_significant_diff_so_far; - - out_buffer.set_wire( - local_wire(self.gate.wire_most_significant_diff()), - most_significant_diff, - ); - for i in 0..self.gate.num_chunks { - out_buffer.set_wire( - local_wire(self.gate.wire_first_chunk_val(i)), - first_input_chunks[i], - ); - out_buffer.set_wire( - local_wire(self.gate.wire_second_chunk_val(i)), - second_input_chunks[i], - ); - out_buffer.set_wire( - local_wire(self.gate.wire_equality_dummy(i)), - equality_dummies[i], - ); - out_buffer.set_wire(local_wire(self.gate.wire_chunks_equal(i)), chunks_equal[i]); - out_buffer.set_wire( - local_wire(self.gate.wire_intermediate_value(i)), - intermediate_values[i], - ); - } - } -} - -#[cfg(test)] -mod tests { - use std::marker::PhantomData; - - use anyhow::Result; - use plonky2_field::extension::quartic::QuarticExtension; - use plonky2_field::goldilocks_field::GoldilocksField; - use plonky2_field::types::Field; - use plonky2_field::types::PrimeField64; - use rand::Rng; - - use crate::gates::assert_le::AssertLessThanGate; - use crate::gates::gate::Gate; - use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; - use crate::hash::hash_types::HashOut; - use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::vars::EvaluationVars; - - #[test] - fn wire_indices() { - type AG = AssertLessThanGate; - let num_bits = 40; - let num_chunks = 5; - - let gate = AG { - num_bits, - num_chunks, - _phantom: PhantomData, - }; - - assert_eq!(gate.wire_first_input(), 0); - assert_eq!(gate.wire_second_input(), 1); - assert_eq!(gate.wire_most_significant_diff(), 2); - assert_eq!(gate.wire_first_chunk_val(0), 3); - assert_eq!(gate.wire_first_chunk_val(4), 7); - assert_eq!(gate.wire_second_chunk_val(0), 8); - assert_eq!(gate.wire_second_chunk_val(4), 12); - assert_eq!(gate.wire_equality_dummy(0), 13); - assert_eq!(gate.wire_equality_dummy(4), 17); - assert_eq!(gate.wire_chunks_equal(0), 18); - assert_eq!(gate.wire_chunks_equal(4), 22); - assert_eq!(gate.wire_intermediate_value(0), 23); - assert_eq!(gate.wire_intermediate_value(4), 27); - } - - #[test] - fn low_degree() { - let num_bits = 20; - let num_chunks = 4; - - test_low_degree::(AssertLessThanGate::<_, 4>::new( - num_bits, num_chunks, - )) - } - - #[test] - fn eval_fns() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - - let num_bits = 20; - let num_chunks = 4; - - test_eval_fns::(AssertLessThanGate::<_, D>::new(num_bits, num_chunks)) - } - - #[test] - fn test_gate_constraint() { - type F = GoldilocksField; - type FF = QuarticExtension; - const D: usize = 4; - - let num_bits = 40; - let num_chunks = 5; - let chunk_bits = num_bits / num_chunks; - - // Returns the local wires for an AssertLessThanGate given the two inputs. - let get_wires = |first_input: F, second_input: F| -> Vec { - let mut v = Vec::new(); - - let first_input_u64 = first_input.to_canonical_u64(); - let second_input_u64 = second_input.to_canonical_u64(); - - let chunk_size = 1 << chunk_bits; - let mut first_input_chunks: Vec = (0..num_chunks) - .scan(first_input_u64, |acc, _| { - let tmp = *acc % chunk_size; - *acc /= chunk_size; - Some(F::from_canonical_u64(tmp)) - }) - .collect(); - let mut second_input_chunks: Vec = (0..num_chunks) - .scan(second_input_u64, |acc, _| { - let tmp = *acc % chunk_size; - *acc /= chunk_size; - Some(F::from_canonical_u64(tmp)) - }) - .collect(); - - let mut chunks_equal: Vec = (0..num_chunks) - .map(|i| F::from_bool(first_input_chunks[i] == second_input_chunks[i])) - .collect(); - let mut equality_dummies: Vec = first_input_chunks - .iter() - .zip(second_input_chunks.iter()) - .map(|(&f, &s)| if f == s { F::ONE } else { F::ONE / (s - f) }) - .collect(); - - let mut most_significant_diff_so_far = F::ZERO; - let mut intermediate_values = Vec::new(); - for i in 0..num_chunks { - if first_input_chunks[i] != second_input_chunks[i] { - most_significant_diff_so_far = second_input_chunks[i] - first_input_chunks[i]; - intermediate_values.push(F::ZERO); - } else { - intermediate_values.push(most_significant_diff_so_far); - } - } - let most_significant_diff = most_significant_diff_so_far; - - v.push(first_input); - v.push(second_input); - v.push(most_significant_diff); - v.append(&mut first_input_chunks); - v.append(&mut second_input_chunks); - v.append(&mut equality_dummies); - v.append(&mut chunks_equal); - v.append(&mut intermediate_values); - - v.iter().map(|&x| x.into()).collect() - }; - - let mut rng = rand::thread_rng(); - let max: u64 = 1 << (num_bits - 1); - let first_input_u64 = rng.gen_range(0..max); - let second_input_u64 = { - let mut val = rng.gen_range(0..max); - while val < first_input_u64 { - val = rng.gen_range(0..max); - } - val - }; - - let first_input = F::from_canonical_u64(first_input_u64); - let second_input = F::from_canonical_u64(second_input_u64); - - let less_than_gate = AssertLessThanGate:: { - num_bits, - num_chunks, - _phantom: PhantomData, - }; - let less_than_vars = EvaluationVars { - local_constants: &[], - local_wires: &get_wires(first_input, second_input), - public_inputs_hash: &HashOut::rand(), - }; - assert!( - less_than_gate - .eval_unfiltered(less_than_vars) - .iter() - .all(|x| x.is_zero()), - "Gate constraints are not satisfied." - ); - - let equal_gate = AssertLessThanGate:: { - num_bits, - num_chunks, - _phantom: PhantomData, - }; - let equal_vars = EvaluationVars { - local_constants: &[], - local_wires: &get_wires(first_input, first_input), - public_inputs_hash: &HashOut::rand(), - }; - assert!( - equal_gate - .eval_unfiltered(equal_vars) - .iter() - .all(|x| x.is_zero()), - "Gate constraints are not satisfied." - ); - } -} diff --git a/plonky2/src/gates/mod.rs b/plonky2/src/gates/mod.rs index df65b44c..48e319ef 100644 --- a/plonky2/src/gates/mod.rs +++ b/plonky2/src/gates/mod.rs @@ -3,7 +3,6 @@ pub mod arithmetic_base; pub mod arithmetic_extension; -pub mod assert_le; pub mod base_sum; pub mod constant; pub mod exponentiation; diff --git a/waksman/src/gates/mod.rs b/waksman/src/gates/mod.rs index 5a2a8f48..c73890b1 100644 --- a/waksman/src/gates/mod.rs +++ b/waksman/src/gates/mod.rs @@ -1 +1,2 @@ +pub mod assert_le; pub mod switch; diff --git a/waksman/src/sorting.rs b/waksman/src/sorting.rs index ac598dc8..010bc8b9 100644 --- a/waksman/src/sorting.rs +++ b/waksman/src/sorting.rs @@ -3,7 +3,6 @@ use std::marker::PhantomData; use itertools::izip; use plonky2::field::extension::Extendable; use plonky2::field::types::Field; -use plonky2::gates::assert_le::AssertLessThanGate; use plonky2::hash::hash_types::RichField; use plonky2::iop::generator::{GeneratedValues, SimpleGenerator}; use plonky2::iop::target::{BoolTarget, Target}; @@ -11,6 +10,7 @@ use plonky2::iop::witness::{PartitionWitness, Witness}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2_util::ceil_div_usize; +use crate::gates::assert_le::AssertLessThanGate; use crate::permutation::assert_permutation_circuit; pub struct MemoryOp { From 56591711a80087186d23e5c0b3e468b77fd7d21a Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 14:14:34 -0700 Subject: [PATCH 34/95] added gate oops --- waksman/src/gates/assert_le.rs | 631 +++++++++++++++++++++++++++++++++ 1 file changed, 631 insertions(+) create mode 100644 waksman/src/gates/assert_le.rs diff --git a/waksman/src/gates/assert_le.rs b/waksman/src/gates/assert_le.rs new file mode 100644 index 00000000..ce045958 --- /dev/null +++ b/waksman/src/gates/assert_le.rs @@ -0,0 +1,631 @@ +use std::marker::PhantomData; + +use plonky2_field::extension::Extendable; +use plonky2_field::packed::PackedField; +use plonky2_field::types::{Field, Field64}; +use plonky2_util::{bits_u64, ceil_div_usize}; + +use plonky2::gates::gate::Gate; +use plonky2::gates::packed_util::PackedEvaluableBase; +use plonky2::gates::util::StridedConstraintConsumer; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use plonky2::iop::target::Target; +use plonky2::iop::wire::Wire; +use plonky2::iop::witness::{PartitionWitness, Witness}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_circuit}; +use plonky2::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; + +// TODO: replace/merge this gate with `ComparisonGate`. + +/// A gate for checking that one value is less than or equal to another. +#[derive(Clone, Debug)] +pub struct AssertLessThanGate, const D: usize> { + pub(crate) num_bits: usize, + pub(crate) num_chunks: usize, + _phantom: PhantomData, +} + +impl, const D: usize> AssertLessThanGate { + pub fn new(num_bits: usize, num_chunks: usize) -> Self { + debug_assert!(num_bits < bits_u64(F::ORDER)); + Self { + num_bits, + num_chunks, + _phantom: PhantomData, + } + } + + pub fn chunk_bits(&self) -> usize { + ceil_div_usize(self.num_bits, self.num_chunks) + } + + pub fn wire_first_input(&self) -> usize { + 0 + } + + pub fn wire_second_input(&self) -> usize { + 1 + } + + pub fn wire_most_significant_diff(&self) -> usize { + 2 + } + + pub fn wire_first_chunk_val(&self, chunk: usize) -> usize { + debug_assert!(chunk < self.num_chunks); + 3 + chunk + } + + pub fn wire_second_chunk_val(&self, chunk: usize) -> usize { + debug_assert!(chunk < self.num_chunks); + 3 + self.num_chunks + chunk + } + + pub fn wire_equality_dummy(&self, chunk: usize) -> usize { + debug_assert!(chunk < self.num_chunks); + 3 + 2 * self.num_chunks + chunk + } + + pub fn wire_chunks_equal(&self, chunk: usize) -> usize { + debug_assert!(chunk < self.num_chunks); + 3 + 3 * self.num_chunks + chunk + } + + pub fn wire_intermediate_value(&self, chunk: usize) -> usize { + debug_assert!(chunk < self.num_chunks); + 3 + 4 * self.num_chunks + chunk + } +} + +impl, const D: usize> Gate for AssertLessThanGate { + fn id(&self) -> String { + format!("{:?}", self, D) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let first_input = vars.local_wires[self.wire_first_input()]; + let second_input = vars.local_wires[self.wire_second_input()]; + + // Get chunks and assert that they match + let first_chunks: Vec = (0..self.num_chunks) + .map(|i| vars.local_wires[self.wire_first_chunk_val(i)]) + .collect(); + let second_chunks: Vec = (0..self.num_chunks) + .map(|i| vars.local_wires[self.wire_second_chunk_val(i)]) + .collect(); + + let first_chunks_combined = reduce_with_powers( + &first_chunks, + F::Extension::from_canonical_usize(1 << self.chunk_bits()), + ); + let second_chunks_combined = reduce_with_powers( + &second_chunks, + F::Extension::from_canonical_usize(1 << self.chunk_bits()), + ); + + constraints.push(first_chunks_combined - first_input); + constraints.push(second_chunks_combined - second_input); + + let chunk_size = 1 << self.chunk_bits(); + + let mut most_significant_diff_so_far = F::Extension::ZERO; + + for i in 0..self.num_chunks { + // Range-check the chunks to be less than `chunk_size`. + let first_product = (0..chunk_size) + .map(|x| first_chunks[i] - F::Extension::from_canonical_usize(x)) + .product(); + let second_product = (0..chunk_size) + .map(|x| second_chunks[i] - F::Extension::from_canonical_usize(x)) + .product(); + constraints.push(first_product); + constraints.push(second_product); + + let difference = second_chunks[i] - first_chunks[i]; + let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)]; + let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)]; + + // Two constraints to assert that `chunks_equal` is valid. + constraints.push(difference * equality_dummy - (F::Extension::ONE - chunks_equal)); + constraints.push(chunks_equal * difference); + + // Update `most_significant_diff_so_far`. + let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)]; + constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far); + most_significant_diff_so_far = + intermediate_value + (F::Extension::ONE - chunks_equal) * difference; + } + + let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()]; + constraints.push(most_significant_diff - most_significant_diff_so_far); + + // Range check `most_significant_diff` to be less than `chunk_size`. + let product = (0..chunk_size) + .map(|x| most_significant_diff - F::Extension::from_canonical_usize(x)) + .product(); + constraints.push(product); + + constraints + } + + fn eval_unfiltered_base_one( + &self, + _vars: EvaluationVarsBase, + _yield_constr: StridedConstraintConsumer, + ) { + panic!("use eval_unfiltered_base_packed instead"); + } + + fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch) -> Vec { + self.eval_unfiltered_base_batch_packed(vars_base) + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let first_input = vars.local_wires[self.wire_first_input()]; + let second_input = vars.local_wires[self.wire_second_input()]; + + // Get chunks and assert that they match + let first_chunks: Vec> = (0..self.num_chunks) + .map(|i| vars.local_wires[self.wire_first_chunk_val(i)]) + .collect(); + let second_chunks: Vec> = (0..self.num_chunks) + .map(|i| vars.local_wires[self.wire_second_chunk_val(i)]) + .collect(); + + let chunk_base = builder.constant(F::from_canonical_usize(1 << self.chunk_bits())); + let first_chunks_combined = + reduce_with_powers_ext_circuit(builder, &first_chunks, chunk_base); + let second_chunks_combined = + reduce_with_powers_ext_circuit(builder, &second_chunks, chunk_base); + + constraints.push(builder.sub_extension(first_chunks_combined, first_input)); + constraints.push(builder.sub_extension(second_chunks_combined, second_input)); + + let chunk_size = 1 << self.chunk_bits(); + + let mut most_significant_diff_so_far = builder.zero_extension(); + + let one = builder.one_extension(); + // Find the chosen chunk. + for i in 0..self.num_chunks { + // Range-check the chunks to be less than `chunk_size`. + let mut first_product = one; + let mut second_product = one; + for x in 0..chunk_size { + let x_f = builder.constant_extension(F::Extension::from_canonical_usize(x)); + let first_diff = builder.sub_extension(first_chunks[i], x_f); + let second_diff = builder.sub_extension(second_chunks[i], x_f); + first_product = builder.mul_extension(first_product, first_diff); + second_product = builder.mul_extension(second_product, second_diff); + } + constraints.push(first_product); + constraints.push(second_product); + + let difference = builder.sub_extension(second_chunks[i], first_chunks[i]); + let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)]; + let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)]; + + // Two constraints to assert that `chunks_equal` is valid. + let diff_times_equal = builder.mul_extension(difference, equality_dummy); + let not_equal = builder.sub_extension(one, chunks_equal); + constraints.push(builder.sub_extension(diff_times_equal, not_equal)); + constraints.push(builder.mul_extension(chunks_equal, difference)); + + // Update `most_significant_diff_so_far`. + let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)]; + let old_diff = builder.mul_extension(chunks_equal, most_significant_diff_so_far); + constraints.push(builder.sub_extension(intermediate_value, old_diff)); + + let not_equal = builder.sub_extension(one, chunks_equal); + let new_diff = builder.mul_extension(not_equal, difference); + most_significant_diff_so_far = builder.add_extension(intermediate_value, new_diff); + } + + let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()]; + constraints + .push(builder.sub_extension(most_significant_diff, most_significant_diff_so_far)); + + // Range check `most_significant_diff` to be less than `chunk_size`. + let mut product = builder.one_extension(); + for x in 0..chunk_size { + let x_f = builder.constant_extension(F::Extension::from_canonical_usize(x)); + let diff = builder.sub_extension(most_significant_diff, x_f); + product = builder.mul_extension(product, diff); + } + constraints.push(product); + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec>> { + let gen = AssertLessThanGenerator:: { + row, + gate: self.clone(), + }; + vec![Box::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.wire_intermediate_value(self.num_chunks - 1) + 1 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 1 << self.chunk_bits() + } + + fn num_constraints(&self) -> usize { + 4 + 5 * self.num_chunks + } +} + +impl, const D: usize> PackedEvaluableBase + for AssertLessThanGate +{ + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + let first_input = vars.local_wires[self.wire_first_input()]; + let second_input = vars.local_wires[self.wire_second_input()]; + + // Get chunks and assert that they match + let first_chunks: Vec<_> = (0..self.num_chunks) + .map(|i| vars.local_wires[self.wire_first_chunk_val(i)]) + .collect(); + let second_chunks: Vec<_> = (0..self.num_chunks) + .map(|i| vars.local_wires[self.wire_second_chunk_val(i)]) + .collect(); + + let first_chunks_combined = reduce_with_powers( + &first_chunks, + F::from_canonical_usize(1 << self.chunk_bits()), + ); + let second_chunks_combined = reduce_with_powers( + &second_chunks, + F::from_canonical_usize(1 << self.chunk_bits()), + ); + + yield_constr.one(first_chunks_combined - first_input); + yield_constr.one(second_chunks_combined - second_input); + + let chunk_size = 1 << self.chunk_bits(); + + let mut most_significant_diff_so_far = P::ZEROS; + + for i in 0..self.num_chunks { + // Range-check the chunks to be less than `chunk_size`. + let first_product = (0..chunk_size) + .map(|x| first_chunks[i] - F::from_canonical_usize(x)) + .product(); + let second_product = (0..chunk_size) + .map(|x| second_chunks[i] - F::from_canonical_usize(x)) + .product(); + yield_constr.one(first_product); + yield_constr.one(second_product); + + let difference = second_chunks[i] - first_chunks[i]; + let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)]; + let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)]; + + // Two constraints to assert that `chunks_equal` is valid. + yield_constr.one(difference * equality_dummy - (P::ONES - chunks_equal)); + yield_constr.one(chunks_equal * difference); + + // Update `most_significant_diff_so_far`. + let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)]; + yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far); + most_significant_diff_so_far = + intermediate_value + (P::ONES - chunks_equal) * difference; + } + + let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()]; + yield_constr.one(most_significant_diff - most_significant_diff_so_far); + + // Range check `most_significant_diff` to be less than `chunk_size`. + let product = (0..chunk_size) + .map(|x| most_significant_diff - F::from_canonical_usize(x)) + .product(); + yield_constr.one(product); + } +} + +#[derive(Debug)] +struct AssertLessThanGenerator, const D: usize> { + row: usize, + gate: AssertLessThanGate, +} + +impl, const D: usize> SimpleGenerator + for AssertLessThanGenerator +{ + fn dependencies(&self) -> Vec { + let local_target = |column| Target::wire(self.row, column); + + vec![ + local_target(self.gate.wire_first_input()), + local_target(self.gate.wire_second_input()), + ] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_wire = |column| Wire { + row: self.row, + column, + }; + + let get_local_wire = |column| witness.get_wire(local_wire(column)); + + let first_input = get_local_wire(self.gate.wire_first_input()); + let second_input = get_local_wire(self.gate.wire_second_input()); + + let first_input_u64 = first_input.to_canonical_u64(); + let second_input_u64 = second_input.to_canonical_u64(); + + debug_assert!(first_input_u64 < second_input_u64); + + let chunk_size = 1 << self.gate.chunk_bits(); + let first_input_chunks: Vec = (0..self.gate.num_chunks) + .scan(first_input_u64, |acc, _| { + let tmp = *acc % chunk_size; + *acc /= chunk_size; + Some(F::from_canonical_u64(tmp)) + }) + .collect(); + let second_input_chunks: Vec = (0..self.gate.num_chunks) + .scan(second_input_u64, |acc, _| { + let tmp = *acc % chunk_size; + *acc /= chunk_size; + Some(F::from_canonical_u64(tmp)) + }) + .collect(); + + let chunks_equal: Vec = (0..self.gate.num_chunks) + .map(|i| F::from_bool(first_input_chunks[i] == second_input_chunks[i])) + .collect(); + let equality_dummies: Vec = first_input_chunks + .iter() + .zip(second_input_chunks.iter()) + .map(|(&f, &s)| if f == s { F::ONE } else { F::ONE / (s - f) }) + .collect(); + + let mut most_significant_diff_so_far = F::ZERO; + let mut intermediate_values = Vec::new(); + for i in 0..self.gate.num_chunks { + if first_input_chunks[i] != second_input_chunks[i] { + most_significant_diff_so_far = second_input_chunks[i] - first_input_chunks[i]; + intermediate_values.push(F::ZERO); + } else { + intermediate_values.push(most_significant_diff_so_far); + } + } + let most_significant_diff = most_significant_diff_so_far; + + out_buffer.set_wire( + local_wire(self.gate.wire_most_significant_diff()), + most_significant_diff, + ); + for i in 0..self.gate.num_chunks { + out_buffer.set_wire( + local_wire(self.gate.wire_first_chunk_val(i)), + first_input_chunks[i], + ); + out_buffer.set_wire( + local_wire(self.gate.wire_second_chunk_val(i)), + second_input_chunks[i], + ); + out_buffer.set_wire( + local_wire(self.gate.wire_equality_dummy(i)), + equality_dummies[i], + ); + out_buffer.set_wire(local_wire(self.gate.wire_chunks_equal(i)), chunks_equal[i]); + out_buffer.set_wire( + local_wire(self.gate.wire_intermediate_value(i)), + intermediate_values[i], + ); + } + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use anyhow::Result; + use plonky2_field::extension::quartic::QuarticExtension; + use plonky2_field::goldilocks_field::GoldilocksField; + use plonky2_field::types::Field; + use plonky2_field::types::PrimeField64; + use rand::Rng; + + use crate::gates::assert_le::AssertLessThanGate; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::hash::hash_types::HashOut; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::vars::EvaluationVars; + + #[test] + fn wire_indices() { + type AG = AssertLessThanGate; + let num_bits = 40; + let num_chunks = 5; + + let gate = AG { + num_bits, + num_chunks, + _phantom: PhantomData, + }; + + assert_eq!(gate.wire_first_input(), 0); + assert_eq!(gate.wire_second_input(), 1); + assert_eq!(gate.wire_most_significant_diff(), 2); + assert_eq!(gate.wire_first_chunk_val(0), 3); + assert_eq!(gate.wire_first_chunk_val(4), 7); + assert_eq!(gate.wire_second_chunk_val(0), 8); + assert_eq!(gate.wire_second_chunk_val(4), 12); + assert_eq!(gate.wire_equality_dummy(0), 13); + assert_eq!(gate.wire_equality_dummy(4), 17); + assert_eq!(gate.wire_chunks_equal(0), 18); + assert_eq!(gate.wire_chunks_equal(4), 22); + assert_eq!(gate.wire_intermediate_value(0), 23); + assert_eq!(gate.wire_intermediate_value(4), 27); + } + + #[test] + fn low_degree() { + let num_bits = 20; + let num_chunks = 4; + + test_low_degree::(AssertLessThanGate::<_, 4>::new( + num_bits, num_chunks, + )) + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let num_bits = 20; + let num_chunks = 4; + + test_eval_fns::(AssertLessThanGate::<_, D>::new(num_bits, num_chunks)) + } + + #[test] + fn test_gate_constraint() { + type F = GoldilocksField; + type FF = QuarticExtension; + const D: usize = 4; + + let num_bits = 40; + let num_chunks = 5; + let chunk_bits = num_bits / num_chunks; + + // Returns the local wires for an AssertLessThanGate given the two inputs. + let get_wires = |first_input: F, second_input: F| -> Vec { + let mut v = Vec::new(); + + let first_input_u64 = first_input.to_canonical_u64(); + let second_input_u64 = second_input.to_canonical_u64(); + + let chunk_size = 1 << chunk_bits; + let mut first_input_chunks: Vec = (0..num_chunks) + .scan(first_input_u64, |acc, _| { + let tmp = *acc % chunk_size; + *acc /= chunk_size; + Some(F::from_canonical_u64(tmp)) + }) + .collect(); + let mut second_input_chunks: Vec = (0..num_chunks) + .scan(second_input_u64, |acc, _| { + let tmp = *acc % chunk_size; + *acc /= chunk_size; + Some(F::from_canonical_u64(tmp)) + }) + .collect(); + + let mut chunks_equal: Vec = (0..num_chunks) + .map(|i| F::from_bool(first_input_chunks[i] == second_input_chunks[i])) + .collect(); + let mut equality_dummies: Vec = first_input_chunks + .iter() + .zip(second_input_chunks.iter()) + .map(|(&f, &s)| if f == s { F::ONE } else { F::ONE / (s - f) }) + .collect(); + + let mut most_significant_diff_so_far = F::ZERO; + let mut intermediate_values = Vec::new(); + for i in 0..num_chunks { + if first_input_chunks[i] != second_input_chunks[i] { + most_significant_diff_so_far = second_input_chunks[i] - first_input_chunks[i]; + intermediate_values.push(F::ZERO); + } else { + intermediate_values.push(most_significant_diff_so_far); + } + } + let most_significant_diff = most_significant_diff_so_far; + + v.push(first_input); + v.push(second_input); + v.push(most_significant_diff); + v.append(&mut first_input_chunks); + v.append(&mut second_input_chunks); + v.append(&mut equality_dummies); + v.append(&mut chunks_equal); + v.append(&mut intermediate_values); + + v.iter().map(|&x| x.into()).collect() + }; + + let mut rng = rand::thread_rng(); + let max: u64 = 1 << (num_bits - 1); + let first_input_u64 = rng.gen_range(0..max); + let second_input_u64 = { + let mut val = rng.gen_range(0..max); + while val < first_input_u64 { + val = rng.gen_range(0..max); + } + val + }; + + let first_input = F::from_canonical_u64(first_input_u64); + let second_input = F::from_canonical_u64(second_input_u64); + + let less_than_gate = AssertLessThanGate:: { + num_bits, + num_chunks, + _phantom: PhantomData, + }; + let less_than_vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(first_input, second_input), + public_inputs_hash: &HashOut::rand(), + }; + assert!( + less_than_gate + .eval_unfiltered(less_than_vars) + .iter() + .all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + + let equal_gate = AssertLessThanGate:: { + num_bits, + num_chunks, + _phantom: PhantomData, + }; + let equal_vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(first_input, first_input), + public_inputs_hash: &HashOut::rand(), + }; + assert!( + equal_gate + .eval_unfiltered(equal_vars) + .iter() + .all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + } +} From a41794b5b5fc2f9b40413a06fff6fab81fe85708 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 14:17:03 -0700 Subject: [PATCH 35/95] fixes --- waksman/src/gates/assert_le.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/waksman/src/gates/assert_le.rs b/waksman/src/gates/assert_le.rs index ce045958..c67a7125 100644 --- a/waksman/src/gates/assert_le.rs +++ b/waksman/src/gates/assert_le.rs @@ -1,10 +1,5 @@ use std::marker::PhantomData; -use plonky2_field::extension::Extendable; -use plonky2_field::packed::PackedField; -use plonky2_field::types::{Field, Field64}; -use plonky2_util::{bits_u64, ceil_div_usize}; - use plonky2::gates::gate::Gate; use plonky2::gates::packed_util::PackedEvaluableBase; use plonky2::gates::util::StridedConstraintConsumer; @@ -20,6 +15,10 @@ use plonky2::plonk::vars::{ EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, EvaluationVarsBasePacked, }; +use plonky2_field::extension::Extendable; +use plonky2_field::packed::PackedField; +use plonky2_field::types::{Field, Field64}; +use plonky2_util::{bits_u64, ceil_div_usize}; // TODO: replace/merge this gate with `ComparisonGate`. @@ -450,6 +449,11 @@ mod tests { use std::marker::PhantomData; use anyhow::Result; + use plonky2::gates::gate::Gate; + use plonky2::gates::gate_testing::{test_eval_fns, test_low_degree}; + use plonky2::hash::hash_types::HashOut; + use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use plonky2::plonk::vars::EvaluationVars; use plonky2_field::extension::quartic::QuarticExtension; use plonky2_field::goldilocks_field::GoldilocksField; use plonky2_field::types::Field; @@ -457,11 +461,6 @@ mod tests { use rand::Rng; use crate::gates::assert_le::AssertLessThanGate; - use crate::gates::gate::Gate; - use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; - use crate::hash::hash_types::HashOut; - use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::vars::EvaluationVars; #[test] fn wire_indices() { From 30cc318cde2111b13299d026b148360140b71005 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 14:45:56 -0700 Subject: [PATCH 36/95] Feedback --- evm/src/all_stark.rs | 4 ++-- evm/src/keccak_memory/keccak_memory_stark.rs | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 24499a0b..e5953a34 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -147,14 +147,14 @@ fn ctl_memory() -> CrossTableLookup { let keccak_memory_reads = (0..KECCAK_WIDTH_BYTES).map(|i| { TableWithColumns::new( Table::KeccakMemory, - keccak_memory_stark::ctl_looking_memory_read(i), + keccak_memory_stark::ctl_looking_memory(i, true), Some(keccak_memory_stark::ctl_filter()), ) }); let keccak_memory_writes = (0..KECCAK_WIDTH_BYTES).map(|i| { TableWithColumns::new( Table::KeccakMemory, - keccak_memory_stark::ctl_looking_memory_write(i), + keccak_memory_stark::ctl_looking_memory(i, false), Some(keccak_memory_stark::ctl_filter()), ) }); diff --git a/evm/src/keccak_memory/keccak_memory_stark.rs b/evm/src/keccak_memory/keccak_memory_stark.rs index d12850e1..5b0f0fd1 100644 --- a/evm/src/keccak_memory/keccak_memory_stark.rs +++ b/evm/src/keccak_memory/keccak_memory_stark.rs @@ -40,15 +40,7 @@ pub(crate) fn ctl_looking_keccak() -> Vec> { input_cols.chain(output_cols).collect() } -pub(crate) fn ctl_looking_memory_read(i: usize) -> Vec> { - ctl_looking_memory(i, true) -} - -pub(crate) fn ctl_looking_memory_write(i: usize) -> Vec> { - ctl_looking_memory(i, false) -} - -fn ctl_looking_memory(i: usize, is_read: bool) -> Vec> { +pub(crate) fn ctl_looking_memory(i: usize, is_read: bool) -> Vec> { let mut res = vec![Column::constant(F::from_bool(is_read))]; res.extend(Column::singles([COL_CONTEXT, COL_SEGMENT, COL_VIRTUAL])); From 50c9638b552ded673468652b36bd3d224ce0a00c Mon Sep 17 00:00:00 2001 From: Hamish Ivey-Law <426294+unzvfu@users.noreply.github.com> Date: Fri, 26 Aug 2022 09:13:47 +1000 Subject: [PATCH 37/95] EVM arithmetic unit: unsigned comparisons (#688) * Refactor u256 calculation; return cy/br from calculations. * Implement less than and greater than operations. * Add file documentation. --- evm/src/arithmetic/add.rs | 35 ++-- evm/src/arithmetic/arithmetic_stark.rs | 7 + evm/src/arithmetic/columns.rs | 5 + evm/src/arithmetic/compare.rs | 219 +++++++++++++++++++++++++ evm/src/arithmetic/mod.rs | 1 + evm/src/arithmetic/sub.rs | 23 +-- evm/src/arithmetic/utils.rs | 3 + 7 files changed, 270 insertions(+), 23 deletions(-) create mode 100644 evm/src/arithmetic/compare.rs diff --git a/evm/src/arithmetic/add.rs b/evm/src/arithmetic/add.rs index 80f03d63..e87566b6 100644 --- a/evm/src/arithmetic/add.rs +++ b/evm/src/arithmetic/add.rs @@ -9,6 +9,21 @@ use crate::arithmetic::columns::*; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; +pub(crate) fn u256_add_cc(input0: [u64; N_LIMBS], input1: [u64; N_LIMBS]) -> ([u64; N_LIMBS], u64) { + // Input and output have 16-bit limbs + let mut output = [0u64; N_LIMBS]; + + const MASK: u64 = (1u64 << LIMB_BITS) - 1u64; + let mut cy = 0u64; + for (i, a, b) in izip!(0.., input0, input1) { + let s = a + b + cy; + cy = s >> LIMB_BITS; + assert!(cy <= 1u64, "input limbs were larger than 16 bits"); + output[i] = s & MASK; + } + (output, cy) +} + /// Given two sequences `larger` and `smaller` of equal length (not /// checked), verifies that \sum_i larger[i] 2^(LIMB_BITS * i) == /// \sum_i smaller[i] 2^(LIMB_BITS * i), taking care of carry propagation. @@ -19,7 +34,8 @@ pub(crate) fn eval_packed_generic_are_equal( is_op: P, larger: I, smaller: J, -) where +) -> P +where P: PackedField, I: Iterator, J: Iterator, @@ -36,6 +52,7 @@ pub(crate) fn eval_packed_generic_are_equal( // increase the degree of the constraint. cy = t * overflow_inv; } + cy } pub(crate) fn eval_ext_circuit_are_equal( @@ -44,7 +61,8 @@ pub(crate) fn eval_ext_circuit_are_equal( is_op: ExtensionTarget, larger: I, smaller: J, -) where +) -> ExtensionTarget +where F: RichField + Extendable, I: Iterator>, J: Iterator>, @@ -72,6 +90,7 @@ pub(crate) fn eval_ext_circuit_are_equal( cy = builder.mul_const_extension(overflow_inv, t); } + cy } pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { @@ -79,17 +98,7 @@ pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { let input1_limbs = ADD_INPUT_1.map(|c| lv[c].to_canonical_u64()); // Input and output have 16-bit limbs - let mut output_limbs = [0u64; N_LIMBS]; - - const MASK: u64 = (1u64 << LIMB_BITS) - 1u64; - let mut cy = 0u64; - for (i, a, b) in izip!(0.., input0_limbs, input1_limbs) { - let s = a + b + cy; - cy = s >> LIMB_BITS; - assert!(cy <= 1u64, "input limbs were larger than 16 bits"); - output_limbs[i] = s & MASK; - } - // last carry is dropped because this is addition modulo 2^256. + let (output_limbs, _) = u256_add_cc(input0_limbs, input1_limbs); for (&c, output_limb) in ADD_OUTPUT.iter().zip(output_limbs) { lv[c] = F::from_canonical_u64(output_limb); diff --git a/evm/src/arithmetic/arithmetic_stark.rs b/evm/src/arithmetic/arithmetic_stark.rs index ce8c7528..d3dfa982 100644 --- a/evm/src/arithmetic/arithmetic_stark.rs +++ b/evm/src/arithmetic/arithmetic_stark.rs @@ -8,6 +8,7 @@ use plonky2::hash::hash_types::RichField; use crate::arithmetic::add; use crate::arithmetic::columns; +use crate::arithmetic::compare; use crate::arithmetic::mul; use crate::arithmetic::sub; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; @@ -45,6 +46,10 @@ impl ArithmeticStark { sub::generate(local_values); } else if local_values[columns::IS_MUL].is_one() { mul::generate(local_values); + } else if local_values[columns::IS_LT].is_one() { + compare::generate(local_values, columns::IS_LT); + } else if local_values[columns::IS_GT].is_one() { + compare::generate(local_values, columns::IS_GT); } else { todo!("the requested operation has not yet been implemented"); } @@ -67,6 +72,7 @@ impl, const D: usize> Stark for ArithmeticSta add::eval_packed_generic(lv, yield_constr); sub::eval_packed_generic(lv, yield_constr); mul::eval_packed_generic(lv, yield_constr); + compare::eval_packed_generic(lv, yield_constr); } fn eval_ext_circuit( @@ -79,6 +85,7 @@ impl, const D: usize> Stark for ArithmeticSta add::eval_ext_circuit(builder, lv, yield_constr); sub::eval_ext_circuit(builder, lv, yield_constr); mul::eval_ext_circuit(builder, lv, yield_constr); + compare::eval_ext_circuit(builder, lv, yield_constr); } fn constraint_degree(&self) -> usize { diff --git a/evm/src/arithmetic/columns.rs b/evm/src/arithmetic/columns.rs index e51419a8..7b44adc1 100644 --- a/evm/src/arithmetic/columns.rs +++ b/evm/src/arithmetic/columns.rs @@ -79,4 +79,9 @@ pub(crate) const MUL_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; pub(crate) const MUL_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; pub(crate) const MUL_AUX_INPUT: [usize; N_LIMBS] = AUX_INPUT_0; +pub(crate) const CMP_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; +pub(crate) const CMP_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; +pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2[0]; +pub(crate) const CMP_AUX_INPUT: [usize; N_LIMBS] = AUX_INPUT_0; + pub const NUM_ARITH_COLUMNS: usize = START_SHARED_COLS + NUM_SHARED_COLS; diff --git a/evm/src/arithmetic/compare.rs b/evm/src/arithmetic/compare.rs new file mode 100644 index 00000000..8410cade --- /dev/null +++ b/evm/src/arithmetic/compare.rs @@ -0,0 +1,219 @@ +//! Support for EVM LT and GT instructions +//! +//! This crate verifies EVM LT and GT instructions (i.e. for unsigned +//! inputs). The difference between LT and GT is of course just a +//! matter of the order of the inputs. The verification is essentially +//! identical to the SUB instruction: For both SUB and LT we have values +//! +//! - `input0` +//! - `input1` +//! - `difference` (mod 2^256) +//! - `borrow` (= 0 or 1) +//! +//! satisfying `input0 - input1 = difference + borrow * 2^256`. Where +//! SUB verifies `difference` and ignores `borrow`, LT verifies +//! `borrow` (and uses `difference` as an auxiliary input). + +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; + +use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; +use crate::arithmetic::columns::*; +use crate::arithmetic::sub::u256_sub_br; +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::range_check_error; + +pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) { + let input0 = CMP_INPUT_0.map(|c| lv[c].to_canonical_u64()); + let input1 = CMP_INPUT_1.map(|c| lv[c].to_canonical_u64()); + + let (diff, br) = match op { + // input0 - input1 == diff + br*2^256 + IS_LT => u256_sub_br(input0, input1), + // input1 - input0 == diff + br*2^256 + IS_GT => u256_sub_br(input1, input0), + IS_SLT => todo!(), + IS_SGT => todo!(), + _ => panic!("op code not a comparison"), + }; + + for (&c, diff_limb) in CMP_AUX_INPUT.iter().zip(diff) { + lv[c] = F::from_canonical_u64(diff_limb); + } + lv[CMP_OUTPUT] = F::from_canonical_u64(br); +} + +pub(crate) fn eval_packed_generic_lt( + yield_constr: &mut ConstraintConsumer

, + is_op: P, + input0: [P; N_LIMBS], + input1: [P; N_LIMBS], + aux: [P; N_LIMBS], + output: P, +) { + // Verify (input0 < input1) == output by providing aux such that + // input0 - input1 == aux + output*2^256. + let lhs_limbs = input0.iter().zip(input1).map(|(&a, b)| a - b); + let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.into_iter(), lhs_limbs); + // We don't need to check that cy is 0 or 1, since output has + // already been checked to be 0 or 1. + yield_constr.constraint(is_op * (cy - output)); +} + +pub fn eval_packed_generic( + lv: &[P; NUM_ARITH_COLUMNS], + yield_constr: &mut ConstraintConsumer

, +) { + range_check_error!(CMP_INPUT_0, 16); + range_check_error!(CMP_INPUT_1, 16); + range_check_error!(CMP_AUX_INPUT, 16); + range_check_error!([CMP_OUTPUT], 1); + + let input0 = CMP_INPUT_0.map(|c| lv[c]); + let input1 = CMP_INPUT_1.map(|c| lv[c]); + let aux = CMP_AUX_INPUT.map(|c| lv[c]); + let output = lv[CMP_OUTPUT]; + + eval_packed_generic_lt(yield_constr, lv[IS_LT], input0, input1, aux, output); + eval_packed_generic_lt(yield_constr, lv[IS_GT], input1, input0, aux, output); +} + +#[allow(clippy::needless_collect)] +pub(crate) fn eval_ext_circuit_lt, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + yield_constr: &mut RecursiveConstraintConsumer, + is_op: ExtensionTarget, + input0: [ExtensionTarget; N_LIMBS], + input1: [ExtensionTarget; N_LIMBS], + aux: [ExtensionTarget; N_LIMBS], + output: ExtensionTarget, +) { + // Since `map` is lazy and the closure passed to it borrows + // `builder`, we can't then borrow builder again below in the call + // to `eval_ext_circuit_are_equal`. The solution is to force + // evaluation with `collect`. + let lhs_limbs = input0 + .iter() + .zip(input1) + .map(|(&a, b)| builder.sub_extension(a, b)) + .collect::>>(); + + let cy = eval_ext_circuit_are_equal( + builder, + yield_constr, + is_op, + aux.into_iter(), + lhs_limbs.into_iter(), + ); + let good_output = builder.sub_extension(cy, output); + let filter = builder.mul_extension(is_op, good_output); + yield_constr.constraint(builder, filter); +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &[ExtensionTarget; NUM_ARITH_COLUMNS], + yield_constr: &mut RecursiveConstraintConsumer, +) { + let input0 = CMP_INPUT_0.map(|c| lv[c]); + let input1 = CMP_INPUT_1.map(|c| lv[c]); + let aux = CMP_AUX_INPUT.map(|c| lv[c]); + let output = lv[CMP_OUTPUT]; + + eval_ext_circuit_lt( + builder, + yield_constr, + lv[IS_LT], + input0, + input1, + aux, + output, + ); + eval_ext_circuit_lt( + builder, + yield_constr, + lv[IS_GT], + input1, + input0, + aux, + output, + ); +} + +#[cfg(test)] +mod tests { + use plonky2::field::goldilocks_field::GoldilocksField; + use plonky2::field::types::Field; + use rand::{Rng, SeedableRng}; + use rand_chacha::ChaCha8Rng; + + use super::*; + use crate::arithmetic::columns::NUM_ARITH_COLUMNS; + use crate::constraint_consumer::ConstraintConsumer; + + // TODO: Should be able to refactor this test to apply to all operations. + #[test] + fn generate_eval_consistency_not_compare() { + type F = GoldilocksField; + + let mut rng = ChaCha8Rng::seed_from_u64(0x6feb51b7ec230f25); + let mut lv = [F::default(); NUM_ARITH_COLUMNS].map(|_| F::rand_from_rng(&mut rng)); + + // if `IS_LT == 0`, then the constraints should be met even if + // all values are garbage. `eval_packed_generic` handles IS_GT + // at the same time, so we check both at once. + lv[IS_LT] = F::ZERO; + lv[IS_GT] = F::ZERO; + + let mut constrant_consumer = ConstraintConsumer::new( + vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)], + F::ONE, + F::ONE, + F::ONE, + ); + eval_packed_generic(&lv, &mut constrant_consumer); + for &acc in &constrant_consumer.constraint_accs { + assert_eq!(acc, F::ZERO); + } + } + + #[test] + fn generate_eval_consistency_compare() { + type F = GoldilocksField; + + let mut rng = ChaCha8Rng::seed_from_u64(0x6feb51b7ec230f25); + let mut lv = [F::default(); NUM_ARITH_COLUMNS].map(|_| F::rand_from_rng(&mut rng)); + const N_ITERS: usize = 1000; + + for _ in 0..N_ITERS { + for (op, other_op) in [(IS_LT, IS_GT), (IS_GT, IS_LT)] { + // set op == 1 and ensure all constraints are satisfied. + // we have to explicitly set the other op to zero since both + // are treated by the call. + lv[op] = F::ONE; + lv[other_op] = F::ZERO; + + // set inputs to random values + for (&ai, bi) in CMP_INPUT_0.iter().zip(CMP_INPUT_1) { + lv[ai] = F::from_canonical_u16(rng.gen()); + lv[bi] = F::from_canonical_u16(rng.gen()); + } + + generate(&mut lv, op); + + let mut constrant_consumer = ConstraintConsumer::new( + vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)], + F::ONE, + F::ONE, + F::ONE, + ); + eval_packed_generic(&lv, &mut constrant_consumer); + for &acc in &constrant_consumer.constraint_accs { + assert_eq!(acc, F::ZERO); + } + } + } + } +} diff --git a/evm/src/arithmetic/mod.rs b/evm/src/arithmetic/mod.rs index 07c4c5a9..69fbda09 100644 --- a/evm/src/arithmetic/mod.rs +++ b/evm/src/arithmetic/mod.rs @@ -1,4 +1,5 @@ mod add; +mod compare; mod mul; mod sub; mod utils; diff --git a/evm/src/arithmetic/sub.rs b/evm/src/arithmetic/sub.rs index ce7932e2..c632eb94 100644 --- a/evm/src/arithmetic/sub.rs +++ b/evm/src/arithmetic/sub.rs @@ -9,26 +9,29 @@ use crate::arithmetic::columns::*; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; -pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = SUB_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1_limbs = SUB_INPUT_1.map(|c| lv[c].to_canonical_u64()); - - // Input and output have 16-bit limbs - let mut output_limbs = [0u64; N_LIMBS]; - +pub(crate) fn u256_sub_br(input0: [u64; N_LIMBS], input1: [u64; N_LIMBS]) -> ([u64; N_LIMBS], u64) { const LIMB_BOUNDARY: u64 = 1 << LIMB_BITS; const MASK: u64 = LIMB_BOUNDARY - 1u64; + let mut output = [0u64; N_LIMBS]; let mut br = 0u64; - for (i, a, b) in izip!(0.., input0_limbs, input1_limbs) { + for (i, a, b) in izip!(0.., input0, input1) { let d = LIMB_BOUNDARY + a - b - br; // if a < b, then d < 2^16 so br = 1 // if a >= b, then d >= 2^16 so br = 0 br = 1u64 - (d >> LIMB_BITS); assert!(br <= 1u64, "input limbs were larger than 16 bits"); - output_limbs[i] = d & MASK; + output[i] = d & MASK; } - // last borrow is dropped because this is subtraction modulo 2^256. + + (output, br) +} + +pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { + let input0_limbs = SUB_INPUT_0.map(|c| lv[c].to_canonical_u64()); + let input1_limbs = SUB_INPUT_1.map(|c| lv[c].to_canonical_u64()); + + let (output_limbs, _) = u256_sub_br(input0_limbs, input1_limbs); for (&c, output_limb) in SUB_OUTPUT.iter().zip(output_limbs) { lv[c] = F::from_canonical_u64(output_limb); diff --git a/evm/src/arithmetic/utils.rs b/evm/src/arithmetic/utils.rs index dc9a0a2f..c50481f3 100644 --- a/evm/src/arithmetic/utils.rs +++ b/evm/src/arithmetic/utils.rs @@ -19,4 +19,7 @@ macro_rules! range_check_error { ($cols:ident, $rc_bits:expr) => { $crate::arithmetic::utils::_range_check_error::<$rc_bits>(file!(), line!(), &$cols); }; + ([$cols:ident], $rc_bits:expr) => { + $crate::arithmetic::utils::_range_check_error::<$rc_bits>(file!(), line!(), &[$cols]); + }; } From aa87f2c3ba9c4768e01e661799ab3f8937555822 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 12:24:22 -0700 Subject: [PATCH 38/95] Public memory --- evm/src/arithmetic/arithmetic_stark.rs | 5 +- evm/src/cpu/bootstrap_kernel.rs | 7 +- evm/src/cpu/cpu_stark.rs | 5 +- evm/src/cpu/kernel/global_metadata.rs | 32 +- evm/src/cpu/kernel/mod.rs | 4 +- evm/src/cpu/mod.rs | 1 - evm/src/cpu/public_inputs.rs | 1 - evm/src/cross_table_lookup.rs | 26 +- evm/src/generation/mod.rs | 60 +++- evm/src/generation/state.rs | 66 +++- evm/src/get_challenges.rs | 16 +- evm/src/keccak/keccak_stark.rs | 7 +- evm/src/keccak/round_flags.rs | 6 +- evm/src/keccak_memory/keccak_memory_stark.rs | 7 +- evm/src/logic.rs | 5 +- evm/src/lookup.rs | 12 +- evm/src/memory/memory_stark.rs | 7 +- evm/src/permutation.rs | 5 +- evm/src/proof.rs | 73 +++-- evm/src/prover.rs | 81 ++--- evm/src/recursive_verifier.rs | 311 +++++++++++-------- evm/src/stark.rs | 15 +- evm/src/stark_testing.rs | 10 +- evm/src/util.rs | 27 ++ evm/src/vanishing_poly.rs | 5 +- evm/src/vars.rs | 11 +- evm/src/verifier.rs | 22 +- evm/tests/transfer_to_new_addr.rs | 38 ++- plonky2/src/iop/witness.rs | 9 +- plonky2/src/plonk/circuit_builder.rs | 4 + 30 files changed, 509 insertions(+), 369 deletions(-) delete mode 100644 evm/src/cpu/public_inputs.rs diff --git a/evm/src/arithmetic/arithmetic_stark.rs b/evm/src/arithmetic/arithmetic_stark.rs index ce8c7528..0dd69394 100644 --- a/evm/src/arithmetic/arithmetic_stark.rs +++ b/evm/src/arithmetic/arithmetic_stark.rs @@ -53,11 +53,10 @@ impl ArithmeticStark { impl, const D: usize> Stark for ArithmeticStark { const COLUMNS: usize = columns::NUM_ARITH_COLUMNS; - const PUBLIC_INPUTS: usize = 0; fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -72,7 +71,7 @@ impl, const D: usize> Stark for ArithmeticSta fn eval_ext_circuit( &self, builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let lv = vars.local_values; diff --git a/evm/src/cpu/bootstrap_kernel.rs b/evm/src/cpu/bootstrap_kernel.rs index 2c6afb51..533589af 100644 --- a/evm/src/cpu/bootstrap_kernel.rs +++ b/evm/src/cpu/bootstrap_kernel.rs @@ -15,7 +15,6 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::keccak_util::keccakf_u32s; -use crate::cpu::public_inputs::NUM_PUBLIC_INPUTS; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::memory::NUM_CHANNELS; @@ -50,7 +49,7 @@ pub(crate) fn generate_bootstrap_kernel(state: &mut GenerationState let mut packed_bytes: u32 = 0; for (addr, byte) in chunk { let channel = addr % NUM_CHANNELS; - state.set_mem_current(channel, Segment::Code, addr, byte.into()); + state.set_mem_cpu_current(channel, Segment::Code, addr, byte.into()); packed_bytes = (packed_bytes << 8) | byte as u32; } @@ -73,7 +72,7 @@ pub(crate) fn generate_bootstrap_kernel(state: &mut GenerationState } pub(crate) fn eval_bootstrap_kernel>( - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) { let local_values: &CpuColumnsView<_> = vars.local_values.borrow(); @@ -109,7 +108,7 @@ pub(crate) fn eval_bootstrap_kernel>( pub(crate) fn eval_bootstrap_kernel_circuit, const D: usize>( builder: &mut CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let local_values: &CpuColumnsView<_> = vars.local_values.borrow(); diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 0478c609..852b7b54 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -99,11 +99,10 @@ impl CpuStark { impl, const D: usize> Stark for CpuStark { const COLUMNS: usize = NUM_CPU_COLUMNS; - const PUBLIC_INPUTS: usize = 0; fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -122,7 +121,7 @@ impl, const D: usize> Stark for CpuStark, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let local_values = vars.local_values.borrow(); diff --git a/evm/src/cpu/kernel/global_metadata.rs b/evm/src/cpu/kernel/global_metadata.rs index 6378cd74..b6daf7e6 100644 --- a/evm/src/cpu/kernel/global_metadata.rs +++ b/evm/src/cpu/kernel/global_metadata.rs @@ -21,10 +21,20 @@ pub(crate) enum GlobalMetadata { /// The number of storage tries involved in this transaction. I.e. the number of values in /// `StorageTrieAddresses`, `StorageTriePointers` and `StorageTrieCheckpointPointers`. NumStorageTries = 7, + + // The root digests of each Merkle trie before these transactions. + StateTrieRootDigestBefore = 8, + TransactionsTrieRootDigestBefore = 9, + ReceiptsTrieRootDigestBefore = 10, + + // The root digests of each Merkle trie after these transactions. + StateTrieRootDigestAfter = 11, + TransactionsTrieRootDigestAfter = 12, + ReceiptsTrieRootDigestAfter = 13, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 8; + pub(crate) const COUNT: usize = 14; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -36,6 +46,12 @@ impl GlobalMetadata { Self::TransactionTrieRoot, Self::ReceiptTrieRoot, Self::NumStorageTries, + Self::StateTrieRootDigestBefore, + Self::TransactionsTrieRootDigestBefore, + Self::ReceiptsTrieRootDigestBefore, + Self::StateTrieRootDigestAfter, + Self::TransactionsTrieRootDigestAfter, + Self::ReceiptsTrieRootDigestAfter, ] } @@ -50,6 +66,20 @@ impl GlobalMetadata { GlobalMetadata::TransactionTrieRoot => "GLOBAL_METADATA_TXN_TRIE_ROOT", GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT", GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES", + GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE", + GlobalMetadata::TransactionsTrieRootDigestBefore => { + "GLOBAL_METADATA_TXNS_TRIE_DIGEST_BEFORE" + } + GlobalMetadata::ReceiptsTrieRootDigestBefore => { + "GLOBAL_METADATA_RECEIPTS_TRIE_DIGEST_BEFORE" + } + GlobalMetadata::StateTrieRootDigestAfter => "GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER", + GlobalMetadata::TransactionsTrieRootDigestAfter => { + "GLOBAL_METADATA_TXNS_TRIE_DIGEST_AFTER" + } + GlobalMetadata::ReceiptsTrieRootDigestAfter => { + "GLOBAL_METADATA_RECEIPTS_TRIE_DIGEST_AFTER" + } } } } diff --git a/evm/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs index eceba813..ef5a9ba0 100644 --- a/evm/src/cpu/kernel/mod.rs +++ b/evm/src/cpu/kernel/mod.rs @@ -2,9 +2,9 @@ pub mod aggregator; pub mod assembler; mod ast; mod constants; -mod context_metadata; +pub(crate) mod context_metadata; mod cost_estimator; -mod global_metadata; +pub(crate) mod global_metadata; pub(crate) mod keccak_util; mod opcodes; mod optimizer; diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index 5950c837..92e3e6ec 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -5,6 +5,5 @@ pub mod cpu_stark; pub(crate) mod decode; mod jumps; pub mod kernel; -pub mod public_inputs; mod simple_logic; mod syscalls; diff --git a/evm/src/cpu/public_inputs.rs b/evm/src/cpu/public_inputs.rs deleted file mode 100644 index 0a02e406..00000000 --- a/evm/src/cpu/public_inputs.rs +++ /dev/null @@ -1 +0,0 @@ -pub const NUM_PUBLIC_INPUTS: usize = 0; // PIs will be added later. diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 6071f8ff..e4c2cccb 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -19,7 +19,7 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::permutation::{ get_grand_product_challenge_set, GrandProductChallenge, GrandProductChallengeSet, }; -use crate::proof::{StarkProofWithPublicInputs, StarkProofWithPublicInputsTarget}; +use crate::proof::{StarkProof, StarkProofTarget}; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -337,7 +337,7 @@ impl<'a, F: RichField + Extendable, const D: usize> CtlCheckVars<'a, F, F::Extension, F::Extension, D> { pub(crate) fn from_proofs>( - proofs: &[StarkProofWithPublicInputs], + proofs: &[StarkProof], cross_table_lookups: &'a [CrossTableLookup], ctl_challenges: &'a GrandProductChallengeSet, num_permutation_zs: &[usize], @@ -347,7 +347,7 @@ impl<'a, F: RichField + Extendable, const D: usize> .iter() .zip(num_permutation_zs) .map(|(p, &num_perms)| { - let openings = &p.proof.openings; + let openings = &p.openings; let ctl_zs = openings.permutation_ctl_zs.iter().skip(num_perms); let ctl_zs_next = openings.permutation_ctl_zs_next.iter().skip(num_perms); ctl_zs.zip(ctl_zs_next) @@ -388,7 +388,7 @@ impl<'a, F: RichField + Extendable, const D: usize> } pub(crate) fn eval_cross_table_lookup_checks( - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, ctl_vars: &[CtlCheckVars], consumer: &mut ConstraintConsumer

, ) where @@ -441,7 +441,7 @@ pub struct CtlCheckVarsTarget<'a, F: Field, const D: usize> { impl<'a, F: Field, const D: usize> CtlCheckVarsTarget<'a, F, D> { pub(crate) fn from_proofs( - proofs: &[StarkProofWithPublicInputsTarget], + proofs: &[StarkProofTarget], cross_table_lookups: &'a [CrossTableLookup], ctl_challenges: &'a GrandProductChallengeSet, num_permutation_zs: &[usize], @@ -451,7 +451,7 @@ impl<'a, F: Field, const D: usize> CtlCheckVarsTarget<'a, F, D> { .iter() .zip(num_permutation_zs) .map(|(p, &num_perms)| { - let openings = &p.proof.openings; + let openings = &p.openings; let ctl_zs = openings.permutation_ctl_zs.iter().skip(num_perms); let ctl_zs_next = openings.permutation_ctl_zs_next.iter().skip(num_perms); ctl_zs.zip(ctl_zs_next) @@ -497,7 +497,7 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< const D: usize, >( builder: &mut CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, ctl_vars: &[CtlCheckVarsTarget], consumer: &mut RecursiveConstraintConsumer, ) { @@ -559,17 +559,17 @@ pub(crate) fn verify_cross_table_lookups< const D: usize, >( cross_table_lookups: Vec>, - proofs: &[StarkProofWithPublicInputs], + proofs: &[StarkProof], challenges: GrandProductChallengeSet, config: &StarkConfig, ) -> Result<()> { let degrees_bits = proofs .iter() - .map(|p| p.proof.recover_degree_bits(config)) + .map(|p| p.recover_degree_bits(config)) .collect::>(); let mut ctl_zs_openings = proofs .iter() - .map(|p| p.proof.openings.ctl_zs_last.iter()) + .map(|p| p.openings.ctl_zs_last.iter()) .collect::>(); for ( i, @@ -617,17 +617,17 @@ pub(crate) fn verify_cross_table_lookups_circuit< >( builder: &mut CircuitBuilder, cross_table_lookups: Vec>, - proofs: &[StarkProofWithPublicInputsTarget], + proofs: &[StarkProofTarget], challenges: GrandProductChallengeSet, inner_config: &StarkConfig, ) { let degrees_bits = proofs .iter() - .map(|p| p.proof.recover_degree_bits(inner_config)) + .map(|p| p.recover_degree_bits(inner_config)) .collect::>(); let mut ctl_zs_openings = proofs .iter() - .map(|p| p.proof.openings.ctl_zs_last.iter()) + .map(|p| p.openings.ctl_zs_last.iter()) .collect::>(); for ( i, diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 72b2be12..9cc18389 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -7,17 +7,20 @@ use plonky2::hash::hash_types::RichField; use crate::all_stark::AllStark; use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; use crate::cpu::columns::NUM_CPU_COLUMNS; +use crate::cpu::kernel::global_metadata::GlobalMetadata; use crate::generation::partial_trie::PartialTrie; use crate::generation::state::GenerationState; +use crate::memory::segments::Segment; +use crate::memory::NUM_CHANNELS; +use crate::proof::{BlockMetadata, PublicValues, TrieRoots}; use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; pub mod partial_trie; pub(crate) mod state; -#[allow(unused)] // TODO: Should be used soon. -pub struct TransactionData { - pub signed_txn: Vec, +pub struct EvmInputs { + pub signed_txns: Vec>, /// A partial version of the state trie prior to this transaction. It should include all nodes /// that will be accessed by this transaction. @@ -25,30 +28,55 @@ pub struct TransactionData { /// A partial version of the transaction trie prior to this transaction. It should include all /// nodes that will be accessed by this transaction. - pub transaction_trie: PartialTrie, + pub transactions_trie: PartialTrie, /// A partial version of the receipt trie prior to this transaction. It should include all nodes /// that will be accessed by this transaction. - pub receipt_trie: PartialTrie, + pub receipts_trie: PartialTrie, /// A partial version of each storage trie prior to this transaction. It should include all /// storage tries, and nodes therein, that will be accessed by this transaction. pub storage_tries: Vec<(Address, PartialTrie)>, + + pub block_metadata: BlockMetadata, } -#[allow(unused)] // TODO: Should be used soon. -pub fn generate_traces, const D: usize>( +pub(crate) fn generate_traces, const D: usize>( all_stark: &AllStark, - txns: &[TransactionData], -) -> Vec>> { + inputs: EvmInputs, +) -> (Vec>>, PublicValues) { let mut state = GenerationState::::default(); generate_bootstrap_kernel::(&mut state); - for txn in txns { + for txn in &inputs.signed_txns { generate_txn(&mut state, txn); } + // TODO: Pad to a power of two, ending in the `halt` kernel function. + + let cpu_rows = state.cpu_rows.len(); + let mem_end_timestamp = cpu_rows * NUM_CHANNELS; + let mut read_metadata = |field| { + state.get_mem( + 0, + Segment::GlobalMetadata, + field as usize, + mem_end_timestamp, + ) + }; + + let trie_roots_before = TrieRoots { + state_root: read_metadata(GlobalMetadata::StateTrieRootDigestBefore), + transactions_root: read_metadata(GlobalMetadata::TransactionsTrieRootDigestBefore), + receipts_root: read_metadata(GlobalMetadata::ReceiptsTrieRootDigestBefore), + }; + let trie_roots_after = TrieRoots { + state_root: read_metadata(GlobalMetadata::StateTrieRootDigestAfter), + transactions_root: read_metadata(GlobalMetadata::TransactionsTrieRootDigestAfter), + receipts_root: read_metadata(GlobalMetadata::ReceiptsTrieRootDigestAfter), + }; + let GenerationState { cpu_rows, current_cpu_row, @@ -63,9 +91,17 @@ pub fn generate_traces, const D: usize>( let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); let logic_trace = all_stark.logic_stark.generate_trace(logic_ops); let memory_trace = all_stark.memory_stark.generate_trace(memory.log); - vec![cpu_trace, keccak_trace, logic_trace, memory_trace] + let traces = vec![cpu_trace, keccak_trace, logic_trace, memory_trace]; + + let public_values = PublicValues { + trie_roots_before, + trie_roots_after, + block_metadata: inputs.block_metadata, + }; + + (traces, public_values) } -fn generate_txn(_state: &mut GenerationState, _txn: &TransactionData) { +fn generate_txn(_state: &mut GenerationState, _signed_txn: &[u8]) { // TODO } diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index bc84c01c..866f9fd7 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -10,6 +10,7 @@ use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; use crate::memory::segments::Segment; use crate::memory::NUM_CHANNELS; +use crate::util::u256_limbs; use crate::{keccak, logic}; #[derive(Debug)] @@ -52,28 +53,49 @@ impl GenerationState { result } - /// Read some memory within the current execution context, and log the operation. + /// Like `get_mem_cpu`, but reads from the current context specifically. #[allow(unused)] // TODO: Should be used soon. - pub(crate) fn get_mem_current( + pub(crate) fn get_mem_cpu_current( &mut self, channel_index: usize, segment: Segment, virt: usize, ) -> U256 { let context = self.current_context; - self.get_mem(channel_index, context, segment, virt) + self.get_mem_cpu(channel_index, context, segment, virt) } - /// Read some memory, and log the operation. - pub(crate) fn get_mem( + /// Simulates the CPU reading some memory through the given channel. Besides logging the memory + /// operation, this also generates the associated registers in the current CPU row. + pub(crate) fn get_mem_cpu( &mut self, channel_index: usize, context: usize, segment: Segment, virt: usize, ) -> U256 { + let timestamp = self.cpu_rows.len() * NUM_CHANNELS + channel_index; + let value = self.get_mem(context, segment, virt, timestamp); + self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; - let timestamp = self.cpu_rows.len(); + self.current_cpu_row.mem_is_read[channel_index] = F::ONE; + self.current_cpu_row.mem_addr_context[channel_index] = F::from_canonical_usize(context); + self.current_cpu_row.mem_addr_segment[channel_index] = + F::from_canonical_usize(segment as usize); + self.current_cpu_row.mem_addr_virtual[channel_index] = F::from_canonical_usize(virt); + self.current_cpu_row.mem_value[channel_index] = u256_limbs(value); + + value + } + + /// Read some memory, and log the operation. + pub(crate) fn get_mem( + &mut self, + context: usize, + segment: Segment, + virt: usize, + timestamp: usize, + ) -> U256 { let value = self.memory.contexts[context].segments[segment as usize].get(virt); self.memory.log.push(MemoryOp { filter: true, @@ -88,7 +110,7 @@ impl GenerationState { } /// Write some memory within the current execution context, and log the operation. - pub(crate) fn set_mem_current( + pub(crate) fn set_mem_cpu_current( &mut self, channel_index: usize, segment: Segment, @@ -96,11 +118,11 @@ impl GenerationState { value: U256, ) { let context = self.current_context; - self.set_mem(channel_index, context, segment, virt, value); + self.set_mem_cpu(channel_index, context, segment, virt, value); } /// Write some memory, and log the operation. - pub(crate) fn set_mem( + pub(crate) fn set_mem_cpu( &mut self, channel_index: usize, context: usize, @@ -108,9 +130,27 @@ impl GenerationState { virt: usize, value: U256, ) { + let timestamp = self.cpu_rows.len() * NUM_CHANNELS + channel_index; + self.set_mem(context, segment, virt, value, timestamp); + self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; - let timestamp = self.cpu_rows.len(); - let timestamp = timestamp * NUM_CHANNELS + channel_index; + self.current_cpu_row.mem_is_read[channel_index] = F::ZERO; // For clarity; should already be 0. + self.current_cpu_row.mem_addr_context[channel_index] = F::from_canonical_usize(context); + self.current_cpu_row.mem_addr_segment[channel_index] = + F::from_canonical_usize(segment as usize); + self.current_cpu_row.mem_addr_virtual[channel_index] = F::from_canonical_usize(virt); + self.current_cpu_row.mem_value[channel_index] = u256_limbs(value); + } + + /// Write some memory, and log the operation. + pub(crate) fn set_mem( + &mut self, + context: usize, + segment: Segment, + virt: usize, + value: U256, + timestamp: usize, + ) { self.memory.log.push(MemoryOp { filter: true, timestamp, @@ -133,11 +173,12 @@ impl GenerationState { virt: usize, ) -> [u64; keccak::keccak_stark::NUM_INPUTS] { let read_timestamp = self.cpu_rows.len() * NUM_CHANNELS; + let _write_timestamp = read_timestamp + 1; let input = (0..25) .map(|i| { let bytes = [0, 1, 2, 3, 4, 5, 6, 7].map(|j| { let virt = virt + i * 8 + j; - let byte = self.get_mem(0, context, segment, virt); + let byte = self.get_mem(context, segment, virt, read_timestamp); debug_assert!(byte.bits() <= 8); byte.as_u32() as u8 }); @@ -155,6 +196,7 @@ impl GenerationState { input, output, }); + // TODO: Write output to memory. output } diff --git a/evm/src/get_challenges.rs b/evm/src/get_challenges.rs index 88727ad3..52c2b796 100644 --- a/evm/src/get_challenges.rs +++ b/evm/src/get_challenges.rs @@ -24,9 +24,11 @@ impl, C: GenericConfig, const D: usize> A let mut challenger = Challenger::::new(); for proof in &self.stark_proofs { - challenger.observe_cap(&proof.proof.trace_cap); + challenger.observe_cap(&proof.trace_cap); } + // TODO: Observe public values. + let ctl_challenges = get_grand_product_challenge_set(&mut challenger, config.num_challenges); @@ -58,7 +60,7 @@ impl AllProofTarget { let mut challenger = RecursiveChallenger::::new(builder); for proof in &self.stark_proofs { - challenger.observe_cap(&proof.proof.trace_cap); + challenger.observe_cap(&proof.trace_cap); } let ctl_challenges = @@ -85,7 +87,7 @@ impl AllProofTarget { } } -impl StarkProofWithPublicInputs +impl StarkProof where F: RichField + Extendable, C: GenericConfig, @@ -98,7 +100,7 @@ where stark_permutation_batch_size: usize, config: &StarkConfig, ) -> StarkProofChallenges { - let degree_bits = self.proof.recover_degree_bits(config); + let degree_bits = self.recover_degree_bits(config); let StarkProof { permutation_ctl_zs_cap, @@ -112,7 +114,7 @@ where .. }, .. - } = &self.proof; + } = &self; let num_challenges = config.num_challenges; @@ -148,7 +150,7 @@ where } } -impl StarkProofWithPublicInputsTarget { +impl StarkProofTarget { pub(crate) fn get_challenges, C: GenericConfig>( &self, builder: &mut CircuitBuilder, @@ -172,7 +174,7 @@ impl StarkProofWithPublicInputsTarget { .. }, .. - } = &self.proof; + } = &self; let num_challenges = config.num_challenges; diff --git a/evm/src/keccak/keccak_stark.rs b/evm/src/keccak/keccak_stark.rs index d405c0e8..32aba72e 100644 --- a/evm/src/keccak/keccak_stark.rs +++ b/evm/src/keccak/keccak_stark.rs @@ -32,8 +32,6 @@ pub(crate) const NUM_ROUNDS: usize = 24; /// Number of 64-bit elements in the Keccak permutation input. pub(crate) const NUM_INPUTS: usize = 25; -pub(crate) const NUM_PUBLIC_INPUTS: usize = 0; - pub fn ctl_data() -> Vec> { let mut res: Vec<_> = (0..2 * NUM_INPUTS).map(reg_input_limb).collect(); res.extend(Column::singles((0..2 * NUM_INPUTS).map(reg_output_limb))); @@ -228,11 +226,10 @@ impl, const D: usize> KeccakStark { impl, const D: usize> Stark for KeccakStark { const COLUMNS: usize = NUM_COLUMNS; - const PUBLIC_INPUTS: usize = NUM_PUBLIC_INPUTS; fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -380,7 +377,7 @@ impl, const D: usize> Stark for KeccakStark, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let two = builder.two(); diff --git a/evm/src/keccak/round_flags.rs b/evm/src/keccak/round_flags.rs index 6a4d03b6..920ca4c8 100644 --- a/evm/src/keccak/round_flags.rs +++ b/evm/src/keccak/round_flags.rs @@ -7,12 +7,12 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::keccak::columns::reg_step; use crate::keccak::columns::NUM_COLUMNS; -use crate::keccak::keccak_stark::{NUM_PUBLIC_INPUTS, NUM_ROUNDS}; +use crate::keccak::keccak_stark::NUM_ROUNDS; use crate::vars::StarkEvaluationTargets; use crate::vars::StarkEvaluationVars; pub(crate) fn eval_round_flags>( - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) { // Initially, the first step flag should be 1 while the others should be 0. @@ -30,7 +30,7 @@ pub(crate) fn eval_round_flags>( pub(crate) fn eval_round_flags_recursively, const D: usize>( builder: &mut CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let one = builder.one_extension(); diff --git a/evm/src/keccak_memory/keccak_memory_stark.rs b/evm/src/keccak_memory/keccak_memory_stark.rs index 5b0f0fd1..cf8955b3 100644 --- a/evm/src/keccak_memory/keccak_memory_stark.rs +++ b/evm/src/keccak_memory/keccak_memory_stark.rs @@ -18,8 +18,6 @@ use crate::util::trace_rows_to_poly_values; use crate::vars::StarkEvaluationTargets; use crate::vars::StarkEvaluationVars; -const NUM_PUBLIC_INPUTS: usize = 0; - pub(crate) fn ctl_looked_data() -> Vec> { Column::singles([COL_CONTEXT, COL_SEGMENT, COL_VIRTUAL, COL_READ_TIMESTAMP]).collect() } @@ -162,11 +160,10 @@ impl, const D: usize> KeccakMemoryStark { impl, const D: usize> Stark for KeccakMemoryStark { const COLUMNS: usize = NUM_COLUMNS; - const PUBLIC_INPUTS: usize = NUM_PUBLIC_INPUTS; fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -180,7 +177,7 @@ impl, const D: usize> Stark for KeccakMemoryS fn eval_ext_circuit( &self, builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { // is_real must be 0 or 1. diff --git a/evm/src/logic.rs b/evm/src/logic.rs index 119c3d32..2499101b 100644 --- a/evm/src/logic.rs +++ b/evm/src/logic.rs @@ -140,11 +140,10 @@ impl LogicStark { impl, const D: usize> Stark for LogicStark { const COLUMNS: usize = columns::NUM_COLUMNS; - const PUBLIC_INPUTS: usize = 0; fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -196,7 +195,7 @@ impl, const D: usize> Stark for LogicStark, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let lv = &vars.local_values; diff --git a/evm/src/lookup.rs b/evm/src/lookup.rs index 2c93143f..ae92e864 100644 --- a/evm/src/lookup.rs +++ b/evm/src/lookup.rs @@ -10,13 +10,8 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; -pub(crate) fn eval_lookups< - F: Field, - P: PackedField, - const COLS: usize, - const PUB_INPUTS: usize, ->( - vars: StarkEvaluationVars, +pub(crate) fn eval_lookups, const COLS: usize>( + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, col_permuted_input: usize, col_permuted_table: usize, @@ -42,10 +37,9 @@ pub(crate) fn eval_lookups_circuit< F: RichField + Extendable, const D: usize, const COLS: usize, - const PUB_INPUTS: usize, >( builder: &mut CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, col_permuted_input: usize, col_permuted_table: usize, diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 8ed52ebb..1ec0c11c 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -26,8 +26,6 @@ use crate::permutation::PermutationPair; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; -pub(crate) const NUM_PUBLIC_INPUTS: usize = 0; - pub fn ctl_data() -> Vec> { let mut res = Column::singles([IS_READ, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]).collect_vec(); @@ -218,11 +216,10 @@ impl, const D: usize> MemoryStark { impl, const D: usize> Stark for MemoryStark { const COLUMNS: usize = NUM_COLUMNS; - const PUBLIC_INPUTS: usize = NUM_PUBLIC_INPUTS; fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -302,7 +299,7 @@ impl, const D: usize> Stark for MemoryStark, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { let one = builder.one_extension(); diff --git a/evm/src/permutation.rs b/evm/src/permutation.rs index c21a06de..0bb8ab1d 100644 --- a/evm/src/permutation.rs +++ b/evm/src/permutation.rs @@ -298,7 +298,7 @@ where pub(crate) fn eval_permutation_checks( stark: &S, config: &StarkConfig, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, permutation_vars: PermutationCheckVars, consumer: &mut ConstraintConsumer

, ) where @@ -365,14 +365,13 @@ pub(crate) fn eval_permutation_checks_circuit( builder: &mut CircuitBuilder, stark: &S, config: &StarkConfig, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, permutation_data: PermutationCheckDataTarget, consumer: &mut RecursiveConstraintConsumer, ) where F: RichField + Extendable, S: Stark, [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { let PermutationCheckDataTarget { local_zs, diff --git a/evm/src/proof.rs b/evm/src/proof.rs index 18b89e7a..15ca4656 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -1,3 +1,4 @@ +use ethereum_types::{Address, U256}; use itertools::Itertools; use maybe_rayon::*; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -17,21 +18,22 @@ use crate::permutation::GrandProductChallengeSet; #[derive(Debug, Clone)] pub struct AllProof, C: GenericConfig, const D: usize> { - pub stark_proofs: Vec>, + pub stark_proofs: Vec>, + pub public_values: PublicValues, } impl, C: GenericConfig, const D: usize> AllProof { pub fn degree_bits(&self, config: &StarkConfig) -> Vec { self.stark_proofs .iter() - .map(|proof| proof.proof.recover_degree_bits(config)) + .map(|proof| proof.recover_degree_bits(config)) .collect() } pub fn nums_ctl_zs(&self) -> Vec { self.stark_proofs .iter() - .map(|proof| proof.proof.openings.ctl_zs_last.len()) + .map(|proof| proof.openings.ctl_zs_last.len()) .collect() } } @@ -42,7 +44,54 @@ pub(crate) struct AllProofChallenges, const D: usiz } pub struct AllProofTarget { - pub stark_proofs: Vec>, + pub stark_proofs: Vec>, + pub public_values: PublicValuesTarget, +} + +#[derive(Debug, Clone)] +pub struct PublicValues { + pub trie_roots_before: TrieRoots, + pub trie_roots_after: TrieRoots, + pub block_metadata: BlockMetadata, +} + +#[derive(Debug, Clone)] +pub struct TrieRoots { + pub state_root: U256, + pub transactions_root: U256, + pub receipts_root: U256, +} + +#[derive(Debug, Clone)] +pub struct BlockMetadata { + pub block_coinbase: Address, + pub block_timestamp: U256, + pub block_number: U256, + pub block_difficulty: U256, + pub block_gaslimit: U256, + pub block_chain_id: U256, +} + +/// Note: All the larger integers are encoded with 32-bit limbs in little-endian order. +pub struct PublicValuesTarget { + pub trie_roots_before: TrieRootsTarget, + pub trie_roots_after: TrieRootsTarget, + pub block_metadata: BlockMetadataTarget, +} + +pub struct TrieRootsTarget { + pub state_root: [Target; 8], + pub transactions_root: [Target; 8], + pub receipts_root: [Target; 8], +} + +pub struct BlockMetadataTarget { + pub block_coinbase: [Target; 5], + pub block_timestamp: Target, + pub block_number: Target, + pub block_difficulty: Target, + pub block_gaslimit: Target, + pub block_chain_id: Target, } pub(crate) struct AllProofChallengesTarget { @@ -96,22 +145,6 @@ impl StarkProofTarget { } } -#[derive(Debug, Clone)] -pub struct StarkProofWithPublicInputs< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, -> { - pub proof: StarkProof, - // TODO: Maybe make it generic over a `S: Stark` and replace with `[F; S::PUBLIC_INPUTS]`. - pub public_inputs: Vec, -} - -pub struct StarkProofWithPublicInputsTarget { - pub proof: StarkProofTarget, - pub public_inputs: Vec, -} - pub(crate) struct StarkProofChallenges, const D: usize> { /// Randomness used in any permutation arguments. pub permutation_challenge_sets: Option>>, diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 8c33c289..d5e9a675 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -22,6 +22,7 @@ use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{cross_table_lookup_data, CtlCheckVars, CtlData}; +use crate::generation::{generate_traces, EvmInputs}; use crate::keccak::keccak_stark::KeccakStark; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; @@ -30,17 +31,38 @@ use crate::permutation::PermutationCheckVars; use crate::permutation::{ compute_permutation_z_polys, get_n_grand_product_challenge_sets, GrandProductChallengeSet, }; -use crate::proof::{AllProof, StarkOpeningSet, StarkProof, StarkProofWithPublicInputs}; +use crate::proof::{AllProof, PublicValues, StarkOpeningSet, StarkProof}; use crate::stark::Stark; use crate::vanishing_poly::eval_vanishing_poly; use crate::vars::StarkEvaluationVars; -/// Compute all STARK proofs. +/// Generate traces, then create all STARK proofs. pub fn prove( all_stark: &AllStark, config: &StarkConfig, - trace_poly_values: Vec>>, - public_inputs: Vec>, + inputs: EvmInputs, + timing: &mut TimingTree, +) -> Result> +where + F: RichField + Extendable, + C: GenericConfig, + [(); C::Hasher::HASH_SIZE]:, + [(); CpuStark::::COLUMNS]:, + [(); KeccakStark::::COLUMNS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); LogicStark::::COLUMNS]:, + [(); MemoryStark::::COLUMNS]:, +{ + let (traces, public_values) = generate_traces(all_stark, inputs); + prove_with_traces(all_stark, config, traces, public_values, timing) +} + +/// Compute all STARK proofs. +pub(crate) fn prove_with_traces( + all_stark: &AllStark, + config: &StarkConfig, + trace_poly_values: Vec>>, + public_values: PublicValues, timing: &mut TimingTree, ) -> Result> where @@ -48,19 +70,13 @@ where C: GenericConfig, [(); C::Hasher::HASH_SIZE]:, [(); CpuStark::::COLUMNS]:, - [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, - [(); KeccakStark::::PUBLIC_INPUTS]:, [(); KeccakMemoryStark::::COLUMNS]:, - [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, - [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, - [(); MemoryStark::::PUBLIC_INPUTS]:, { let num_starks = Table::num_tables(); debug_assert_eq!(num_starks, trace_poly_values.len()); - debug_assert_eq!(num_starks, public_inputs.len()); let rate_bits = config.fri_config.rate_bits; let cap_height = config.fri_config.cap_height; @@ -107,10 +123,6 @@ where &trace_poly_values[Table::Cpu as usize], &trace_commitments[Table::Cpu as usize], &ctl_data_per_table[Table::Cpu as usize], - public_inputs[Table::Cpu as usize] - .clone() - .try_into() - .unwrap(), &mut challenger, timing, )?; @@ -120,10 +132,6 @@ where &trace_poly_values[Table::Keccak as usize], &trace_commitments[Table::Keccak as usize], &ctl_data_per_table[Table::Keccak as usize], - public_inputs[Table::Keccak as usize] - .clone() - .try_into() - .unwrap(), &mut challenger, timing, )?; @@ -133,10 +141,6 @@ where &trace_poly_values[Table::KeccakMemory as usize], &trace_commitments[Table::KeccakMemory as usize], &ctl_data_per_table[Table::KeccakMemory as usize], - public_inputs[Table::KeccakMemory as usize] - .clone() - .try_into() - .unwrap(), &mut challenger, timing, )?; @@ -146,10 +150,6 @@ where &trace_poly_values[Table::Logic as usize], &trace_commitments[Table::Logic as usize], &ctl_data_per_table[Table::Logic as usize], - public_inputs[Table::Logic as usize] - .clone() - .try_into() - .unwrap(), &mut challenger, timing, )?; @@ -159,10 +159,6 @@ where &trace_poly_values[Table::Memory as usize], &trace_commitments[Table::Memory as usize], &ctl_data_per_table[Table::Memory as usize], - public_inputs[Table::Memory as usize] - .clone() - .try_into() - .unwrap(), &mut challenger, timing, )?; @@ -176,7 +172,10 @@ where ]; debug_assert_eq!(stark_proofs.len(), num_starks); - Ok(AllProof { stark_proofs }) + Ok(AllProof { + stark_proofs, + public_values, + }) } /// Compute proof for a single STARK table. @@ -186,17 +185,15 @@ fn prove_single_table( trace_poly_values: &[PolynomialValues], trace_commitment: &PolynomialBatch, ctl_data: &CtlData, - public_inputs: [F; S::PUBLIC_INPUTS], challenger: &mut Challenger, timing: &mut TimingTree, -) -> Result> +) -> Result> where F: RichField + Extendable, C: GenericConfig, S: Stark, [(); C::Hasher::HASH_SIZE]:, [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { let degree = trace_poly_values[0].len(); let degree_bits = log2_strict(degree); @@ -250,7 +247,6 @@ where &permutation_ctl_zs_commitment, permutation_challenges.as_ref(), ctl_data, - public_inputs, alphas.clone(), degree_bits, num_permutation_zs, @@ -263,7 +259,6 @@ where &permutation_ctl_zs_commitment, permutation_challenges.as_ref(), ctl_data, - public_inputs, alphas, degree_bits, num_permutation_zs, @@ -332,17 +327,13 @@ where timing, ) ); - let proof = StarkProof { + + Ok(StarkProof { trace_cap: trace_commitment.merkle_tree.cap.clone(), permutation_ctl_zs_cap, quotient_polys_cap, openings, opening_proof, - }; - - Ok(StarkProofWithPublicInputs { - proof, - public_inputs: public_inputs.to_vec(), }) } @@ -354,7 +345,6 @@ fn compute_quotient_polys<'a, F, P, C, S, const D: usize>( permutation_ctl_zs_commitment: &'a PolynomialBatch, permutation_challenges: Option<&'a Vec>>, ctl_data: &CtlData, - public_inputs: [F; S::PUBLIC_INPUTS], alphas: Vec, degree_bits: usize, num_permutation_zs: usize, @@ -366,7 +356,6 @@ where C: GenericConfig, S: Stark, [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { let degree = 1 << degree_bits; let rate_bits = config.fri_config.rate_bits; @@ -428,7 +417,6 @@ where let vars = StarkEvaluationVars { local_values: &get_trace_values_packed(i_start), next_values: &get_trace_values_packed(i_next_start), - public_inputs: &public_inputs, }; let permutation_check_vars = permutation_challenges.map(|permutation_challenge_sets| PermutationCheckVars { @@ -494,7 +482,6 @@ fn check_constraints<'a, F, C, S, const D: usize>( permutation_ctl_zs_commitment: &'a PolynomialBatch, permutation_challenges: Option<&'a Vec>>, ctl_data: &CtlData, - public_inputs: [F; S::PUBLIC_INPUTS], alphas: Vec, degree_bits: usize, num_permutation_zs: usize, @@ -504,7 +491,6 @@ fn check_constraints<'a, F, C, S, const D: usize>( C: GenericConfig, S: Stark, [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { let degree = 1 << degree_bits; let rate_bits = 0; // Set this to higher value to check constraint degree. @@ -553,7 +539,6 @@ fn check_constraints<'a, F, C, S, const D: usize>( let vars = StarkEvaluationVars { local_values: trace_subgroup_evals[i].as_slice().try_into().unwrap(), next_values: trace_subgroup_evals[i_next].as_slice().try_into().unwrap(), - public_inputs: &public_inputs, }; let permutation_check_vars = permutation_challenges.map(|permutation_challenge_sets| PermutationCheckVars { diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index 6eed4717..a86ff06e 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -22,11 +22,12 @@ use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckDataTarget; use crate::proof::{ - AllProof, AllProofChallengesTarget, AllProofTarget, StarkOpeningSetTarget, StarkProof, - StarkProofChallengesTarget, StarkProofTarget, StarkProofWithPublicInputs, - StarkProofWithPublicInputsTarget, + AllProof, AllProofChallengesTarget, AllProofTarget, BlockMetadata, BlockMetadataTarget, + PublicValues, PublicValuesTarget, StarkOpeningSetTarget, StarkProof, + StarkProofChallengesTarget, StarkProofTarget, TrieRoots, TrieRootsTarget, }; use crate::stark::Stark; +use crate::util::{h160_limbs, u256_limbs}; use crate::vanishing_poly::eval_vanishing_poly_circuit; use crate::vars::StarkEvaluationTargets; @@ -41,15 +42,10 @@ pub fn verify_proof_circuit< inner_config: &StarkConfig, ) where [(); CpuStark::::COLUMNS]:, - [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, - [(); KeccakStark::::PUBLIC_INPUTS]:, [(); KeccakMemoryStark::::COLUMNS]:, - [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, - [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, - [(); MemoryStark::::PUBLIC_INPUTS]:, C::Hasher: AlgebraicHasher, { let AllProofChallengesTarget { @@ -158,23 +154,17 @@ fn verify_stark_proof_with_challenges_circuit< >( builder: &mut CircuitBuilder, stark: S, - proof_with_pis: &StarkProofWithPublicInputsTarget, + proof: &StarkProofTarget, challenges: &StarkProofChallengesTarget, ctl_vars: &[CtlCheckVarsTarget], inner_config: &StarkConfig, ) where C::Hasher: AlgebraicHasher, [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { let zero = builder.zero(); let one = builder.one_extension(); - let StarkProofWithPublicInputsTarget { - proof, - public_inputs, - } = proof_with_pis; - assert_eq!(public_inputs.len(), S::PUBLIC_INPUTS); let StarkOpeningSetTarget { local_values, next_values, @@ -186,12 +176,6 @@ fn verify_stark_proof_with_challenges_circuit< let vars = StarkEvaluationTargets { local_values: &local_values.to_vec().try_into().unwrap(), next_values: &next_values.to_vec().try_into().unwrap(), - public_inputs: &public_inputs - .iter() - .map(|&t| builder.convert_to_ext(t)) - .collect::>() - .try_into() - .unwrap(), }; let degree_bits = proof.recover_degree_bits(inner_config); @@ -297,99 +281,93 @@ pub fn add_virtual_all_proof, const D: usize>( nums_ctl_zs: &[usize], ) -> AllProofTarget { let stark_proofs = vec![ - { - let proof = add_virtual_stark_proof( - builder, - all_stark.cpu_stark, - config, - degree_bits[Table::Cpu as usize], - nums_ctl_zs[Table::Cpu as usize], - ); - let public_inputs = builder.add_virtual_targets(CpuStark::::PUBLIC_INPUTS); - StarkProofWithPublicInputsTarget { - proof, - public_inputs, - } - }, - { - let proof = add_virtual_stark_proof( - builder, - all_stark.keccak_stark, - config, - degree_bits[Table::Keccak as usize], - nums_ctl_zs[Table::Keccak as usize], - ); - let public_inputs = builder.add_virtual_targets(KeccakStark::::PUBLIC_INPUTS); - StarkProofWithPublicInputsTarget { - proof, - public_inputs, - } - }, - { - let proof = add_virtual_stark_proof( - builder, - all_stark.keccak_memory_stark, - config, - degree_bits[Table::KeccakMemory as usize], - nums_ctl_zs[Table::KeccakMemory as usize], - ); - let public_inputs = - builder.add_virtual_targets(KeccakMemoryStark::::PUBLIC_INPUTS); - StarkProofWithPublicInputsTarget { - proof, - public_inputs, - } - }, - { - let proof = add_virtual_stark_proof( - builder, - all_stark.logic_stark, - config, - degree_bits[Table::Logic as usize], - nums_ctl_zs[Table::Logic as usize], - ); - let public_inputs = builder.add_virtual_targets(LogicStark::::PUBLIC_INPUTS); - StarkProofWithPublicInputsTarget { - proof, - public_inputs, - } - }, - { - let proof = add_virtual_stark_proof( - builder, - all_stark.memory_stark, - config, - degree_bits[Table::Memory as usize], - nums_ctl_zs[Table::Memory as usize], - ); - let public_inputs = builder.add_virtual_targets(MemoryStark::::PUBLIC_INPUTS); - StarkProofWithPublicInputsTarget { - proof, - public_inputs, - } - }, + add_virtual_stark_proof( + builder, + all_stark.cpu_stark, + config, + degree_bits[Table::Cpu as usize], + nums_ctl_zs[Table::Cpu as usize], + ), + add_virtual_stark_proof( + builder, + all_stark.keccak_stark, + config, + degree_bits[Table::Keccak as usize], + nums_ctl_zs[Table::Keccak as usize], + ), + add_virtual_stark_proof( + builder, + all_stark.keccak_memory_stark, + config, + degree_bits[Table::KeccakMemory as usize], + nums_ctl_zs[Table::KeccakMemory as usize], + ), + add_virtual_stark_proof( + builder, + all_stark.logic_stark, + config, + degree_bits[Table::Logic as usize], + nums_ctl_zs[Table::Logic as usize], + ), + add_virtual_stark_proof( + builder, + all_stark.memory_stark, + config, + degree_bits[Table::Memory as usize], + nums_ctl_zs[Table::Memory as usize], + ), ]; - assert_eq!(stark_proofs.len(), Table::num_tables()); - AllProofTarget { stark_proofs } + + let public_values = add_virtual_public_values(builder); + AllProofTarget { + stark_proofs, + public_values, + } } -pub fn add_virtual_stark_proof_with_pis< - F: RichField + Extendable, - S: Stark, - const D: usize, ->( +pub fn add_virtual_public_values, const D: usize>( builder: &mut CircuitBuilder, - stark: S, - config: &StarkConfig, - degree_bits: usize, - num_ctl_zs: usize, -) -> StarkProofWithPublicInputsTarget { - let proof = add_virtual_stark_proof::(builder, stark, config, degree_bits, num_ctl_zs); - let public_inputs = builder.add_virtual_targets(S::PUBLIC_INPUTS); - StarkProofWithPublicInputsTarget { - proof, - public_inputs, +) -> PublicValuesTarget { + let trie_roots_before = add_virtual_trie_roots(builder); + let trie_roots_after = add_virtual_trie_roots(builder); + let block_metadata = add_virtual_block_metadata(builder); + PublicValuesTarget { + trie_roots_before, + trie_roots_after, + block_metadata, + } +} + +pub fn add_virtual_trie_roots, const D: usize>( + builder: &mut CircuitBuilder, +) -> TrieRootsTarget { + let state_root = builder.add_virtual_target_arr(); + let transactions_root = builder.add_virtual_target_arr(); + let receipts_root = builder.add_virtual_target_arr(); + TrieRootsTarget { + state_root, + transactions_root, + receipts_root, + } +} + +pub fn add_virtual_block_metadata, const D: usize>( + builder: &mut CircuitBuilder, +) -> BlockMetadataTarget { + let block_coinbase = builder.add_virtual_target_arr(); + let block_timestamp = builder.add_virtual_target(); + let block_number = builder.add_virtual_target(); + let block_difficulty = builder.add_virtual_target(); + let block_gaslimit = builder.add_virtual_target(); + let block_chain_id = builder.add_virtual_target(); + BlockMetadataTarget { + block_coinbase, + block_timestamp, + block_number, + block_difficulty, + block_gaslimit, + block_chain_id, } } @@ -455,35 +433,13 @@ pub fn set_all_proof_target, W, const D: usize>( .iter() .zip_eq(&all_proof.stark_proofs) { - set_stark_proof_with_pis_target(witness, pt, p, zero); + set_stark_proof_target(witness, pt, p, zero); } -} - -pub fn set_stark_proof_with_pis_target, W, const D: usize>( - witness: &mut W, - stark_proof_with_pis_target: &StarkProofWithPublicInputsTarget, - stark_proof_with_pis: &StarkProofWithPublicInputs, - zero: Target, -) where - F: RichField + Extendable, - C::Hasher: AlgebraicHasher, - W: Witness, -{ - let StarkProofWithPublicInputs { - proof, - public_inputs, - } = stark_proof_with_pis; - let StarkProofWithPublicInputsTarget { - proof: pt, - public_inputs: pi_targets, - } = stark_proof_with_pis_target; - - // Set public inputs. - for (&pi_t, &pi) in pi_targets.iter().zip_eq(public_inputs) { - witness.set_target(pi_t, pi); - } - - set_stark_proof_target(witness, pt, proof, zero); + set_public_value_targets( + witness, + &all_proof_target.public_values, + &all_proof.public_values, + ) } pub fn set_stark_proof_target, W, const D: usize>( @@ -511,3 +467,84 @@ pub fn set_stark_proof_target, W, const D: usize>( set_fri_proof_target(witness, &proof_target.opening_proof, &proof.opening_proof); } + +pub fn set_public_value_targets( + witness: &mut W, + public_values_target: &PublicValuesTarget, + public_values: &PublicValues, +) where + F: RichField + Extendable, + W: Witness, +{ + set_trie_roots_target( + witness, + &public_values_target.trie_roots_before, + &public_values.trie_roots_before, + ); + set_trie_roots_target( + witness, + &public_values_target.trie_roots_after, + &public_values.trie_roots_after, + ); + set_block_metadata_target( + witness, + &public_values_target.block_metadata, + &public_values.block_metadata, + ); +} + +pub fn set_trie_roots_target( + witness: &mut W, + trie_roots_target: &TrieRootsTarget, + trie_roots: &TrieRoots, +) where + F: RichField + Extendable, + W: Witness, +{ + witness.set_target_arr( + trie_roots_target.state_root, + u256_limbs(trie_roots.state_root), + ); + witness.set_target_arr( + trie_roots_target.transactions_root, + u256_limbs(trie_roots.transactions_root), + ); + witness.set_target_arr( + trie_roots_target.receipts_root, + u256_limbs(trie_roots.receipts_root), + ); +} + +pub fn set_block_metadata_target( + witness: &mut W, + block_metadata_target: &BlockMetadataTarget, + block_metadata: &BlockMetadata, +) where + F: RichField + Extendable, + W: Witness, +{ + witness.set_target_arr( + block_metadata_target.block_coinbase, + h160_limbs(block_metadata.block_coinbase), + ); + witness.set_target( + block_metadata_target.block_timestamp, + F::from_canonical_u64(block_metadata.block_timestamp.as_u64()), + ); + witness.set_target( + block_metadata_target.block_number, + F::from_canonical_u64(block_metadata.block_number.as_u64()), + ); + witness.set_target( + block_metadata_target.block_difficulty, + F::from_canonical_u64(block_metadata.block_difficulty.as_u64()), + ); + witness.set_target( + block_metadata_target.block_gaslimit, + F::from_canonical_u64(block_metadata.block_gaslimit.as_u64()), + ); + witness.set_target( + block_metadata_target.block_chain_id, + F::from_canonical_u64(block_metadata.block_chain_id.as_u64()), + ); +} diff --git a/evm/src/stark.rs b/evm/src/stark.rs index 8935655b..a205547a 100644 --- a/evm/src/stark.rs +++ b/evm/src/stark.rs @@ -20,8 +20,6 @@ use crate::vars::StarkEvaluationVars; pub trait Stark, const D: usize>: Sync { /// The total number of columns in the trace. const COLUMNS: usize; - /// The number of public inputs. - const PUBLIC_INPUTS: usize; /// Evaluate constraints at a vector of points. /// @@ -31,7 +29,7 @@ pub trait Stark, const D: usize>: Sync { /// constraints over `F`. fn eval_packed_generic( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, @@ -40,7 +38,7 @@ pub trait Stark, const D: usize>: Sync { /// Evaluate constraints at a vector of points from the base field `F`. fn eval_packed_base>( &self, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer

, ) { self.eval_packed_generic(vars, yield_constr) @@ -49,12 +47,7 @@ pub trait Stark, const D: usize>: Sync { /// Evaluate constraints at a single point from the degree `D` extension field. fn eval_ext( &self, - vars: StarkEvaluationVars< - F::Extension, - F::Extension, - { Self::COLUMNS }, - { Self::PUBLIC_INPUTS }, - >, + vars: StarkEvaluationVars, yield_constr: &mut ConstraintConsumer, ) { self.eval_packed_generic(vars, yield_constr) @@ -67,7 +60,7 @@ pub trait Stark, const D: usize>: Sync { fn eval_ext_circuit( &self, builder: &mut CircuitBuilder, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ); diff --git a/evm/src/stark_testing.rs b/evm/src/stark_testing.rs index 809423d4..5cd83e41 100644 --- a/evm/src/stark_testing.rs +++ b/evm/src/stark_testing.rs @@ -26,13 +26,11 @@ pub fn test_stark_low_degree, S: Stark, const ) -> Result<()> where [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { let rate_bits = log2_ceil(stark.constraint_degree() + 1); let trace_ldes = random_low_degree_matrix::(S::COLUMNS, rate_bits); let size = trace_ldes.len(); - let public_inputs = F::rand_arr::<{ S::PUBLIC_INPUTS }>(); let lagrange_first = PolynomialValues::selector(WITNESS_SIZE, 0).lde(rate_bits); let lagrange_last = PolynomialValues::selector(WITNESS_SIZE, WITNESS_SIZE - 1).lde(rate_bits); @@ -49,7 +47,6 @@ where .clone() .try_into() .unwrap(), - public_inputs: &public_inputs, }; let mut consumer = ConstraintConsumer::::new( @@ -89,14 +86,12 @@ pub fn test_stark_circuit_constraints< ) -> Result<()> where [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, [(); C::Hasher::HASH_SIZE]:, { // Compute native constraint evaluation on random values. let vars = StarkEvaluationVars { local_values: &F::Extension::rand_arr::<{ S::COLUMNS }>(), next_values: &F::Extension::rand_arr::<{ S::COLUMNS }>(), - public_inputs: &F::Extension::rand_arr::<{ S::PUBLIC_INPUTS }>(), }; let alphas = F::rand_vec(1); let z_last = F::Extension::rand(); @@ -124,8 +119,6 @@ where pw.set_extension_targets(&locals_t, vars.local_values); let nexts_t = builder.add_virtual_extension_targets(S::COLUMNS); pw.set_extension_targets(&nexts_t, vars.next_values); - let pis_t = builder.add_virtual_extension_targets(S::PUBLIC_INPUTS); - pw.set_extension_targets(&pis_t, vars.public_inputs); let alphas_t = builder.add_virtual_targets(1); pw.set_target(alphas_t[0], alphas[0]); let z_last_t = builder.add_virtual_extension_target(); @@ -135,10 +128,9 @@ where let lagrange_last_t = builder.add_virtual_extension_target(); pw.set_extension_target(lagrange_last_t, lagrange_last); - let vars = StarkEvaluationTargets:: { + let vars = StarkEvaluationTargets:: { local_values: &locals_t.try_into().unwrap(), next_values: &nexts_t.try_into().unwrap(), - public_inputs: &pis_t.try_into().unwrap(), }; let mut consumer = RecursiveConstraintConsumer::::new( builder.zero_extension(), diff --git a/evm/src/util.rs b/evm/src/util.rs index 5bc85f99..5b75c999 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -1,3 +1,4 @@ +use ethereum_types::{H160, U256}; use itertools::Itertools; use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; @@ -40,3 +41,29 @@ pub fn trace_rows_to_poly_values( .map(|column| PolynomialValues::new(column)) .collect() } + +/// Returns the 32-bit little-endian limbs of a `U256`. +pub(crate) fn u256_limbs(u256: U256) -> [F; 8] { + u256.0 + .into_iter() + .flat_map(|limb_64| { + let lo = (limb_64 & 0xFFFFFFFF) as u32; + let hi = (limb_64 >> 32) as u32; + [lo, hi] + }) + .map(F::from_canonical_u32) + .collect_vec() + .try_into() + .unwrap() +} + +/// Returns the 32-bit limbs of a `U160`. +pub(crate) fn h160_limbs(h160: H160) -> [F; 5] { + h160.0 + .chunks(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) + .map(F::from_canonical_u32) + .collect_vec() + .try_into() + .unwrap() +} diff --git a/evm/src/vanishing_poly.rs b/evm/src/vanishing_poly.rs index c0a6534b..e776fa5c 100644 --- a/evm/src/vanishing_poly.rs +++ b/evm/src/vanishing_poly.rs @@ -20,7 +20,7 @@ use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; pub(crate) fn eval_vanishing_poly( stark: &S, config: &StarkConfig, - vars: StarkEvaluationVars, + vars: StarkEvaluationVars, permutation_vars: Option>, ctl_vars: &[CtlCheckVars], consumer: &mut ConstraintConsumer

, @@ -48,7 +48,7 @@ pub(crate) fn eval_vanishing_poly_circuit( builder: &mut CircuitBuilder, stark: &S, config: &StarkConfig, - vars: StarkEvaluationTargets, + vars: StarkEvaluationTargets, permutation_data: Option>, ctl_vars: &[CtlCheckVarsTarget], consumer: &mut RecursiveConstraintConsumer, @@ -57,7 +57,6 @@ pub(crate) fn eval_vanishing_poly_circuit( C: GenericConfig, S: Stark, [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, { stark.eval_ext_circuit(builder, vars, consumer); if let Some(permutation_data) = permutation_data { diff --git a/evm/src/vars.rs b/evm/src/vars.rs index 682ac837..6c82675c 100644 --- a/evm/src/vars.rs +++ b/evm/src/vars.rs @@ -3,24 +3,17 @@ use plonky2::field::types::Field; use plonky2::iop::ext_target::ExtensionTarget; #[derive(Debug, Copy, Clone)] -pub struct StarkEvaluationVars<'a, F, P, const COLUMNS: usize, const PUBLIC_INPUTS: usize> +pub struct StarkEvaluationVars<'a, F, P, const COLUMNS: usize> where F: Field, P: PackedField, { pub local_values: &'a [P; COLUMNS], pub next_values: &'a [P; COLUMNS], - pub public_inputs: &'a [P::Scalar; PUBLIC_INPUTS], } #[derive(Debug, Copy, Clone)] -pub struct StarkEvaluationTargets< - 'a, - const D: usize, - const COLUMNS: usize, - const PUBLIC_INPUTS: usize, -> { +pub struct StarkEvaluationTargets<'a, const D: usize, const COLUMNS: usize> { pub local_values: &'a [ExtensionTarget; COLUMNS], pub next_values: &'a [ExtensionTarget; COLUMNS], - pub public_inputs: &'a [ExtensionTarget; PUBLIC_INPUTS], } diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index c8e3b8e6..9b56422d 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -17,7 +17,7 @@ use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckVars; use crate::proof::{ - AllProof, AllProofChallenges, StarkOpeningSet, StarkProofChallenges, StarkProofWithPublicInputs, + AllProof, AllProofChallenges, StarkOpeningSet, StarkProof, StarkProofChallenges, }; use crate::stark::Stark; use crate::vanishing_poly::eval_vanishing_poly; @@ -30,15 +30,10 @@ pub fn verify_proof, C: GenericConfig, co ) -> Result<()> where [(); CpuStark::::COLUMNS]:, - [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, - [(); KeccakStark::::PUBLIC_INPUTS]:, [(); KeccakMemoryStark::::COLUMNS]:, - [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, - [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, - [(); MemoryStark::::PUBLIC_INPUTS]:, [(); C::Hasher::HASH_SIZE]:, { let AllProofChallenges { @@ -115,21 +110,15 @@ pub(crate) fn verify_stark_proof_with_challenges< const D: usize, >( stark: S, - proof_with_pis: &StarkProofWithPublicInputs, + proof: &StarkProof, challenges: &StarkProofChallenges, ctl_vars: &[CtlCheckVars], config: &StarkConfig, ) -> Result<()> where [(); S::COLUMNS]:, - [(); S::PUBLIC_INPUTS]:, [(); C::Hasher::HASH_SIZE]:, { - let StarkProofWithPublicInputs { - proof, - public_inputs, - } = proof_with_pis; - ensure!(public_inputs.len() == S::PUBLIC_INPUTS); let StarkOpeningSet { local_values, next_values, @@ -141,13 +130,6 @@ where let vars = StarkEvaluationVars { local_values: &local_values.to_vec().try_into().unwrap(), next_values: &next_values.to_vec().try_into().unwrap(), - public_inputs: &public_inputs - .iter() - .copied() - .map(F::Extension::from_basefield) - .collect::>() - .try_into() - .unwrap(), }; let degree_bits = proof.recover_degree_bits(config); diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index e59e5d85..a2a5788e 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -5,7 +5,8 @@ use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; use plonky2_evm::generation::partial_trie::PartialTrie; -use plonky2_evm::generation::{generate_traces, TransactionData}; +use plonky2_evm::generation::EvmInputs; +use plonky2_evm::proof::BlockMetadata; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; @@ -18,26 +19,29 @@ type C = PoseidonGoldilocksConfig; #[ignore] // TODO: Won't work until txn parsing, storage, etc. are implemented. fn test_simple_transfer() -> anyhow::Result<()> { let all_stark = AllStark::::default(); + let config = StarkConfig::standard_fast_config(); - let txn = TransactionData { - signed_txn: hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38").to_vec(), - // TODO: Add trie with sender account. - state_trie: PartialTrie::Empty, - transaction_trie: PartialTrie::Empty, - receipt_trie: PartialTrie::Empty, - storage_tries: vec![], + let block_metadata = BlockMetadata { + block_coinbase: Default::default(), + block_timestamp: Default::default(), + block_number: Default::default(), + block_difficulty: Default::default(), + block_gaslimit: Default::default(), + block_chain_id: Default::default(), }; - let traces = generate_traces(&all_stark, &[txn]); + let txn = hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38"); - let config = StarkConfig::standard_fast_config(); - let proof = prove::( - &all_stark, - &config, - traces, - vec![vec![]; 4], - &mut TimingTree::default(), - )?; + let inputs = EvmInputs { + signed_txns: vec![txn.to_vec()], + state_trie: PartialTrie::Empty, + transactions_trie: PartialTrie::Empty, + receipts_trie: PartialTrie::Empty, + storage_tries: vec![], + block_metadata, + }; + + let proof = prove::(&all_stark, &config, inputs, &mut TimingTree::default())?; verify_proof(all_stark, proof, &config) } diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs index 871f303f..caa22c33 100644 --- a/plonky2/src/iop/witness.rs +++ b/plonky2/src/iop/witness.rs @@ -104,9 +104,12 @@ pub trait Witness { where F: RichField + Extendable, { - let limbs = value.to_basefield_array(); - (0..D).for_each(|i| { - self.set_target(et.0[i], limbs[i]); + self.set_target_arr(et.0, value.to_basefield_array()); + } + + fn set_target_arr(&mut self, targets: [Target; N], values: [F; N]) { + (0..N).for_each(|i| { + self.set_target(targets[i], values[i]); }); } diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 6728551c..ca68af9c 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -157,6 +157,10 @@ impl, const D: usize> CircuitBuilder { (0..n).map(|_i| self.add_virtual_target()).collect() } + pub fn add_virtual_target_arr(&mut self) -> [Target; N] { + [0; N].map(|_| self.add_virtual_target()) + } + pub fn add_virtual_hash(&mut self) -> HashOutTarget { HashOutTarget::from_vec(self.add_virtual_targets(4)) } From b829b44dcf0931ba088866f54b382b6ea786bc1b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 22:11:25 -0700 Subject: [PATCH 39/95] Fix test --- evm/src/all_stark.rs | 11 ++++++----- evm/src/generation/mod.rs | 5 +++-- evm/src/proof.rs | 8 +++++--- evm/src/prover.rs | 4 ++-- evm/tests/transfer_to_new_addr.rs | 4 ++-- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index e3db96ed..68ffe86f 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -189,7 +189,7 @@ mod tests { use plonky2::util::timing::TimingTree; use rand::{thread_rng, Rng}; - use crate::all_stark::{AllStark, Table}; + use crate::all_stark::AllStark; use crate::config::StarkConfig; use crate::cpu::cpu_stark::CpuStark; use crate::cpu::kernel::aggregator::KERNEL; @@ -200,8 +200,8 @@ mod tests { use crate::memory::memory_stark::tests::generate_random_memory_ops; use crate::memory::memory_stark::MemoryStark; use crate::memory::NUM_CHANNELS; - use crate::proof::AllProof; - use crate::prover::prove; + use crate::proof::{AllProof, PublicValues}; + use crate::prover::prove_with_traces; use crate::recursive_verifier::{ add_virtual_all_proof, set_all_proof_target, verify_proof_circuit, }; @@ -704,11 +704,12 @@ mod tests { ]; check_ctls(&traces, &all_stark.cross_table_lookups); - let proof = prove::( + let public_values = PublicValues::default(); + let proof = prove_with_traces::( &all_stark, config, traces, - vec![vec![]; Table::num_tables()], + public_values, &mut TimingTree::default(), )?; diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 9cc18389..7b54ea29 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -19,7 +19,8 @@ pub(crate) mod memory; pub mod partial_trie; pub(crate) mod state; -pub struct EvmInputs { +/// Inputs needed for trace generation. +pub struct GenerationInputs { pub signed_txns: Vec>, /// A partial version of the state trie prior to this transaction. It should include all nodes @@ -43,7 +44,7 @@ pub struct EvmInputs { pub(crate) fn generate_traces, const D: usize>( all_stark: &AllStark, - inputs: EvmInputs, + inputs: GenerationInputs, ) -> (Vec>>, PublicValues) { let mut state = GenerationState::::default(); diff --git a/evm/src/proof.rs b/evm/src/proof.rs index 15ca4656..6cb47d33 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -48,21 +48,22 @@ pub struct AllProofTarget { pub public_values: PublicValuesTarget, } -#[derive(Debug, Clone)] +/// Memory values which are public. +#[derive(Debug, Clone, Default)] pub struct PublicValues { pub trie_roots_before: TrieRoots, pub trie_roots_after: TrieRoots, pub block_metadata: BlockMetadata, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct TrieRoots { pub state_root: U256, pub transactions_root: U256, pub receipts_root: U256, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct BlockMetadata { pub block_coinbase: Address, pub block_timestamp: U256, @@ -72,6 +73,7 @@ pub struct BlockMetadata { pub block_chain_id: U256, } +/// Memory values which are public. /// Note: All the larger integers are encoded with 32-bit limbs in little-endian order. pub struct PublicValuesTarget { pub trie_roots_before: TrieRootsTarget, diff --git a/evm/src/prover.rs b/evm/src/prover.rs index d5e9a675..75152d61 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -22,7 +22,7 @@ use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{cross_table_lookup_data, CtlCheckVars, CtlData}; -use crate::generation::{generate_traces, EvmInputs}; +use crate::generation::{generate_traces, GenerationInputs}; use crate::keccak::keccak_stark::KeccakStark; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; @@ -40,7 +40,7 @@ use crate::vars::StarkEvaluationVars; pub fn prove( all_stark: &AllStark, config: &StarkConfig, - inputs: EvmInputs, + inputs: GenerationInputs, timing: &mut TimingTree, ) -> Result> where diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index a2a5788e..50099816 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -5,7 +5,7 @@ use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; use plonky2_evm::generation::partial_trie::PartialTrie; -use plonky2_evm::generation::EvmInputs; +use plonky2_evm::generation::GenerationInputs; use plonky2_evm::proof::BlockMetadata; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; @@ -32,7 +32,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { let txn = hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38"); - let inputs = EvmInputs { + let inputs = GenerationInputs { signed_txns: vec![txn.to_vec()], state_trie: PartialTrie::Empty, transactions_trie: PartialTrie::Empty, From 66a39996797d037eb2dd99abbdba36555469e564 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 23:13:13 -0700 Subject: [PATCH 40/95] Keccak generation tweak --- evm/src/keccak/keccak_stark.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/evm/src/keccak/keccak_stark.rs b/evm/src/keccak/keccak_stark.rs index d405c0e8..e8e0e9a3 100644 --- a/evm/src/keccak/keccak_stark.rs +++ b/evm/src/keccak/keccak_stark.rs @@ -134,9 +134,10 @@ impl, const D: usize> KeccakStark { } } - // Populate A'. - // A'[x, y] = xor(A[x, y], D[x]) - // = xor(A[x, y], C[x - 1], ROT(C[x + 1], 1)) + // Populate A'. To avoid shifting indices, we rewrite + // A'[x, y, z] = xor(A[x, y, z], C[x - 1, z], C[x + 1, z - 1]) + // as + // A'[x, y, z] = xor(A[x, y, z], C[x, z], C'[x, z]). for x in 0..5 { for y in 0..5 { for z in 0..64 { @@ -145,11 +146,8 @@ impl, const D: usize> KeccakStark { let reg_a_limb = reg_a(x, y) + is_high_limb; let a_limb = row[reg_a_limb].to_canonical_u64() as u32; let a_bit = F::from_bool(((a_limb >> bit_in_limb) & 1) != 0); - row[reg_a_prime(x, y, z)] = xor([ - a_bit, - row[reg_c((x + 4) % 5, z)], - row[reg_c((x + 1) % 5, (z + 64 - 1) % 64)], - ]); + row[reg_a_prime(x, y, z)] = + xor([a_bit, row[reg_c(x, z)], row[reg_c_prime(x, z)]]); } } } From d0be79e822546cb47fb758fa65b03a3a61131101 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 23:35:38 -0700 Subject: [PATCH 41/95] Feedback --- evm/src/cpu/kernel/global_metadata.rs | 2 +- evm/src/generation/mod.rs | 16 ++++++++-------- evm/src/proof.rs | 6 ++++-- evm/src/recursive_verifier.rs | 14 ++++++++++---- evm/src/util.rs | 2 +- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/evm/src/cpu/kernel/global_metadata.rs b/evm/src/cpu/kernel/global_metadata.rs index b6daf7e6..ddc3c839 100644 --- a/evm/src/cpu/kernel/global_metadata.rs +++ b/evm/src/cpu/kernel/global_metadata.rs @@ -18,7 +18,7 @@ pub(crate) enum GlobalMetadata { TransactionTrieRoot = 5, /// A pointer to the root of the receipt trie within the `TrieData` buffer. ReceiptTrieRoot = 6, - /// The number of storage tries involved in this transaction. I.e. the number of values in + /// The number of storage tries involved in these transactions. I.e. the number of values in /// `StorageTrieAddresses`, `StorageTriePointers` and `StorageTrieCheckpointPointers`. NumStorageTries = 7, diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 7b54ea29..67b65c31 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -23,20 +23,20 @@ pub(crate) mod state; pub struct GenerationInputs { pub signed_txns: Vec>, - /// A partial version of the state trie prior to this transaction. It should include all nodes - /// that will be accessed by this transaction. + /// A partial version of the state trie prior to these transactions. It should include all nodes + /// that will be accessed by these transactions. pub state_trie: PartialTrie, - /// A partial version of the transaction trie prior to this transaction. It should include all - /// nodes that will be accessed by this transaction. + /// A partial version of the transaction trie prior to these transactions. It should include all + /// nodes that will be accessed by these transactions. pub transactions_trie: PartialTrie, - /// A partial version of the receipt trie prior to this transaction. It should include all nodes - /// that will be accessed by this transaction. + /// A partial version of the receipt trie prior to these transactions. It should include all nodes + /// that will be accessed by these transactions. pub receipts_trie: PartialTrie, - /// A partial version of each storage trie prior to this transaction. It should include all - /// storage tries, and nodes therein, that will be accessed by this transaction. + /// A partial version of each storage trie prior to these transactions. It should include all + /// storage tries, and nodes therein, that will be accessed by these transactions. pub storage_tries: Vec<(Address, PartialTrie)>, pub block_metadata: BlockMetadata, diff --git a/evm/src/proof.rs b/evm/src/proof.rs index 6cb47d33..81512c78 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -65,12 +65,13 @@ pub struct TrieRoots { #[derive(Debug, Clone, Default)] pub struct BlockMetadata { - pub block_coinbase: Address, + pub block_beneficiary: Address, pub block_timestamp: U256, pub block_number: U256, pub block_difficulty: U256, pub block_gaslimit: U256, pub block_chain_id: U256, + pub block_base_fee: U256, } /// Memory values which are public. @@ -88,12 +89,13 @@ pub struct TrieRootsTarget { } pub struct BlockMetadataTarget { - pub block_coinbase: [Target; 5], + pub block_beneficiary: [Target; 5], pub block_timestamp: Target, pub block_number: Target, pub block_difficulty: Target, pub block_gaslimit: Target, pub block_chain_id: Target, + pub block_base_fee: Target, } pub(crate) struct AllProofChallengesTarget { diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index a86ff06e..217a07f7 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -355,19 +355,21 @@ pub fn add_virtual_trie_roots, const D: usize>( pub fn add_virtual_block_metadata, const D: usize>( builder: &mut CircuitBuilder, ) -> BlockMetadataTarget { - let block_coinbase = builder.add_virtual_target_arr(); + let block_beneficiary = builder.add_virtual_target_arr(); let block_timestamp = builder.add_virtual_target(); let block_number = builder.add_virtual_target(); let block_difficulty = builder.add_virtual_target(); let block_gaslimit = builder.add_virtual_target(); let block_chain_id = builder.add_virtual_target(); + let block_base_fee = builder.add_virtual_target(); BlockMetadataTarget { - block_coinbase, + block_beneficiary, block_timestamp, block_number, block_difficulty, block_gaslimit, block_chain_id, + block_base_fee, } } @@ -524,8 +526,8 @@ pub fn set_block_metadata_target( W: Witness, { witness.set_target_arr( - block_metadata_target.block_coinbase, - h160_limbs(block_metadata.block_coinbase), + block_metadata_target.block_beneficiary, + h160_limbs(block_metadata.block_beneficiary), ); witness.set_target( block_metadata_target.block_timestamp, @@ -547,4 +549,8 @@ pub fn set_block_metadata_target( block_metadata_target.block_chain_id, F::from_canonical_u64(block_metadata.block_chain_id.as_u64()), ); + witness.set_target( + block_metadata_target.block_base_fee, + F::from_canonical_u64(block_metadata.block_base_fee.as_u64()), + ); } diff --git a/evm/src/util.rs b/evm/src/util.rs index 5b75c999..ae5281db 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -47,7 +47,7 @@ pub(crate) fn u256_limbs(u256: U256) -> [F; 8] { u256.0 .into_iter() .flat_map(|limb_64| { - let lo = (limb_64 & 0xFFFFFFFF) as u32; + let lo = limb_64 as u32; let hi = (limb_64 >> 32) as u32; [lo, hi] }) From a4300758b4a4edce59b90d4106a65debffc29f44 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Aug 2022 23:38:39 -0700 Subject: [PATCH 42/95] Fix test --- evm/tests/transfer_to_new_addr.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index 50099816..ecb71076 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -21,14 +21,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); - let block_metadata = BlockMetadata { - block_coinbase: Default::default(), - block_timestamp: Default::default(), - block_number: Default::default(), - block_difficulty: Default::default(), - block_gaslimit: Default::default(), - block_chain_id: Default::default(), - }; + let block_metadata = BlockMetadata::default(); let txn = hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38"); From 05c3c4d9076e9c6fa0ba24861cd80468f9eb5d32 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 26 Aug 2022 10:12:45 +0200 Subject: [PATCH 43/95] First pass --- evm/src/all_stark.rs | 24 +++++++-------------- evm/src/cross_table_lookup.rs | 30 ++++++++++++-------------- evm/src/generation/mod.rs | 13 +++++++++--- evm/src/get_challenges.rs | 40 +++++++++++++++++------------------ evm/src/proof.rs | 23 ++++++++------------ evm/src/prover.rs | 10 +++------ evm/src/recursive_verifier.rs | 3 +-- 7 files changed, 64 insertions(+), 79 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 68ffe86f..15d0a6f8 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -41,28 +41,24 @@ impl, const D: usize> Default for AllStark { } impl, const D: usize> AllStark { - pub(crate) fn nums_permutation_zs(&self, config: &StarkConfig) -> Vec { - let ans = vec![ + pub(crate) fn nums_permutation_zs(&self, config: &StarkConfig) -> [usize; NUM_TABLES] { + [ self.cpu_stark.num_permutation_batches(config), self.keccak_stark.num_permutation_batches(config), self.keccak_memory_stark.num_permutation_batches(config), self.logic_stark.num_permutation_batches(config), self.memory_stark.num_permutation_batches(config), - ]; - debug_assert_eq!(ans.len(), Table::num_tables()); - ans + ] } - pub(crate) fn permutation_batch_sizes(&self) -> Vec { - let ans = vec![ + pub(crate) fn permutation_batch_sizes(&self) -> [usize; NUM_TABLES] { + [ self.cpu_stark.permutation_batch_size(), self.keccak_stark.permutation_batch_size(), self.keccak_memory_stark.permutation_batch_size(), self.logic_stark.permutation_batch_size(), self.memory_stark.permutation_batch_size(), - ]; - debug_assert_eq!(ans.len(), Table::num_tables()); - ans + ] } } @@ -75,11 +71,7 @@ pub enum Table { Memory = 4, } -impl Table { - pub(crate) fn num_tables() -> usize { - Table::Memory as usize + 1 - } -} +pub(crate) const NUM_TABLES: usize = Table::Memory as usize + 1; #[allow(unused)] // TODO: Should be used soon. pub(crate) fn all_cross_table_lookups() -> Vec> { @@ -695,7 +687,7 @@ mod tests { &mut memory_trace, ); - let traces = vec![ + let traces = [ cpu_trace, keccak_trace, keccak_memory_trace, diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index e4c2cccb..37a8b5a6 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -13,7 +13,7 @@ use plonky2::iop::target::Target; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::config::GenericConfig; -use crate::all_stark::Table; +use crate::all_stark::{Table, NUM_TABLES}; use crate::config::StarkConfig; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::permutation::{ @@ -216,12 +216,12 @@ impl CtlData { pub fn cross_table_lookup_data, const D: usize>( config: &StarkConfig, - trace_poly_values: &[Vec>], + trace_poly_values: &[Vec>; NUM_TABLES], cross_table_lookups: &[CrossTableLookup], challenger: &mut Challenger, -) -> Vec> { +) -> [CtlData; NUM_TABLES] { let challenges = get_grand_product_challenge_set(challenger, config.num_challenges); - let mut ctl_data_per_table = vec![CtlData::default(); trace_poly_values.len()]; + let mut ctl_data_per_table = [0; NUM_TABLES].map(|_| CtlData::default()); for CrossTableLookup { looking_tables, looked_table, @@ -337,12 +337,11 @@ impl<'a, F: RichField + Extendable, const D: usize> CtlCheckVars<'a, F, F::Extension, F::Extension, D> { pub(crate) fn from_proofs>( - proofs: &[StarkProof], + proofs: &[StarkProof; NUM_TABLES], cross_table_lookups: &'a [CrossTableLookup], ctl_challenges: &'a GrandProductChallengeSet, - num_permutation_zs: &[usize], - ) -> Vec> { - debug_assert_eq!(proofs.len(), num_permutation_zs.len()); + num_permutation_zs: &[usize; NUM_TABLES], + ) -> [Vec; NUM_TABLES] { let mut ctl_zs = proofs .iter() .zip(num_permutation_zs) @@ -354,7 +353,7 @@ impl<'a, F: RichField + Extendable, const D: usize> }) .collect::>(); - let mut ctl_vars_per_table = vec![vec![]; proofs.len()]; + let mut ctl_vars_per_table = [0; NUM_TABLES].map(|_| vec![]); for CrossTableLookup { looking_tables, looked_table, @@ -441,12 +440,11 @@ pub struct CtlCheckVarsTarget<'a, F: Field, const D: usize> { impl<'a, F: Field, const D: usize> CtlCheckVarsTarget<'a, F, D> { pub(crate) fn from_proofs( - proofs: &[StarkProofTarget], + proofs: &[StarkProofTarget; NUM_TABLES], cross_table_lookups: &'a [CrossTableLookup], ctl_challenges: &'a GrandProductChallengeSet, - num_permutation_zs: &[usize], - ) -> Vec> { - debug_assert_eq!(proofs.len(), num_permutation_zs.len()); + num_permutation_zs: &[usize; NUM_TABLES], + ) -> [Vec; NUM_TABLES] { let mut ctl_zs = proofs .iter() .zip(num_permutation_zs) @@ -458,7 +456,7 @@ impl<'a, F: Field, const D: usize> CtlCheckVarsTarget<'a, F, D> { }) .collect::>(); - let mut ctl_vars_per_table = vec![vec![]; proofs.len()]; + let mut ctl_vars_per_table = [0; NUM_TABLES].map(|_| vec![]); for CrossTableLookup { looking_tables, looked_table, @@ -559,7 +557,7 @@ pub(crate) fn verify_cross_table_lookups< const D: usize, >( cross_table_lookups: Vec>, - proofs: &[StarkProof], + proofs: &[StarkProof; NUM_TABLES], challenges: GrandProductChallengeSet, config: &StarkConfig, ) -> Result<()> { @@ -617,7 +615,7 @@ pub(crate) fn verify_cross_table_lookups_circuit< >( builder: &mut CircuitBuilder, cross_table_lookups: Vec>, - proofs: &[StarkProofTarget], + proofs: &[StarkProofTarget; NUM_TABLES], challenges: GrandProductChallengeSet, inner_config: &StarkConfig, ) { diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 67b65c31..5a1d0786 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -4,7 +4,7 @@ use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; -use crate::all_stark::AllStark; +use crate::all_stark::{AllStark, NUM_TABLES}; use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; use crate::cpu::columns::NUM_CPU_COLUMNS; use crate::cpu::kernel::global_metadata::GlobalMetadata; @@ -45,7 +45,7 @@ pub struct GenerationInputs { pub(crate) fn generate_traces, const D: usize>( all_stark: &AllStark, inputs: GenerationInputs, -) -> (Vec>>, PublicValues) { +) -> ([Vec>; NUM_TABLES], PublicValues) { let mut state = GenerationState::::default(); generate_bootstrap_kernel::(&mut state); @@ -90,9 +90,16 @@ pub(crate) fn generate_traces, const D: usize>( let cpu_trace = trace_rows_to_poly_values(cpu_rows); let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); + let keccak_memory_trace = all_stark.keccak_memory_stark.generate_trace(vec![], 16); // TODO let logic_trace = all_stark.logic_stark.generate_trace(logic_ops); let memory_trace = all_stark.memory_stark.generate_trace(memory.log); - let traces = vec![cpu_trace, keccak_trace, logic_trace, memory_trace]; + let traces = [ + cpu_trace, + keccak_trace, + keccak_memory_trace, + logic_trace, + memory_trace, + ]; let public_values = PublicValues { trie_roots_before, diff --git a/evm/src/get_challenges.rs b/evm/src/get_challenges.rs index 52c2b796..6545a1af 100644 --- a/evm/src/get_challenges.rs +++ b/evm/src/get_challenges.rs @@ -1,4 +1,3 @@ -use itertools::izip; use plonky2::field::extension::Extendable; use plonky2::fri::proof::{FriProof, FriProofTarget}; use plonky2::hash::hash_types::RichField; @@ -32,16 +31,18 @@ impl, C: GenericConfig, const D: usize> A let ctl_challenges = get_grand_product_challenge_set(&mut challenger, config.num_challenges); + let num_permutation_zs = all_stark.nums_permutation_zs(config); + let num_permutation_batch_sizes = all_stark.permutation_batch_sizes(); + AllProofChallenges { - stark_challenges: izip!( - &self.stark_proofs, - all_stark.nums_permutation_zs(config), - all_stark.permutation_batch_sizes() - ) - .map(|(proof, num_perm, batch_size)| { - proof.get_challenges(&mut challenger, num_perm > 0, batch_size, config) - }) - .collect(), + stark_challenges: std::array::from_fn(|i| { + self.stark_proofs[i].get_challenges( + &mut challenger, + num_permutation_zs[i] > 0, + num_permutation_batch_sizes[i], + config, + ) + }), ctl_challenges, } } @@ -66,22 +67,19 @@ impl AllProofTarget { let ctl_challenges = get_grand_product_challenge_set_target(builder, &mut challenger, config.num_challenges); + let num_permutation_zs = all_stark.nums_permutation_zs(config); + let num_permutation_batch_sizes = all_stark.permutation_batch_sizes(); + AllProofChallengesTarget { - stark_challenges: izip!( - &self.stark_proofs, - all_stark.nums_permutation_zs(config), - all_stark.permutation_batch_sizes() - ) - .map(|(proof, num_perm, batch_size)| { - proof.get_challenges::( + stark_challenges: std::array::from_fn(|i| { + self.stark_proofs[i].get_challenges::( builder, &mut challenger, - num_perm > 0, - batch_size, + num_permutation_zs[i] > 0, + num_permutation_batch_sizes[i], config, ) - }) - .collect(), + }), ctl_challenges, } } diff --git a/evm/src/proof.rs b/evm/src/proof.rs index 81512c78..a5bc61a7 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -13,38 +13,33 @@ use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::target::Target; use plonky2::plonk::config::GenericConfig; +use crate::all_stark::NUM_TABLES; use crate::config::StarkConfig; use crate::permutation::GrandProductChallengeSet; #[derive(Debug, Clone)] pub struct AllProof, C: GenericConfig, const D: usize> { - pub stark_proofs: Vec>, + pub stark_proofs: [StarkProof; NUM_TABLES], pub public_values: PublicValues, } impl, C: GenericConfig, const D: usize> AllProof { - pub fn degree_bits(&self, config: &StarkConfig) -> Vec { - self.stark_proofs - .iter() - .map(|proof| proof.recover_degree_bits(config)) - .collect() + pub fn degree_bits(&self, config: &StarkConfig) -> [usize; NUM_TABLES] { + std::array::from_fn(|i| self.stark_proofs[i].recover_degree_bits(config)) } - pub fn nums_ctl_zs(&self) -> Vec { - self.stark_proofs - .iter() - .map(|proof| proof.openings.ctl_zs_last.len()) - .collect() + pub fn nums_ctl_zs(&self) -> [usize; NUM_TABLES] { + std::array::from_fn(|i| self.stark_proofs[i].openings.ctl_zs_last.len()) } } pub(crate) struct AllProofChallenges, const D: usize> { - pub stark_challenges: Vec>, + pub stark_challenges: [StarkProofChallenges; NUM_TABLES], pub ctl_challenges: GrandProductChallengeSet, } pub struct AllProofTarget { - pub stark_proofs: Vec>, + pub stark_proofs: [StarkProofTarget; NUM_TABLES], pub public_values: PublicValuesTarget, } @@ -99,7 +94,7 @@ pub struct BlockMetadataTarget { } pub(crate) struct AllProofChallengesTarget { - pub stark_challenges: Vec>, + pub stark_challenges: [StarkProofChallengesTarget; NUM_TABLES], pub ctl_challenges: GrandProductChallengeSet, } diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 75152d61..5a23297a 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -17,7 +17,7 @@ use plonky2::util::timing::TimingTree; use plonky2::util::transpose; use plonky2_util::{log2_ceil, log2_strict}; -use crate::all_stark::{AllStark, Table}; +use crate::all_stark::{AllStark, Table, NUM_TABLES}; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; @@ -61,7 +61,7 @@ where pub(crate) fn prove_with_traces( all_stark: &AllStark, config: &StarkConfig, - trace_poly_values: Vec>>, + trace_poly_values: [Vec>; NUM_TABLES], public_values: PublicValues, timing: &mut TimingTree, ) -> Result> @@ -75,9 +75,6 @@ where [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, { - let num_starks = Table::num_tables(); - debug_assert_eq!(num_starks, trace_poly_values.len()); - let rate_bits = config.fri_config.rate_bits; let cap_height = config.fri_config.cap_height; @@ -163,14 +160,13 @@ where timing, )?; - let stark_proofs = vec![ + let stark_proofs = [ cpu_proof, keccak_proof, keccak_memory_proof, logic_proof, memory_proof, ]; - debug_assert_eq!(stark_proofs.len(), num_starks); Ok(AllProof { stark_proofs, diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index 217a07f7..800ee461 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -280,7 +280,7 @@ pub fn add_virtual_all_proof, const D: usize>( degree_bits: &[usize], nums_ctl_zs: &[usize], ) -> AllProofTarget { - let stark_proofs = vec![ + let stark_proofs = [ add_virtual_stark_proof( builder, all_stark.cpu_stark, @@ -317,7 +317,6 @@ pub fn add_virtual_all_proof, const D: usize>( nums_ctl_zs[Table::Memory as usize], ), ]; - assert_eq!(stark_proofs.len(), Table::num_tables()); let public_values = add_virtual_public_values(builder); AllProofTarget { From a1941308eb9ea3eed3785767349d44b57f5486a2 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 26 Aug 2022 11:07:16 +0200 Subject: [PATCH 44/95] Minor --- evm/src/generation/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 5a1d0786..5803d12c 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -90,7 +90,7 @@ pub(crate) fn generate_traces, const D: usize>( let cpu_trace = trace_rows_to_poly_values(cpu_rows); let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); - let keccak_memory_trace = all_stark.keccak_memory_stark.generate_trace(vec![], 16); // TODO + let keccak_memory_trace = all_stark.keccak_memory_stark.generate_trace(vec![], 0); // TODO let logic_trace = all_stark.logic_stark.generate_trace(logic_ops); let memory_trace = all_stark.memory_stark.generate_trace(memory.log); let traces = [ From e7edfdd6a30d08fb7a0b2702c8b3795c9a126036 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 26 Aug 2022 18:30:26 +0200 Subject: [PATCH 45/95] Minor --- evm/src/generation/mod.rs | 7 ++++++- evm/src/prover.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 5803d12c..5b0b3c8f 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -5,6 +5,7 @@ use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use crate::all_stark::{AllStark, NUM_TABLES}; +use crate::config::StarkConfig; use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; use crate::cpu::columns::NUM_CPU_COLUMNS; use crate::cpu::kernel::global_metadata::GlobalMetadata; @@ -45,6 +46,7 @@ pub struct GenerationInputs { pub(crate) fn generate_traces, const D: usize>( all_stark: &AllStark, inputs: GenerationInputs, + config: &StarkConfig, ) -> ([Vec>; NUM_TABLES], PublicValues) { let mut state = GenerationState::::default(); @@ -83,6 +85,7 @@ pub(crate) fn generate_traces, const D: usize>( current_cpu_row, memory, keccak_inputs, + keccak_memory_inputs, logic_ops, .. } = state; @@ -90,7 +93,9 @@ pub(crate) fn generate_traces, const D: usize>( let cpu_trace = trace_rows_to_poly_values(cpu_rows); let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); - let keccak_memory_trace = all_stark.keccak_memory_stark.generate_trace(vec![], 0); // TODO + let keccak_memory_trace = all_stark + .keccak_memory_stark + .generate_trace(keccak_memory_inputs, 1 << config.fri_config.cap_height); let logic_trace = all_stark.logic_stark.generate_trace(logic_ops); let memory_trace = all_stark.memory_stark.generate_trace(memory.log); let traces = [ diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 5a23297a..31e76a1c 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -53,7 +53,7 @@ where [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, { - let (traces, public_values) = generate_traces(all_stark, inputs); + let (traces, public_values) = generate_traces(all_stark, inputs, config); prove_with_traces(all_stark, config, traces, public_values, timing) } From 8aa3ed0997b36df4f1f80cd6c348ba98d99da153 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Fri, 26 Aug 2022 16:10:34 -0400 Subject: [PATCH 46/95] cleaner witness extension --- ecdsa/src/gadgets/biguint.rs | 82 ++++++++++++++------------- ecdsa/src/gadgets/curve_fixed_base.rs | 4 +- ecdsa/src/gadgets/glv.rs | 9 ++- ecdsa/src/gadgets/nonnative.rs | 44 ++++++-------- u32/src/gadgets/arithmetic_u32.rs | 6 +- u32/src/witness.rs | 37 +++++++----- 6 files changed, 93 insertions(+), 89 deletions(-) diff --git a/ecdsa/src/gadgets/biguint.rs b/ecdsa/src/gadgets/biguint.rs index 1dbe4657..188b04ba 100644 --- a/ecdsa/src/gadgets/biguint.rs +++ b/ecdsa/src/gadgets/biguint.rs @@ -7,10 +7,10 @@ use plonky2::iop::target::{BoolTarget, Target}; use plonky2::iop::witness::{PartitionWitness, Witness}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2_field::extension::Extendable; -use plonky2_field::types::PrimeField; +use plonky2_field::types::{PrimeField, PrimeField64}; use plonky2_u32::gadgets::arithmetic_u32::{CircuitBuilderU32, U32Target}; use plonky2_u32::gadgets::multiple_comparison::list_le_u32_circuit; -use plonky2_u32::witness::{generated_values_set_u32_target, witness_set_u32_target}; +use plonky2_u32::witness::{GeneratedValuesU32, WitnessU32}; #[derive(Clone, Debug)] pub struct BigUintTarget { @@ -270,41 +270,43 @@ impl, const D: usize> CircuitBuilderBiguint } } -pub fn witness_get_biguint_target, F: PrimeField>( - witness: &W, - bt: BigUintTarget, -) -> BigUint { - bt.limbs +pub trait WitnessBigUint: Witness { + fn get_biguint_target(&self, target: BigUintTarget) -> BigUint; + fn set_biguint_target(&mut self, target: &BigUintTarget, value: &BigUint); +} + +impl, F: PrimeField64> WitnessBigUint for T { + fn get_biguint_target(&self, target: BigUintTarget) -> BigUint { + target.limbs .into_iter() .rev() .fold(BigUint::zero(), |acc, limb| { - (acc << 32) + witness.get_target(limb.0).to_canonical_biguint() + (acc << 32) + self.get_target(limb.0).to_canonical_biguint() }) -} + } -pub fn witness_set_biguint_target, F: PrimeField>( - witness: &mut W, - target: &BigUintTarget, - value: &BigUint, -) { - let mut limbs = value.to_u32_digits(); - assert!(target.num_limbs() >= limbs.len()); - limbs.resize(target.num_limbs(), 0); - for i in 0..target.num_limbs() { - witness_set_u32_target(witness, target.limbs[i], limbs[i]); + fn set_biguint_target(&mut self, target: &BigUintTarget, value: &BigUint) { + let mut limbs = value.to_u32_digits(); + assert!(target.num_limbs() >= limbs.len()); + limbs.resize(target.num_limbs(), 0); + for i in 0..target.num_limbs() { + self.set_u32_target(target.limbs[i], limbs[i]); + } } } -pub fn buffer_set_biguint_target( - buffer: &mut GeneratedValues, - target: &BigUintTarget, - value: &BigUint, -) { - let mut limbs = value.to_u32_digits(); - assert!(target.num_limbs() >= limbs.len()); - limbs.resize(target.num_limbs(), 0); - for i in 0..target.num_limbs() { - generated_values_set_u32_target(buffer, target.get_limb(i), limbs[i]); +pub trait GeneratedValuesBigUint { + fn set_biguint_target(&mut self, target: &BigUintTarget, value: &BigUint); +} + +impl GeneratedValuesBigUint for GeneratedValues { + fn set_biguint_target(&mut self, target: &BigUintTarget, value: &BigUint) { + let mut limbs = value.to_u32_digits(); + assert!(target.num_limbs() >= limbs.len()); + limbs.resize(target.num_limbs(), 0); + for i in 0..target.num_limbs() { + self.set_u32_target(target.get_limb(i), limbs[i]); + } } } @@ -330,12 +332,12 @@ impl, const D: usize> SimpleGenerator } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = witness_get_biguint_target(witness, self.a.clone()); - let b = witness_get_biguint_target(witness, self.b.clone()); + let a = witness.get_biguint_target(self.a.clone()); + let b = witness.get_biguint_target(self.b.clone()); let (div, rem) = a.div_rem(&b); - buffer_set_biguint_target(out_buffer, &self.div, &div); - buffer_set_biguint_target(out_buffer, &self.rem, &rem); + out_buffer.set_biguint_target(&self.div, &div); + out_buffer.set_biguint_target(&self.rem, &rem); } } @@ -350,7 +352,7 @@ mod tests { }; use rand::Rng; - use crate::gadgets::biguint::{witness_set_biguint_target, CircuitBuilderBiguint}; + use crate::gadgets::biguint::{WitnessBigUint, CircuitBuilderBiguint}; #[test] fn test_biguint_add() -> Result<()> { @@ -373,9 +375,9 @@ mod tests { let expected_z = builder.add_virtual_biguint_target(expected_z_value.to_u32_digits().len()); builder.connect_biguint(&z, &expected_z); - witness_set_biguint_target(&mut pw, &x, &x_value); - witness_set_biguint_target(&mut pw, &y, &y_value); - witness_set_biguint_target(&mut pw, &expected_z, &expected_z_value); + pw.set_biguint_target(&x, &x_value); + pw.set_biguint_target(&y, &y_value); + pw.set_biguint_target(&expected_z, &expected_z_value); let data = builder.build::(); let proof = data.prove(pw).unwrap(); @@ -433,9 +435,9 @@ mod tests { let expected_z = builder.add_virtual_biguint_target(expected_z_value.to_u32_digits().len()); builder.connect_biguint(&z, &expected_z); - witness_set_biguint_target(&mut pw, &x, &x_value); - witness_set_biguint_target(&mut pw, &y, &y_value); - witness_set_biguint_target(&mut pw, &expected_z, &expected_z_value); + pw.set_biguint_target(&x, &x_value); + pw.set_biguint_target(&y, &y_value); + pw.set_biguint_target(&expected_z, &expected_z_value); let data = builder.build::(); let proof = data.prove(pw).unwrap(); diff --git a/ecdsa/src/gadgets/curve_fixed_base.rs b/ecdsa/src/gadgets/curve_fixed_base.rs index 44dc9488..0fd8e841 100644 --- a/ecdsa/src/gadgets/curve_fixed_base.rs +++ b/ecdsa/src/gadgets/curve_fixed_base.rs @@ -76,7 +76,7 @@ mod tests { use crate::curve::curve_types::{Curve, CurveScalar}; use crate::curve::secp256k1::Secp256K1; - use crate::gadgets::biguint::witness_set_biguint_target; + use crate::gadgets::biguint::WitnessBigUint; use crate::gadgets::curve::CircuitBuilderCurve; use crate::gadgets::curve_fixed_base::fixed_base_curve_mul_circuit; use crate::gadgets::nonnative::CircuitBuilderNonNative; @@ -101,7 +101,7 @@ mod tests { builder.curve_assert_valid(&res_expected); let n_target = builder.add_virtual_nonnative_target::(); - witness_set_biguint_target(&mut pw, &n_target.value, &n.to_canonical_biguint()); + pw.set_biguint_target(&n_target.value, &n.to_canonical_biguint()); let res_target = fixed_base_curve_mul_circuit(&mut builder, g, &n_target); builder.curve_assert_valid(&res_target); diff --git a/ecdsa/src/gadgets/glv.rs b/ecdsa/src/gadgets/glv.rs index 8e62e906..2d86652c 100644 --- a/ecdsa/src/gadgets/glv.rs +++ b/ecdsa/src/gadgets/glv.rs @@ -12,7 +12,7 @@ use plonky2_field::types::{Field, PrimeField}; use crate::curve::glv::{decompose_secp256k1_scalar, GLV_BETA, GLV_S}; use crate::curve::secp256k1::Secp256K1; -use crate::gadgets::biguint::{buffer_set_biguint_target, witness_get_biguint_target}; +use crate::gadgets::biguint::{GeneratedValuesBigUint, WitnessBigUint}; use crate::gadgets::curve::{AffinePointTarget, CircuitBuilderCurve}; use crate::gadgets::curve_msm::curve_msm_circuit; use crate::gadgets::nonnative::{CircuitBuilderNonNative, NonNativeTarget}; @@ -116,15 +116,14 @@ impl, const D: usize> SimpleGenerator } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let k = Secp256K1Scalar::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let k = Secp256K1Scalar::from_noncanonical_biguint(witness.get_biguint_target( self.k.value.clone(), )); let (k1, k2, k1_neg, k2_neg) = decompose_secp256k1_scalar(k); - buffer_set_biguint_target(out_buffer, &self.k1.value, &k1.to_canonical_biguint()); - buffer_set_biguint_target(out_buffer, &self.k2.value, &k2.to_canonical_biguint()); + out_buffer.set_biguint_target(&self.k1.value, &k1.to_canonical_biguint()); + out_buffer.set_biguint_target(&self.k2.value, &k2.to_canonical_biguint()); out_buffer.set_bool_target(self.k1_neg, k1_neg); out_buffer.set_bool_target(self.k2_neg, k2_neg); } diff --git a/ecdsa/src/gadgets/nonnative.rs b/ecdsa/src/gadgets/nonnative.rs index 393aac75..db1231bc 100644 --- a/ecdsa/src/gadgets/nonnative.rs +++ b/ecdsa/src/gadgets/nonnative.rs @@ -10,11 +10,11 @@ use plonky2_field::types::PrimeField; use plonky2_field::{extension::Extendable, types::Field}; use plonky2_u32::gadgets::arithmetic_u32::{CircuitBuilderU32, U32Target}; use plonky2_u32::gadgets::range_check::range_check_u32_circuit; -use plonky2_u32::witness::generated_values_set_u32_target; +use plonky2_u32::witness::GeneratedValuesU32; use plonky2_util::ceil_div_usize; use crate::gadgets::biguint::{ - buffer_set_biguint_target, witness_get_biguint_target, BigUintTarget, CircuitBuilderBiguint, + GeneratedValuesBigUint, WitnessBigUint, BigUintTarget, CircuitBuilderBiguint, }; #[derive(Clone, Debug)] @@ -467,12 +467,10 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let a = FF::from_noncanonical_biguint(witness.get_biguint_target( self.a.value.clone(), )); - let b = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let b = FF::from_noncanonical_biguint(witness.get_biguint_target( self.b.value.clone(), )); let a_biguint = a.to_canonical_biguint(); @@ -485,7 +483,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat (false, sum_biguint) }; - buffer_set_biguint_target(out_buffer, &self.sum.value, &sum_reduced); + out_buffer.set_biguint_target(&self.sum.value, &sum_reduced); out_buffer.set_bool_target(self.overflow, overflow); } } @@ -514,8 +512,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat .summands .iter() .map(|summand| { - FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + FF::from_noncanonical_biguint(witness.get_biguint_target( summand.value.clone(), )) }) @@ -533,8 +530,8 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat let (overflow_biguint, sum_reduced) = sum_biguint.div_rem(&modulus); let overflow = overflow_biguint.to_u64_digits()[0] as u32; - buffer_set_biguint_target(out_buffer, &self.sum.value, &sum_reduced); - generated_values_set_u32_target(out_buffer, self.overflow, overflow); + out_buffer.set_biguint_target(&self.sum.value, &sum_reduced); + out_buffer.set_u32_target(self.overflow, overflow); } } @@ -562,12 +559,10 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let a = FF::from_noncanonical_biguint(witness.get_biguint_target( self.a.value.clone(), )); - let b = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let b = FF::from_noncanonical_biguint(witness.get_biguint_target( self.b.value.clone(), )); let a_biguint = a.to_canonical_biguint(); @@ -580,7 +575,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat (modulus + a_biguint - b_biguint, true) }; - buffer_set_biguint_target(out_buffer, &self.diff.value, &diff_biguint); + out_buffer.set_biguint_target(&self.diff.value, &diff_biguint); out_buffer.set_bool_target(self.overflow, overflow); } } @@ -609,12 +604,10 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let a = FF::from_noncanonical_biguint(witness.get_biguint_target( self.a.value.clone(), )); - let b = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let b = FF::from_noncanonical_biguint(witness.get_biguint_target( self.b.value.clone(), )); let a_biguint = a.to_canonical_biguint(); @@ -625,8 +618,8 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat let modulus = FF::order(); let (overflow_biguint, prod_reduced) = prod_biguint.div_rem(&modulus); - buffer_set_biguint_target(out_buffer, &self.prod.value, &prod_reduced); - buffer_set_biguint_target(out_buffer, &self.overflow, &overflow_biguint); + out_buffer.set_biguint_target(&self.prod.value, &prod_reduced); + out_buffer.set_biguint_target(&self.overflow, &overflow_biguint); } } @@ -646,8 +639,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let x = FF::from_noncanonical_biguint(witness_get_biguint_target( - witness, + let x = FF::from_noncanonical_biguint(witness.get_biguint_target( self.x.value.clone(), )); let inv = x.inverse(); @@ -658,8 +650,8 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat let modulus = FF::order(); let (div, _rem) = prod.div_rem(&modulus); - buffer_set_biguint_target(out_buffer, &self.div, &div); - buffer_set_biguint_target(out_buffer, &self.inv, &inv_biguint); + out_buffer.set_biguint_target(&self.div, &div); + out_buffer.set_biguint_target(&self.inv, &inv_biguint); } } diff --git a/u32/src/gadgets/arithmetic_u32.rs b/u32/src/gadgets/arithmetic_u32.rs index 7a7731b1..7475681c 100644 --- a/u32/src/gadgets/arithmetic_u32.rs +++ b/u32/src/gadgets/arithmetic_u32.rs @@ -10,7 +10,7 @@ use plonky2_field::extension::Extendable; use crate::gates::add_many_u32::U32AddManyGate; use crate::gates::arithmetic_u32::U32ArithmeticGate; use crate::gates::subtraction_u32::U32SubtractionGate; -use crate::witness::generated_values_set_u32_target; +use crate::witness::GeneratedValuesU32; #[derive(Clone, Copy, Debug)] pub struct U32Target(pub Target); @@ -249,8 +249,8 @@ impl, const D: usize> SimpleGenerator let low = x_u64 as u32; let high = (x_u64 >> 32) as u32; - generated_values_set_u32_target(out_buffer, self.low, low); - generated_values_set_u32_target(out_buffer, self.high, high); + out_buffer.set_u32_target(self.low, low); + out_buffer.set_u32_target(self.high, high); } } diff --git a/u32/src/witness.rs b/u32/src/witness.rs index 1b88d60d..38aa2238 100644 --- a/u32/src/witness.rs +++ b/u32/src/witness.rs @@ -1,21 +1,32 @@ use plonky2::iop::generator::GeneratedValues; use plonky2::iop::witness::Witness; -use plonky2_field::types::Field; +use plonky2_field::types::{Field, PrimeField64}; use crate::gadgets::arithmetic_u32::U32Target; -pub fn generated_values_set_u32_target( - buffer: &mut GeneratedValues, - target: U32Target, - value: u32, -) { - buffer.set_target(target.0, F::from_canonical_u32(value)) +pub trait WitnessU32: Witness { + fn set_u32_target(&mut self, target: U32Target, value: u32); + fn get_u32_target(&self, target: U32Target) -> (u32, u32); } -pub fn witness_set_u32_target, F: Field>( - witness: &mut W, - target: U32Target, - value: u32, -) { - witness.set_target(target.0, F::from_canonical_u32(value)) +impl, F: PrimeField64> WitnessU32 for T { + fn set_u32_target(&mut self, target: U32Target, value: u32) { + self.set_target(target.0, F::from_canonical_u32(value)); + } + fn get_u32_target(&self, target: U32Target) -> (u32, u32) { + let x_u64 = self.get_target(target.0).to_canonical_u64(); + let low = x_u64 as u32; + let high = (x_u64 >> 32) as u32; + (low, high) + } +} + +pub trait GeneratedValuesU32 { + fn set_u32_target(&mut self, target: U32Target, value: u32); +} + +impl GeneratedValuesU32 for GeneratedValues { + fn set_u32_target(&mut self, target: U32Target, value: u32) { + self.set_target(target.0, F::from_canonical_u32(value)) + } } From 356c7cd9255bdc6ab32e3c8dd4f1d7a35d45aeb2 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Fri, 26 Aug 2022 16:10:44 -0400 Subject: [PATCH 47/95] fmt --- ecdsa/src/gadgets/biguint.rs | 15 ++++++++------- ecdsa/src/gadgets/glv.rs | 6 +++--- ecdsa/src/gadgets/nonnative.rs | 34 +++++++++------------------------- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/ecdsa/src/gadgets/biguint.rs b/ecdsa/src/gadgets/biguint.rs index 188b04ba..faae365c 100644 --- a/ecdsa/src/gadgets/biguint.rs +++ b/ecdsa/src/gadgets/biguint.rs @@ -277,12 +277,13 @@ pub trait WitnessBigUint: Witness { impl, F: PrimeField64> WitnessBigUint for T { fn get_biguint_target(&self, target: BigUintTarget) -> BigUint { - target.limbs - .into_iter() - .rev() - .fold(BigUint::zero(), |acc, limb| { - (acc << 32) + self.get_target(limb.0).to_canonical_biguint() - }) + target + .limbs + .into_iter() + .rev() + .fold(BigUint::zero(), |acc, limb| { + (acc << 32) + self.get_target(limb.0).to_canonical_biguint() + }) } fn set_biguint_target(&mut self, target: &BigUintTarget, value: &BigUint) { @@ -352,7 +353,7 @@ mod tests { }; use rand::Rng; - use crate::gadgets::biguint::{WitnessBigUint, CircuitBuilderBiguint}; + use crate::gadgets::biguint::{CircuitBuilderBiguint, WitnessBigUint}; #[test] fn test_biguint_add() -> Result<()> { diff --git a/ecdsa/src/gadgets/glv.rs b/ecdsa/src/gadgets/glv.rs index 2d86652c..4302023e 100644 --- a/ecdsa/src/gadgets/glv.rs +++ b/ecdsa/src/gadgets/glv.rs @@ -116,9 +116,9 @@ impl, const D: usize> SimpleGenerator } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let k = Secp256K1Scalar::from_noncanonical_biguint(witness.get_biguint_target( - self.k.value.clone(), - )); + let k = Secp256K1Scalar::from_noncanonical_biguint( + witness.get_biguint_target(self.k.value.clone()), + ); let (k1, k2, k1_neg, k2_neg) = decompose_secp256k1_scalar(k); diff --git a/ecdsa/src/gadgets/nonnative.rs b/ecdsa/src/gadgets/nonnative.rs index db1231bc..c6ff4753 100644 --- a/ecdsa/src/gadgets/nonnative.rs +++ b/ecdsa/src/gadgets/nonnative.rs @@ -14,7 +14,7 @@ use plonky2_u32::witness::GeneratedValuesU32; use plonky2_util::ceil_div_usize; use crate::gadgets::biguint::{ - GeneratedValuesBigUint, WitnessBigUint, BigUintTarget, CircuitBuilderBiguint, + BigUintTarget, CircuitBuilderBiguint, GeneratedValuesBigUint, WitnessBigUint, }; #[derive(Clone, Debug)] @@ -467,12 +467,8 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.a.value.clone(), - )); - let b = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.b.value.clone(), - )); + let a = FF::from_noncanonical_biguint(witness.get_biguint_target(self.a.value.clone())); + let b = FF::from_noncanonical_biguint(witness.get_biguint_target(self.b.value.clone())); let a_biguint = a.to_canonical_biguint(); let b_biguint = b.to_canonical_biguint(); let sum_biguint = a_biguint + b_biguint; @@ -512,9 +508,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat .summands .iter() .map(|summand| { - FF::from_noncanonical_biguint(witness.get_biguint_target( - summand.value.clone(), - )) + FF::from_noncanonical_biguint(witness.get_biguint_target(summand.value.clone())) }) .collect(); let summand_biguints: Vec<_> = summands @@ -559,12 +553,8 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.a.value.clone(), - )); - let b = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.b.value.clone(), - )); + let a = FF::from_noncanonical_biguint(witness.get_biguint_target(self.a.value.clone())); + let b = FF::from_noncanonical_biguint(witness.get_biguint_target(self.b.value.clone())); let a_biguint = a.to_canonical_biguint(); let b_biguint = b.to_canonical_biguint(); @@ -604,12 +594,8 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let a = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.a.value.clone(), - )); - let b = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.b.value.clone(), - )); + let a = FF::from_noncanonical_biguint(witness.get_biguint_target(self.a.value.clone())); + let b = FF::from_noncanonical_biguint(witness.get_biguint_target(self.b.value.clone())); let a_biguint = a.to_canonical_biguint(); let b_biguint = b.to_canonical_biguint(); @@ -639,9 +625,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let x = FF::from_noncanonical_biguint(witness.get_biguint_target( - self.x.value.clone(), - )); + let x = FF::from_noncanonical_biguint(witness.get_biguint_target(self.x.value.clone())); let inv = x.inverse(); let x_biguint = x.to_canonical_biguint(); From f48de368a9f9c09d34734533f8bfb7b61cce493a Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Fri, 26 Aug 2022 16:39:39 -0500 Subject: [PATCH 48/95] Make jumps, logic, and syscalls read from/write to memory columns (#699) * Make jumps, logic, and syscalls read from/write to memory columns * Change CTL convention (outputs precede inputs) * Change convention so outputs follow inputs in memory channel order --- evm/src/all_stark.rs | 99 +++++++++++++++------------ evm/src/cpu/columns/general.rs | 38 ++-------- evm/src/cpu/cpu_stark.rs | 7 +- evm/src/cpu/jumps.rs | 39 ++++++----- evm/src/cpu/simple_logic/eq_iszero.rs | 45 +++++++----- evm/src/cpu/simple_logic/not.rs | 24 ++++--- evm/src/cpu/syscalls.rs | 18 ++--- 7 files changed, 137 insertions(+), 133 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 15d0a6f8..e8b44d23 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -318,45 +318,18 @@ mod tests { cpu_trace_rows.push(row.into()); } - for i in 0..num_logic_rows { + // Pad to `num_memory_ops` for memory testing. + for _ in cpu_trace_rows.len()..num_memory_ops { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); + row.opcode_bits = bits_from_opcode(0x5b); row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; - - // Since these are the first cycle rows, we must start with PC=route_txn then increment. - row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"] + i); - row.opcode_bits = bits_from_opcode( - if logic_trace[logic::columns::IS_AND].values[i] != F::ZERO { - 0x16 - } else if logic_trace[logic::columns::IS_OR].values[i] != F::ZERO { - 0x17 - } else if logic_trace[logic::columns::IS_XOR].values[i] != F::ZERO { - 0x18 - } else { - panic!() - }, - ); - - let logic = row.general.logic_mut(); - - let input0_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT0); - for (col_cpu, limb_cols_logic) in logic.input0.iter_mut().zip(input0_bit_cols) { - *col_cpu = limb_from_bits_le(limb_cols_logic.map(|col| logic_trace[col].values[i])); - } - - let input1_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT1); - for (col_cpu, limb_cols_logic) in logic.input1.iter_mut().zip(input1_bit_cols) { - *col_cpu = limb_from_bits_le(limb_cols_logic.map(|col| logic_trace[col].values[i])); - } - - for (col_cpu, col_logic) in logic.output.iter_mut().zip(logic::columns::RESULT) { - *col_cpu = logic_trace[col_logic].values[i]; - } - + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"]); cpu_stark.generate(row.borrow_mut()); cpu_trace_rows.push(row.into()); } + for i in 0..num_memory_ops { let mem_timestamp: usize = memory_trace[memory::columns::TIMESTAMP].values[i] .to_canonical_u64() @@ -388,6 +361,44 @@ mod tests { } } + for i in 0..num_logic_rows { + let mut row: cpu::columns::CpuColumnsView = + [F::ZERO; CpuStark::::COLUMNS].into(); + row.is_cpu_cycle = F::ONE; + row.is_kernel_mode = F::ONE; + + // Since these are the first cycle rows, we must start with PC=route_txn then increment. + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"] + i); + row.opcode_bits = bits_from_opcode( + if logic_trace[logic::columns::IS_AND].values[i] != F::ZERO { + 0x16 + } else if logic_trace[logic::columns::IS_OR].values[i] != F::ZERO { + 0x17 + } else if logic_trace[logic::columns::IS_XOR].values[i] != F::ZERO { + 0x18 + } else { + panic!() + }, + ); + + let input0_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT0); + for (col_cpu, limb_cols_logic) in row.mem_value[0].iter_mut().zip(input0_bit_cols) { + *col_cpu = limb_from_bits_le(limb_cols_logic.map(|col| logic_trace[col].values[i])); + } + + let input1_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT1); + for (col_cpu, limb_cols_logic) in row.mem_value[1].iter_mut().zip(input1_bit_cols) { + *col_cpu = limb_from_bits_le(limb_cols_logic.map(|col| logic_trace[col].values[i])); + } + + for (col_cpu, col_logic) in row.mem_value[2].iter_mut().zip(logic::columns::RESULT) { + *col_cpu = logic_trace[col_logic].values[i]; + } + + cpu_stark.generate(row.borrow_mut()); + cpu_trace_rows.push(row.into()); + } + // Trap to kernel { let mut row: cpu::columns::CpuColumnsView = @@ -398,7 +409,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x0a); // `EXP` is implemented in software row.is_kernel_mode = F::ONE; row.program_counter = last_row.program_counter + F::ONE; - row.general.syscalls_mut().output = [ + row.mem_value[0] = [ row.program_counter, F::ONE, F::ZERO, @@ -420,7 +431,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0xf9); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_usize(KERNEL.global_labels["sys_exp"]); - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(15682), F::ONE, F::ZERO, @@ -442,7 +453,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_u16(15682); - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(15106), F::ZERO, F::ZERO, @@ -452,7 +463,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.general.jumps_mut().input1 = [ + row.mem_value[1] = [ F::ONE, F::ZERO, F::ZERO, @@ -479,7 +490,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0xf9); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_u16(15106); - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(63064), F::ZERO, F::ZERO, @@ -501,7 +512,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(63064); - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(3754), F::ZERO, F::ZERO, @@ -511,7 +522,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.general.jumps_mut().input1 = [ + row.mem_value[1] = [ F::ONE, F::ZERO, F::ZERO, @@ -539,7 +550,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x57); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(3754); - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(37543), F::ZERO, F::ZERO, @@ -549,7 +560,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.general.jumps_mut().input1 = [ + row.mem_value[1] = [ F::ZERO, F::ZERO, F::ZERO, @@ -577,7 +588,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x57); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(37543); - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(37543), F::ZERO, F::ZERO, @@ -606,7 +617,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ZERO; row.program_counter = last_row.program_counter + F::ONE; - row.general.jumps_mut().input0 = [ + row.mem_value[0] = [ F::from_canonical_u16(37543), F::ZERO, F::ZERO, @@ -616,7 +627,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.general.jumps_mut().input1 = [ + row.mem_value[1] = [ F::ONE, F::ZERO, F::ZERO, diff --git a/evm/src/cpu/columns/general.rs b/evm/src/cpu/columns/general.rs index affd676d..43f987e9 100644 --- a/evm/src/cpu/columns/general.rs +++ b/evm/src/cpu/columns/general.rs @@ -9,7 +9,6 @@ pub(crate) union CpuGeneralColumnsView { arithmetic: CpuArithmeticView, logic: CpuLogicView, jumps: CpuJumpsView, - syscalls: CpuSyscallsView, } impl CpuGeneralColumnsView { @@ -52,16 +51,6 @@ impl CpuGeneralColumnsView { pub(crate) fn jumps_mut(&mut self) -> &mut CpuJumpsView { unsafe { &mut self.jumps } } - - // SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn syscalls(&self) -> &CpuSyscallsView { - unsafe { &self.syscalls } - } - - // SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn syscalls_mut(&mut self) -> &mut CpuSyscallsView { - unsafe { &mut self.syscalls } - } } impl PartialEq for CpuGeneralColumnsView { @@ -107,23 +96,16 @@ pub(crate) struct CpuArithmeticView { #[derive(Copy, Clone)] pub(crate) struct CpuLogicView { - // Assuming a limb size of 32 bits. - pub(crate) input0: [T; 8], - pub(crate) input1: [T; 8], - pub(crate) output: [T; 8], - - // Pseudoinverse of `(input0 - input1)`. Used prove that they are unequal. + // Pseudoinverse of `(input0 - input1)`. Used prove that they are unequal. Assumes 32-bit limbs. pub(crate) diff_pinv: [T; 8], } #[derive(Copy, Clone)] pub(crate) struct CpuJumpsView { - /// Assuming a limb size of 32 bits. - /// The top stack value at entry (for jumps, the address; for `EXIT_KERNEL`, the address and new - /// privilege level). - pub(crate) input0: [T; 8], - /// For `JUMPI`, the second stack value (the predicate). For `JUMP`, 1. - pub(crate) input1: [T; 8], + /// `input0` is `mem_value[0]`. It's the top stack value at entry (for jumps, the address; for + /// `EXIT_KERNEL`, the address and new privilege level). + /// `input1` is `mem_value[1]`. For `JUMPI`, it's the second stack value (the predicate). For + /// `JUMP`, 1. /// Inverse of `input0[1] + ... + input0[7]`, if one exists; otherwise, an arbitrary value. /// Needed to prove that `input0` is nonzero. @@ -162,15 +144,5 @@ pub(crate) struct CpuJumpsView { pub(crate) should_trap: T, } -#[derive(Copy, Clone)] -pub(crate) struct CpuSyscallsView { - /// Assuming a limb size of 32 bits. - /// The output contains the context that is required to from the system call in `EXIT_KERNEL`. - /// `output[0]` contains the program counter at the time the system call was made (the address - /// of the syscall instruction). `output[1]` is 1 if we were in kernel mode at the time and 0 - /// otherwise. `output[2]`, ..., `output[7]` are zero. - pub(crate) output: [T; 8], -} - // `u8` is guaranteed to have a `size_of` of 1. pub const NUM_SHARED_COLUMNS: usize = size_of::>(); diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 852b7b54..39518a43 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -48,10 +48,9 @@ pub fn ctl_filter_keccak_memory() -> Column { pub fn ctl_data_logic() -> Vec> { let mut res = Column::singles([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]).collect_vec(); - let logic = COL_MAP.general.logic(); - res.extend(Column::singles(logic.input0)); - res.extend(Column::singles(logic.input1)); - res.extend(Column::singles(logic.output)); + res.extend(Column::singles(COL_MAP.mem_value[0])); + res.extend(Column::singles(COL_MAP.mem_value[1])); + res.extend(Column::singles(COL_MAP.mem_value[2])); res } diff --git a/evm/src/cpu/jumps.rs b/evm/src/cpu/jumps.rs index 10c9503a..bac10eb6 100644 --- a/evm/src/cpu/jumps.rs +++ b/evm/src/cpu/jumps.rs @@ -17,16 +17,16 @@ pub fn eval_packed_exit_kernel( nv: &CpuColumnsView

, yield_constr: &mut ConstraintConsumer

, ) { - let jumps_lv = lv.general.jumps(); + let input = lv.mem_value[0]; // If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode // flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the // kernel to set them to zero). yield_constr.constraint_transition( - lv.is_cpu_cycle * lv.is_exit_kernel * (jumps_lv.input0[0] - nv.program_counter), + lv.is_cpu_cycle * lv.is_exit_kernel * (input[0] - nv.program_counter), ); yield_constr.constraint_transition( - lv.is_cpu_cycle * lv.is_exit_kernel * (jumps_lv.input0[1] - nv.is_kernel_mode), + lv.is_cpu_cycle * lv.is_exit_kernel * (input[1] - nv.is_kernel_mode), ); } @@ -36,18 +36,18 @@ pub fn eval_ext_circuit_exit_kernel, const D: usize nv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - let jumps_lv = lv.general.jumps(); + let input = lv.mem_value[0]; let filter = builder.mul_extension(lv.is_cpu_cycle, lv.is_exit_kernel); // If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode // flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the // kernel to set them to zero). - let pc_constr = builder.sub_extension(jumps_lv.input0[0], nv.program_counter); + let pc_constr = builder.sub_extension(input[0], nv.program_counter); let pc_constr = builder.mul_extension(filter, pc_constr); yield_constr.constraint_transition(builder, pc_constr); - let kernel_constr = builder.sub_extension(jumps_lv.input0[1], nv.is_kernel_mode); + let kernel_constr = builder.sub_extension(input[1], nv.is_kernel_mode); let kernel_constr = builder.mul_extension(filter, kernel_constr); yield_constr.constraint_transition(builder, kernel_constr); } @@ -58,12 +58,14 @@ pub fn eval_packed_jump_jumpi( yield_constr: &mut ConstraintConsumer

, ) { let jumps_lv = lv.general.jumps(); + let input0 = lv.mem_value[0]; + let input1 = lv.mem_value[1]; let filter = lv.is_jump + lv.is_jumpi; // `JUMP` or `JUMPI` // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. // In other words, we implement `JUMP(addr)` as `JUMPI(addr, cond=1)`. - yield_constr.constraint(lv.is_jump * (jumps_lv.input1[0] - P::ONES)); - for &limb in &jumps_lv.input1[1..] { + yield_constr.constraint(lv.is_jump * (input1[0] - P::ONES)); + for &limb in &input1[1..] { // Set all limbs (other than the least-significant limb) to 0. // NB: Technically, they don't have to be 0, as long as the sum // `input1[0] + ... + input1[7]` cannot overflow. @@ -75,7 +77,7 @@ pub fn eval_packed_jump_jumpi( yield_constr .constraint(filter * jumps_lv.input0_upper_zero * (jumps_lv.input0_upper_zero - P::ONES)); // The below sum cannot overflow due to the limb size. - let input0_upper_sum: P = jumps_lv.input0[1..].iter().copied().sum(); + let input0_upper_sum: P = input0[1..].iter().copied().sum(); // `input0_upper_zero` = 1 implies `input0_upper_sum` = 0. yield_constr.constraint(filter * jumps_lv.input0_upper_zero * input0_upper_sum); // `input0_upper_zero` = 0 implies `input0_upper_sum_inv * input0_upper_sum` = 1, which can only @@ -113,7 +115,7 @@ pub fn eval_packed_jump_jumpi( // Validate `should_continue` // This sum cannot overflow (due to limb size). - let input1_sum: P = jumps_lv.input1.into_iter().sum(); + let input1_sum: P = input1.into_iter().sum(); // `should_continue` = 1 implies `input1_sum` = 0. yield_constr.constraint(filter * jumps_lv.should_continue * input1_sum); // `should_continue` = 0 implies `input1_sum * input1_sum_inv` = 1, which can only happen if @@ -147,9 +149,8 @@ pub fn eval_packed_jump_jumpi( yield_constr.constraint_transition( filter * jumps_lv.should_continue * (nv.program_counter - lv.program_counter - P::ONES), ); - yield_constr.constraint_transition( - filter * jumps_lv.should_jump * (nv.program_counter - jumps_lv.input0[0]), - ); + yield_constr + .constraint_transition(filter * jumps_lv.should_jump * (nv.program_counter - input0[0])); } pub fn eval_ext_circuit_jump_jumpi, const D: usize>( @@ -159,15 +160,17 @@ pub fn eval_ext_circuit_jump_jumpi, const D: usize> yield_constr: &mut RecursiveConstraintConsumer, ) { let jumps_lv = lv.general.jumps(); + let input0 = lv.mem_value[0]; + let input1 = lv.mem_value[1]; let filter = builder.add_extension(lv.is_jump, lv.is_jumpi); // `JUMP` or `JUMPI` // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. // In other words, we implement `JUMP(addr)` as `JUMPI(addr, cond=1)`. { - let constr = builder.mul_sub_extension(lv.is_jump, jumps_lv.input1[0], lv.is_jump); + let constr = builder.mul_sub_extension(lv.is_jump, input1[0], lv.is_jump); yield_constr.constraint(builder, constr); } - for &limb in &jumps_lv.input1[1..] { + for &limb in &input1[1..] { // Set all limbs (other than the least-significant limb) to 0. // NB: Technically, they don't have to be 0, as long as the sum // `input1[0] + ... + input1[7]` cannot overflow. @@ -188,7 +191,7 @@ pub fn eval_ext_circuit_jump_jumpi, const D: usize> } { // The below sum cannot overflow due to the limb size. - let input0_upper_sum = builder.add_many_extension(jumps_lv.input0[1..].iter()); + let input0_upper_sum = builder.add_many_extension(input0[1..].iter()); // `input0_upper_zero` = 1 implies `input0_upper_sum` = 0. let constr = builder.mul_extension(jumps_lv.input0_upper_zero, input0_upper_sum); @@ -251,7 +254,7 @@ pub fn eval_ext_circuit_jump_jumpi, const D: usize> // Validate `should_continue` { // This sum cannot overflow (due to limb size). - let input1_sum = builder.add_many_extension(jumps_lv.input1.into_iter()); + let input1_sum = builder.add_many_extension(input1.into_iter()); // `should_continue` = 1 implies `input1_sum` = 0. let constr = builder.mul_extension(jumps_lv.should_continue, input1_sum); @@ -326,7 +329,7 @@ pub fn eval_ext_circuit_jump_jumpi, const D: usize> } // ...or jumping. { - let constr = builder.sub_extension(nv.program_counter, jumps_lv.input0[0]); + let constr = builder.sub_extension(nv.program_counter, input0[0]); let constr = builder.mul_extension(jumps_lv.should_jump, constr); let constr = builder.mul_extension(filter, constr); yield_constr.constraint_transition(builder, constr); diff --git a/evm/src/cpu/simple_logic/eq_iszero.rs b/evm/src/cpu/simple_logic/eq_iszero.rs index e1b33dc9..c3d9bc99 100644 --- a/evm/src/cpu/simple_logic/eq_iszero.rs +++ b/evm/src/cpu/simple_logic/eq_iszero.rs @@ -8,7 +8,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::CpuColumnsView; pub fn generate(lv: &mut CpuColumnsView) { - let logic = lv.general.logic_mut(); + let input0 = lv.mem_value[0]; + let eq_filter = lv.is_eq.to_canonical_u64(); let iszero_filter = lv.is_iszero.to_canonical_u64(); assert!(eq_filter <= 1); @@ -19,19 +20,22 @@ pub fn generate(lv: &mut CpuColumnsView) { return; } + let input1 = &mut lv.mem_value[1]; if iszero_filter != 0 { - for limb in logic.input1.iter_mut() { + for limb in input1.iter_mut() { *limb = F::ZERO; } } - let num_unequal_limbs = izip!(logic.input0, logic.input1) + let input1 = lv.mem_value[1]; + let num_unequal_limbs = izip!(input0, input1) .map(|(limb0, limb1)| (limb0 != limb1) as usize) .sum(); let equal = num_unequal_limbs == 0; - logic.output[0] = F::from_bool(equal); - for limb in &mut logic.output[1..] { + let output = &mut lv.mem_value[2]; + output[0] = F::from_bool(equal); + for limb in &mut output[1..] { *limb = F::ZERO; } @@ -40,10 +44,11 @@ pub fn generate(lv: &mut CpuColumnsView) { // Then `diff @ x = num_unequal_limbs`, where `@` denotes the dot product. We set // `diff_pinv = num_unequal_limbs^-1 * x` if `num_unequal_limbs != 0` and 0 otherwise. We have // `diff @ diff_pinv = 1 - equal` as desired. + let logic = lv.general.logic_mut(); let num_unequal_limbs_inv = F::from_canonical_usize(num_unequal_limbs) .try_inverse() .unwrap_or(F::ZERO); - for (limb_pinv, limb0, limb1) in izip!(logic.diff_pinv.iter_mut(), logic.input0, logic.input1) { + for (limb_pinv, limb0, limb1) in izip!(logic.diff_pinv.iter_mut(), input0, input1) { *limb_pinv = (limb0 - limb1).try_inverse().unwrap_or(F::ZERO) * num_unequal_limbs_inv; } } @@ -53,27 +58,31 @@ pub fn eval_packed( yield_constr: &mut ConstraintConsumer

, ) { let logic = lv.general.logic(); + let input0 = lv.mem_value[0]; + let input1 = lv.mem_value[1]; + let output = lv.mem_value[2]; + let eq_filter = lv.is_eq; let iszero_filter = lv.is_iszero; let eq_or_iszero_filter = eq_filter + iszero_filter; - let equal = logic.output[0]; + let equal = output[0]; let unequal = P::ONES - equal; // Handle `EQ` and `ISZERO`. Most limbs of the output are 0, but the least-significant one is // either 0 or 1. yield_constr.constraint(eq_or_iszero_filter * equal * unequal); - for &limb in &logic.output[1..] { + for &limb in &output[1..] { yield_constr.constraint(eq_or_iszero_filter * limb); } // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) as EQ(x, 0). - for limb in logic.input1 { + for limb in input1 { yield_constr.constraint(iszero_filter * limb); } // `equal` implies `input0[i] == input1[i]` for all `i`. - for (limb0, limb1) in izip!(logic.input0, logic.input1) { + for (limb0, limb1) in izip!(input0, input1) { let diff = limb0 - limb1; yield_constr.constraint(eq_or_iszero_filter * equal * diff); } @@ -82,7 +91,7 @@ pub fn eval_packed( // If `unequal`, find `diff_pinv` such that `(input0 - input1) @ diff_pinv == 1`, where `@` // denotes the dot product (there will be many such `diff_pinv`). This can only be done if // `input0 != input1`. - let dot: P = izip!(logic.input0, logic.input1, logic.diff_pinv) + let dot: P = izip!(input0, input1, logic.diff_pinv) .map(|(limb0, limb1, diff_pinv_el)| (limb0 - limb1) * diff_pinv_el) .sum(); yield_constr.constraint(eq_or_iszero_filter * (dot - unequal)); @@ -97,11 +106,15 @@ pub fn eval_ext_circuit, const D: usize>( let one = builder.one_extension(); let logic = lv.general.logic(); + let input0 = lv.mem_value[0]; + let input1 = lv.mem_value[1]; + let output = lv.mem_value[2]; + let eq_filter = lv.is_eq; let iszero_filter = lv.is_iszero; let eq_or_iszero_filter = builder.add_extension(eq_filter, iszero_filter); - let equal = logic.output[0]; + let equal = output[0]; let unequal = builder.sub_extension(one, equal); // Handle `EQ` and `ISZERO`. Most limbs of the output are 0, but the least-significant one is @@ -111,19 +124,19 @@ pub fn eval_ext_circuit, const D: usize>( let constr = builder.mul_extension(eq_or_iszero_filter, constr); yield_constr.constraint(builder, constr); } - for &limb in &logic.output[1..] { + for &limb in &output[1..] { let constr = builder.mul_extension(eq_or_iszero_filter, limb); yield_constr.constraint(builder, constr); } // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) as EQ(x, 0). - for limb in logic.input1 { + for limb in input1 { let constr = builder.mul_extension(iszero_filter, limb); yield_constr.constraint(builder, constr); } // `equal` implies `input0[i] == input1[i]` for all `i`. - for (limb0, limb1) in izip!(logic.input0, logic.input1) { + for (limb0, limb1) in izip!(input0, input1) { let diff = builder.sub_extension(limb0, limb1); let constr = builder.mul_extension(equal, diff); let constr = builder.mul_extension(eq_or_iszero_filter, constr); @@ -135,7 +148,7 @@ pub fn eval_ext_circuit, const D: usize>( // denotes the dot product (there will be many such `diff_pinv`). This can only be done if // `input0 != input1`. { - let dot: ExtensionTarget = izip!(logic.input0, logic.input1, logic.diff_pinv).fold( + let dot: ExtensionTarget = izip!(input0, input1, logic.diff_pinv).fold( zero, |cumul, (limb0, limb1, diff_pinv_el)| { let diff = builder.sub_extension(limb0, limb1); diff --git a/evm/src/cpu/simple_logic/not.rs b/evm/src/cpu/simple_logic/not.rs index bcff3344..d9a16a66 100644 --- a/evm/src/cpu/simple_logic/not.rs +++ b/evm/src/cpu/simple_logic/not.rs @@ -17,8 +17,9 @@ pub fn generate(lv: &mut CpuColumnsView) { } assert_eq!(is_not_filter, 1); - let logic = lv.general.logic_mut(); - for (input, output_ref) in logic.input0.into_iter().zip(logic.output.iter_mut()) { + let input = lv.mem_value[0]; + let output = &mut lv.mem_value[1]; + for (input, output_ref) in input.into_iter().zip(output.iter_mut()) { let input = input.to_canonical_u64(); assert_eq!(input >> LIMB_SIZE, 0); let output = input ^ ALL_1_LIMB; @@ -30,14 +31,16 @@ pub fn eval_packed( lv: &CpuColumnsView

, yield_constr: &mut ConstraintConsumer

, ) { - // This is simple: just do output = 0xffff - input. - let logic = lv.general.logic(); + // This is simple: just do output = 0xffffffff - input. + let input = lv.mem_value[0]; + let output = lv.mem_value[1]; let cycle_filter = lv.is_cpu_cycle; let is_not_filter = lv.is_not; let filter = cycle_filter * is_not_filter; - for (input, output) in logic.input0.into_iter().zip(logic.output) { - yield_constr - .constraint(filter * (output + input - P::Scalar::from_canonical_u64(ALL_1_LIMB))); + for (input_limb, output_limb) in input.into_iter().zip(output) { + yield_constr.constraint( + filter * (output_limb + input_limb - P::Scalar::from_canonical_u64(ALL_1_LIMB)), + ); } } @@ -46,12 +49,13 @@ pub fn eval_ext_circuit, const D: usize>( lv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - let logic = lv.general.logic(); + let input = lv.mem_value[0]; + let output = lv.mem_value[1]; let cycle_filter = lv.is_cpu_cycle; let is_not_filter = lv.is_not; let filter = builder.mul_extension(cycle_filter, is_not_filter); - for (input, output) in logic.input0.into_iter().zip(logic.output) { - let constr = builder.add_extension(output, input); + for (input_limb, output_limb) in input.into_iter().zip(output) { + let constr = builder.add_extension(output_limb, input_limb); let constr = builder.arithmetic_extension( F::ONE, -F::from_canonical_u64(ALL_1_LIMB), diff --git a/evm/src/cpu/syscalls.rs b/evm/src/cpu/syscalls.rs index a676a6a2..1ca45bc3 100644 --- a/evm/src/cpu/syscalls.rs +++ b/evm/src/cpu/syscalls.rs @@ -28,7 +28,6 @@ pub fn eval_packed( nv: &CpuColumnsView

, yield_constr: &mut ConstraintConsumer

, ) { - let lv_syscalls = lv.general.syscalls(); let syscall_list = Lazy::force(&TRAP_LIST); // 1 if _any_ syscall, else 0. let should_syscall: P = syscall_list @@ -48,12 +47,14 @@ pub fn eval_packed( yield_constr.constraint_transition(filter * (nv.program_counter - syscall_dst)); // If syscall: set kernel mode yield_constr.constraint_transition(filter * (nv.is_kernel_mode - P::ONES)); + + let output = lv.mem_value[0]; // If syscall: push current PC to stack - yield_constr.constraint(filter * (lv_syscalls.output[0] - lv.program_counter)); + yield_constr.constraint(filter * (output[0] - lv.program_counter)); // If syscall: push current kernel flag to stack (share register with PC) - yield_constr.constraint(filter * (lv_syscalls.output[1] - lv.is_kernel_mode)); + yield_constr.constraint(filter * (output[1] - lv.is_kernel_mode)); // If syscall: zero the rest of that register - for &limb in &lv_syscalls.output[2..] { + for &limb in &output[2..] { yield_constr.constraint(filter * limb); } } @@ -64,7 +65,6 @@ pub fn eval_ext_circuit, const D: usize>( nv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - let lv_syscalls = lv.general.syscalls(); let syscall_list = Lazy::force(&TRAP_LIST); // 1 if _any_ syscall, else 0. let should_syscall = @@ -90,20 +90,22 @@ pub fn eval_ext_circuit, const D: usize>( let constr = builder.mul_sub_extension(filter, nv.is_kernel_mode, filter); yield_constr.constraint_transition(builder, constr); } + + let output = lv.mem_value[0]; // If syscall: push current PC to stack { - let constr = builder.sub_extension(lv_syscalls.output[0], lv.program_counter); + let constr = builder.sub_extension(output[0], lv.program_counter); let constr = builder.mul_extension(filter, constr); yield_constr.constraint(builder, constr); } // If syscall: push current kernel flag to stack (share register with PC) { - let constr = builder.sub_extension(lv_syscalls.output[1], lv.is_kernel_mode); + let constr = builder.sub_extension(output[1], lv.is_kernel_mode); let constr = builder.mul_extension(filter, constr); yield_constr.constraint(builder, constr); } // If syscall: zero the rest of that register - for &limb in &lv_syscalls.output[2..] { + for &limb in &output[2..] { let constr = builder.mul_extension(filter, limb); yield_constr.constraint(builder, constr); } From 08758a3b9db6874ccef3cef383a05507bc4087a7 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 26 Aug 2022 15:47:26 -0700 Subject: [PATCH 49/95] newline --- u32/src/witness.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/u32/src/witness.rs b/u32/src/witness.rs index 38aa2238..ddc3432f 100644 --- a/u32/src/witness.rs +++ b/u32/src/witness.rs @@ -13,6 +13,7 @@ impl, F: PrimeField64> WitnessU32 for T { fn set_u32_target(&mut self, target: U32Target, value: u32) { self.set_target(target.0, F::from_canonical_u32(value)); } + fn get_u32_target(&self, target: U32Target) -> (u32, u32) { let x_u64 = self.get_target(target.0).to_canonical_u64(); let low = x_u64 as u32; From 013bf6471d1543c5e0d957eb564fab534ad7e99b Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Fri, 26 Aug 2022 22:05:16 -0700 Subject: [PATCH 50/95] Transpose memory columns (make it an array of channel structs) (#700) --- evm/src/all_stark.rs | 57 +++++++++++++++------------ evm/src/cpu/columns/general.rs | 8 ++-- evm/src/cpu/columns/mod.rs | 22 +++++++---- evm/src/cpu/cpu_stark.rs | 25 ++++++------ evm/src/cpu/jumps.rs | 12 +++--- evm/src/cpu/simple_logic/eq_iszero.rs | 20 +++++----- evm/src/cpu/simple_logic/not.rs | 12 +++--- evm/src/cpu/syscalls.rs | 4 +- evm/src/generation/state.rs | 28 ++++++------- 9 files changed, 100 insertions(+), 88 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index e8b44d23..5fd262ac 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -344,19 +344,16 @@ mod tests { if is_actual_op { let row: &mut cpu::columns::CpuColumnsView = cpu_trace_rows[clock].borrow_mut(); - - row.mem_channel_used[channel] = F::ONE; row.clock = F::from_canonical_usize(clock); - row.mem_is_read[channel] = memory_trace[memory::columns::IS_READ].values[i]; - row.mem_addr_context[channel] = - memory_trace[memory::columns::ADDR_CONTEXT].values[i]; - row.mem_addr_segment[channel] = - memory_trace[memory::columns::ADDR_SEGMENT].values[i]; - row.mem_addr_virtual[channel] = - memory_trace[memory::columns::ADDR_VIRTUAL].values[i]; + + let channel = &mut row.mem_channels[channel]; + channel.used = F::ONE; + channel.is_read = memory_trace[memory::columns::IS_READ].values[i]; + channel.addr_context = memory_trace[memory::columns::ADDR_CONTEXT].values[i]; + channel.addr_segment = memory_trace[memory::columns::ADDR_SEGMENT].values[i]; + channel.addr_virtual = memory_trace[memory::columns::ADDR_VIRTUAL].values[i]; for j in 0..8 { - row.mem_value[channel][j] = - memory_trace[memory::columns::value_limb(j)].values[i]; + channel.value[j] = memory_trace[memory::columns::value_limb(j)].values[i]; } } } @@ -382,16 +379,24 @@ mod tests { ); let input0_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT0); - for (col_cpu, limb_cols_logic) in row.mem_value[0].iter_mut().zip(input0_bit_cols) { + for (col_cpu, limb_cols_logic) in + row.mem_channels[0].value.iter_mut().zip(input0_bit_cols) + { *col_cpu = limb_from_bits_le(limb_cols_logic.map(|col| logic_trace[col].values[i])); } let input1_bit_cols = logic::columns::limb_bit_cols_for_input(logic::columns::INPUT1); - for (col_cpu, limb_cols_logic) in row.mem_value[1].iter_mut().zip(input1_bit_cols) { + for (col_cpu, limb_cols_logic) in + row.mem_channels[1].value.iter_mut().zip(input1_bit_cols) + { *col_cpu = limb_from_bits_le(limb_cols_logic.map(|col| logic_trace[col].values[i])); } - for (col_cpu, col_logic) in row.mem_value[2].iter_mut().zip(logic::columns::RESULT) { + for (col_cpu, col_logic) in row.mem_channels[2] + .value + .iter_mut() + .zip(logic::columns::RESULT) + { *col_cpu = logic_trace[col_logic].values[i]; } @@ -409,7 +414,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x0a); // `EXP` is implemented in software row.is_kernel_mode = F::ONE; row.program_counter = last_row.program_counter + F::ONE; - row.mem_value[0] = [ + row.mem_channels[0].value = [ row.program_counter, F::ONE, F::ZERO, @@ -431,7 +436,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0xf9); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_usize(KERNEL.global_labels["sys_exp"]); - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(15682), F::ONE, F::ZERO, @@ -453,7 +458,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_u16(15682); - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(15106), F::ZERO, F::ZERO, @@ -463,7 +468,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.mem_value[1] = [ + row.mem_channels[1].value = [ F::ONE, F::ZERO, F::ZERO, @@ -490,7 +495,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0xf9); row.is_kernel_mode = F::ONE; row.program_counter = F::from_canonical_u16(15106); - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(63064), F::ZERO, F::ZERO, @@ -512,7 +517,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(63064); - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(3754), F::ZERO, F::ZERO, @@ -522,7 +527,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.mem_value[1] = [ + row.mem_channels[1].value = [ F::ONE, F::ZERO, F::ZERO, @@ -550,7 +555,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x57); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(3754); - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(37543), F::ZERO, F::ZERO, @@ -560,7 +565,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.mem_value[1] = [ + row.mem_channels[1].value = [ F::ZERO, F::ZERO, F::ZERO, @@ -588,7 +593,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x57); row.is_kernel_mode = F::ZERO; row.program_counter = F::from_canonical_u16(37543); - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(37543), F::ZERO, F::ZERO, @@ -617,7 +622,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x56); row.is_kernel_mode = F::ZERO; row.program_counter = last_row.program_counter + F::ONE; - row.mem_value[0] = [ + row.mem_channels[0].value = [ F::from_canonical_u16(37543), F::ZERO, F::ZERO, @@ -627,7 +632,7 @@ mod tests { F::ZERO, F::ZERO, ]; - row.mem_value[1] = [ + row.mem_channels[1].value = [ F::ONE, F::ZERO, F::ZERO, diff --git a/evm/src/cpu/columns/general.rs b/evm/src/cpu/columns/general.rs index 43f987e9..134788dc 100644 --- a/evm/src/cpu/columns/general.rs +++ b/evm/src/cpu/columns/general.rs @@ -102,10 +102,10 @@ pub(crate) struct CpuLogicView { #[derive(Copy, Clone)] pub(crate) struct CpuJumpsView { - /// `input0` is `mem_value[0]`. It's the top stack value at entry (for jumps, the address; for - /// `EXIT_KERNEL`, the address and new privilege level). - /// `input1` is `mem_value[1]`. For `JUMPI`, it's the second stack value (the predicate). For - /// `JUMP`, 1. + /// `input0` is `mem_channel[0].value`. It's the top stack value at entry (for jumps, the + /// address; for `EXIT_KERNEL`, the address and new privilege level). + /// `input1` is `mem_channel[1].value`. For `JUMPI`, it's the second stack value (the + /// predicate). For `JUMP`, 1. /// Inverse of `input0[1] + ... + input0[7]`, if one exists; otherwise, an arbitrary value. /// Needed to prove that `input0` is nonzero. diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 564ea246..34a02837 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -11,6 +11,19 @@ use crate::memory; mod general; +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MemoryChannelView { + /// 1 if this row includes a memory operation in the `i`th channel of the memory bus, otherwise + /// 0. + pub used: T, + pub is_read: T, + pub addr_context: T, + pub addr_segment: T, + pub addr_virtual: T, + pub value: [T; memory::VALUE_LIMBS], +} + #[repr(C)] #[derive(Eq, PartialEq, Debug)] pub struct CpuColumnsView { @@ -159,14 +172,7 @@ pub struct CpuColumnsView { pub(crate) general: CpuGeneralColumnsView, pub(crate) clock: T, - /// 1 if this row includes a memory operation in the `i`th channel of the memory bus, otherwise - /// 0. - pub mem_channel_used: [T; memory::NUM_CHANNELS], - pub mem_is_read: [T; memory::NUM_CHANNELS], - pub mem_addr_context: [T; memory::NUM_CHANNELS], - pub mem_addr_segment: [T; memory::NUM_CHANNELS], - pub mem_addr_virtual: [T; memory::NUM_CHANNELS], - pub mem_value: [[T; memory::VALUE_LIMBS]; memory::NUM_CHANNELS], + pub mem_channels: [MemoryChannelView; memory::NUM_CHANNELS], } // `u8` is guaranteed to have a `size_of` of 1. diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 39518a43..9fd4792d 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -28,9 +28,9 @@ pub fn ctl_data_keccak_memory() -> Vec> { // channel 1: stack[-1] = context // channel 2: stack[-2] = segment // channel 3: stack[-3] = virtual - let context = Column::single(COL_MAP.mem_value[1][0]); - let segment = Column::single(COL_MAP.mem_value[2][0]); - let virt = Column::single(COL_MAP.mem_value[3][0]); + let context = Column::single(COL_MAP.mem_channels[1].value[0]); + let segment = Column::single(COL_MAP.mem_channels[2].value[0]); + let virt = Column::single(COL_MAP.mem_channels[3].value[0]); let num_channels = F::from_canonical_usize(NUM_CHANNELS); let clock = Column::linear_combination([(COL_MAP.clock, num_channels)]); @@ -48,9 +48,9 @@ pub fn ctl_filter_keccak_memory() -> Column { pub fn ctl_data_logic() -> Vec> { let mut res = Column::singles([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]).collect_vec(); - res.extend(Column::singles(COL_MAP.mem_value[0])); - res.extend(Column::singles(COL_MAP.mem_value[1])); - res.extend(Column::singles(COL_MAP.mem_value[2])); + res.extend(Column::singles(COL_MAP.mem_channels[0].value)); + res.extend(Column::singles(COL_MAP.mem_channels[1].value)); + res.extend(Column::singles(COL_MAP.mem_channels[2].value)); res } @@ -60,14 +60,15 @@ pub fn ctl_filter_logic() -> Column { pub fn ctl_data_memory(channel: usize) -> Vec> { debug_assert!(channel < NUM_CHANNELS); + let channel_map = COL_MAP.mem_channels[channel]; let mut cols: Vec> = Column::singles([ - COL_MAP.mem_is_read[channel], - COL_MAP.mem_addr_context[channel], - COL_MAP.mem_addr_segment[channel], - COL_MAP.mem_addr_virtual[channel], + channel_map.is_read, + channel_map.addr_context, + channel_map.addr_segment, + channel_map.addr_virtual, ]) .collect_vec(); - cols.extend(Column::singles(COL_MAP.mem_value[channel])); + cols.extend(Column::singles(channel_map.value)); let scalar = F::from_canonical_usize(NUM_CHANNELS); let addend = F::from_canonical_usize(channel); @@ -80,7 +81,7 @@ pub fn ctl_data_memory(channel: usize) -> Vec> { } pub fn ctl_filter_memory(channel: usize) -> Column { - Column::single(COL_MAP.mem_channel_used[channel]) + Column::single(COL_MAP.mem_channels[channel].used) } #[derive(Copy, Clone, Default)] diff --git a/evm/src/cpu/jumps.rs b/evm/src/cpu/jumps.rs index bac10eb6..219b39dd 100644 --- a/evm/src/cpu/jumps.rs +++ b/evm/src/cpu/jumps.rs @@ -17,7 +17,7 @@ pub fn eval_packed_exit_kernel( nv: &CpuColumnsView

, yield_constr: &mut ConstraintConsumer

, ) { - let input = lv.mem_value[0]; + let input = lv.mem_channels[0].value; // If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode // flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the @@ -36,7 +36,7 @@ pub fn eval_ext_circuit_exit_kernel, const D: usize nv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - let input = lv.mem_value[0]; + let input = lv.mem_channels[0].value; let filter = builder.mul_extension(lv.is_cpu_cycle, lv.is_exit_kernel); // If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode @@ -58,8 +58,8 @@ pub fn eval_packed_jump_jumpi( yield_constr: &mut ConstraintConsumer

, ) { let jumps_lv = lv.general.jumps(); - let input0 = lv.mem_value[0]; - let input1 = lv.mem_value[1]; + let input0 = lv.mem_channels[0].value; + let input1 = lv.mem_channels[1].value; let filter = lv.is_jump + lv.is_jumpi; // `JUMP` or `JUMPI` // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. @@ -160,8 +160,8 @@ pub fn eval_ext_circuit_jump_jumpi, const D: usize> yield_constr: &mut RecursiveConstraintConsumer, ) { let jumps_lv = lv.general.jumps(); - let input0 = lv.mem_value[0]; - let input1 = lv.mem_value[1]; + let input0 = lv.mem_channels[0].value; + let input1 = lv.mem_channels[1].value; let filter = builder.add_extension(lv.is_jump, lv.is_jumpi); // `JUMP` or `JUMPI` // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. diff --git a/evm/src/cpu/simple_logic/eq_iszero.rs b/evm/src/cpu/simple_logic/eq_iszero.rs index c3d9bc99..6b7294a8 100644 --- a/evm/src/cpu/simple_logic/eq_iszero.rs +++ b/evm/src/cpu/simple_logic/eq_iszero.rs @@ -8,7 +8,7 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::CpuColumnsView; pub fn generate(lv: &mut CpuColumnsView) { - let input0 = lv.mem_value[0]; + let input0 = lv.mem_channels[0].value; let eq_filter = lv.is_eq.to_canonical_u64(); let iszero_filter = lv.is_iszero.to_canonical_u64(); @@ -20,20 +20,20 @@ pub fn generate(lv: &mut CpuColumnsView) { return; } - let input1 = &mut lv.mem_value[1]; + let input1 = &mut lv.mem_channels[1].value; if iszero_filter != 0 { for limb in input1.iter_mut() { *limb = F::ZERO; } } - let input1 = lv.mem_value[1]; + let input1 = lv.mem_channels[1].value; let num_unequal_limbs = izip!(input0, input1) .map(|(limb0, limb1)| (limb0 != limb1) as usize) .sum(); let equal = num_unequal_limbs == 0; - let output = &mut lv.mem_value[2]; + let output = &mut lv.mem_channels[2].value; output[0] = F::from_bool(equal); for limb in &mut output[1..] { *limb = F::ZERO; @@ -58,9 +58,9 @@ pub fn eval_packed( yield_constr: &mut ConstraintConsumer

, ) { let logic = lv.general.logic(); - let input0 = lv.mem_value[0]; - let input1 = lv.mem_value[1]; - let output = lv.mem_value[2]; + let input0 = lv.mem_channels[0].value; + let input1 = lv.mem_channels[1].value; + let output = lv.mem_channels[2].value; let eq_filter = lv.is_eq; let iszero_filter = lv.is_iszero; @@ -106,9 +106,9 @@ pub fn eval_ext_circuit, const D: usize>( let one = builder.one_extension(); let logic = lv.general.logic(); - let input0 = lv.mem_value[0]; - let input1 = lv.mem_value[1]; - let output = lv.mem_value[2]; + let input0 = lv.mem_channels[0].value; + let input1 = lv.mem_channels[1].value; + let output = lv.mem_channels[2].value; let eq_filter = lv.is_eq; let iszero_filter = lv.is_iszero; diff --git a/evm/src/cpu/simple_logic/not.rs b/evm/src/cpu/simple_logic/not.rs index d9a16a66..83d43276 100644 --- a/evm/src/cpu/simple_logic/not.rs +++ b/evm/src/cpu/simple_logic/not.rs @@ -17,8 +17,8 @@ pub fn generate(lv: &mut CpuColumnsView) { } assert_eq!(is_not_filter, 1); - let input = lv.mem_value[0]; - let output = &mut lv.mem_value[1]; + let input = lv.mem_channels[0].value; + let output = &mut lv.mem_channels[1].value; for (input, output_ref) in input.into_iter().zip(output.iter_mut()) { let input = input.to_canonical_u64(); assert_eq!(input >> LIMB_SIZE, 0); @@ -32,8 +32,8 @@ pub fn eval_packed( yield_constr: &mut ConstraintConsumer

, ) { // This is simple: just do output = 0xffffffff - input. - let input = lv.mem_value[0]; - let output = lv.mem_value[1]; + let input = lv.mem_channels[0].value; + let output = lv.mem_channels[1].value; let cycle_filter = lv.is_cpu_cycle; let is_not_filter = lv.is_not; let filter = cycle_filter * is_not_filter; @@ -49,8 +49,8 @@ pub fn eval_ext_circuit, const D: usize>( lv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - let input = lv.mem_value[0]; - let output = lv.mem_value[1]; + let input = lv.mem_channels[0].value; + let output = lv.mem_channels[1].value; let cycle_filter = lv.is_cpu_cycle; let is_not_filter = lv.is_not; let filter = builder.mul_extension(cycle_filter, is_not_filter); diff --git a/evm/src/cpu/syscalls.rs b/evm/src/cpu/syscalls.rs index 1ca45bc3..116713ae 100644 --- a/evm/src/cpu/syscalls.rs +++ b/evm/src/cpu/syscalls.rs @@ -48,7 +48,7 @@ pub fn eval_packed( // If syscall: set kernel mode yield_constr.constraint_transition(filter * (nv.is_kernel_mode - P::ONES)); - let output = lv.mem_value[0]; + let output = lv.mem_channels[0].value; // If syscall: push current PC to stack yield_constr.constraint(filter * (output[0] - lv.program_counter)); // If syscall: push current kernel flag to stack (share register with PC) @@ -91,7 +91,7 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr.constraint_transition(builder, constr); } - let output = lv.mem_value[0]; + let output = lv.mem_channels[0].value; // If syscall: push current PC to stack { let constr = builder.sub_extension(output[0], lv.program_counter); diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 866f9fd7..4cbe61c8 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -77,13 +77,13 @@ impl GenerationState { let timestamp = self.cpu_rows.len() * NUM_CHANNELS + channel_index; let value = self.get_mem(context, segment, virt, timestamp); - self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; - self.current_cpu_row.mem_is_read[channel_index] = F::ONE; - self.current_cpu_row.mem_addr_context[channel_index] = F::from_canonical_usize(context); - self.current_cpu_row.mem_addr_segment[channel_index] = - F::from_canonical_usize(segment as usize); - self.current_cpu_row.mem_addr_virtual[channel_index] = F::from_canonical_usize(virt); - self.current_cpu_row.mem_value[channel_index] = u256_limbs(value); + let channel = &mut self.current_cpu_row.mem_channels[channel_index]; + channel.used = F::ONE; + channel.is_read = F::ONE; + channel.addr_context = F::from_canonical_usize(context); + channel.addr_segment = F::from_canonical_usize(segment as usize); + channel.addr_virtual = F::from_canonical_usize(virt); + channel.value = u256_limbs(value); value } @@ -133,13 +133,13 @@ impl GenerationState { let timestamp = self.cpu_rows.len() * NUM_CHANNELS + channel_index; self.set_mem(context, segment, virt, value, timestamp); - self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; - self.current_cpu_row.mem_is_read[channel_index] = F::ZERO; // For clarity; should already be 0. - self.current_cpu_row.mem_addr_context[channel_index] = F::from_canonical_usize(context); - self.current_cpu_row.mem_addr_segment[channel_index] = - F::from_canonical_usize(segment as usize); - self.current_cpu_row.mem_addr_virtual[channel_index] = F::from_canonical_usize(virt); - self.current_cpu_row.mem_value[channel_index] = u256_limbs(value); + let channel = &mut self.current_cpu_row.mem_channels[channel_index]; + channel.used = F::ONE; + channel.is_read = F::ZERO; // For clarity; should already be 0. + channel.addr_context = F::from_canonical_usize(context); + channel.addr_segment = F::from_canonical_usize(segment as usize); + channel.addr_virtual = F::from_canonical_usize(virt); + channel.value = u256_limbs(value); } /// Write some memory, and log the operation. From 8505d64e37dda12835229aa4e738476040aa14ad Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 30 Aug 2022 12:16:40 -0700 Subject: [PATCH 51/95] Fill in `keccakf_u32s` --- evm/src/cpu/kernel/keccak_util.rs | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/keccak_util.rs b/evm/src/cpu/kernel/keccak_util.rs index 1498ba08..52cc0f08 100644 --- a/evm/src/cpu/kernel/keccak_util.rs +++ b/evm/src/cpu/kernel/keccak_util.rs @@ -1,3 +1,5 @@ +use tiny_keccak::keccakf; + /// A Keccak-f based hash. /// /// This hash does not use standard Keccak padding, since we don't care about extra zeros at the @@ -9,6 +11,42 @@ pub(crate) fn hash_kernel(_code: &[u8]) -> [u32; 8] { } /// Like tiny-keccak's `keccakf`, but deals with `u32` limbs instead of `u64` limbs. -pub(crate) fn keccakf_u32s(_state: &mut [u32; 50]) { - // TODO: Implement +pub(crate) fn keccakf_u32s(state_u32s: &mut [u32; 50]) { + let mut state_u64s: [u64; 25] = std::array::from_fn(|i| { + let lo = state_u32s[i * 2] as u64; + let hi = state_u32s[i * 2 + 1] as u64; + lo | (hi << 32) + }); + keccakf(&mut state_u64s); + *state_u32s = std::array::from_fn(|i| { + let u64_limb = state_u64s[i / 2]; + let is_hi = i % 2; + (u64_limb >> (is_hi * 32)) as u32 + }); +} + +#[cfg(test)] +mod tests { + use tiny_keccak::keccakf; + + use crate::cpu::kernel::keccak_util::keccakf_u32s; + + #[test] + #[rustfmt::skip] + fn test_consistency() { + // We will hash the same data using keccakf and keccakf_u32s. + // The inputs were randomly generated in Python. + let mut state_u64s: [u64; 25] = [0x5dc43ed05dc64048, 0x7bb9e18cdc853880, 0xc1fde300665b008f, 0xeeab85e089d5e431, 0xf7d61298e9ef27ea, 0xc2c5149d1a492455, 0x37a2f4eca0c2d2f2, 0xa35e50c015b3e85c, 0xd2daeced29446ebe, 0x245845f1bac1b98e, 0x3b3aa8783f30a9bf, 0x209ca9a81956d241, 0x8b8ea714da382165, 0x6063e67e202c6d29, 0xf4bac2ded136b907, 0xb17301b461eae65, 0xa91ff0e134ed747c, 0xcc080b28d0c20f1d, 0xf0f79cbec4fb551c, 0x25e04cb0aa930cad, 0x803113d1b541a202, 0xfaf1e4e7cd23b7ec, 0x36a03bbf2469d3b0, 0x25217341908cdfc0, 0xe9cd83f88fdcd500]; + let mut state_u32s: [u32; 50] = [0x5dc64048, 0x5dc43ed0, 0xdc853880, 0x7bb9e18c, 0x665b008f, 0xc1fde300, 0x89d5e431, 0xeeab85e0, 0xe9ef27ea, 0xf7d61298, 0x1a492455, 0xc2c5149d, 0xa0c2d2f2, 0x37a2f4ec, 0x15b3e85c, 0xa35e50c0, 0x29446ebe, 0xd2daeced, 0xbac1b98e, 0x245845f1, 0x3f30a9bf, 0x3b3aa878, 0x1956d241, 0x209ca9a8, 0xda382165, 0x8b8ea714, 0x202c6d29, 0x6063e67e, 0xd136b907, 0xf4bac2de, 0x461eae65, 0xb17301b, 0x34ed747c, 0xa91ff0e1, 0xd0c20f1d, 0xcc080b28, 0xc4fb551c, 0xf0f79cbe, 0xaa930cad, 0x25e04cb0, 0xb541a202, 0x803113d1, 0xcd23b7ec, 0xfaf1e4e7, 0x2469d3b0, 0x36a03bbf, 0x908cdfc0, 0x25217341, 0x8fdcd500, 0xe9cd83f8]; + + // The first output was generated using tiny-keccak; the second was derived from it. + let out_u64s: [u64; 25] = [0x8a541df597e79a72, 0x5c26b8c84faaebb3, 0xc0e8f4e67ca50497, 0x95d98a688de12dec, 0x1c837163975ffaed, 0x9481ec7ef948900e, 0x6a072c65d050a9a1, 0x3b2817da6d615bee, 0x7ffb3c4f8b94bf21, 0x85d6c418cced4a11, 0x18edbe0442884135, 0x2bf265ef3204b7fd, 0xc1e12ce30630d105, 0x8c554dbc61844574, 0x5504db652ce9e42c, 0x2217f3294d0dabe5, 0x7df8eebbcf5b74df, 0x3a56ebb61956f501, 0x7840219dc6f37cc, 0x23194159c967947, 0x9da289bf616ba14d, 0x5a90aaeeca9e9e5b, 0x885dcdc4a549b4e3, 0x46cb188c20947df7, 0x1ef285948ee3d8ab]; + let out_u32s: [u32; 50] = [0x97e79a72, 0x8a541df5, 0x4faaebb3, 0x5c26b8c8, 0x7ca50497, 0xc0e8f4e6, 0x8de12dec, 0x95d98a68, 0x975ffaed, 0x1c837163, 0xf948900e, 0x9481ec7e, 0xd050a9a1, 0x6a072c65, 0x6d615bee, 0x3b2817da, 0x8b94bf21, 0x7ffb3c4f, 0xcced4a11, 0x85d6c418, 0x42884135, 0x18edbe04, 0x3204b7fd, 0x2bf265ef, 0x630d105, 0xc1e12ce3, 0x61844574, 0x8c554dbc, 0x2ce9e42c, 0x5504db65, 0x4d0dabe5, 0x2217f329, 0xcf5b74df, 0x7df8eebb, 0x1956f501, 0x3a56ebb6, 0xdc6f37cc, 0x7840219, 0x9c967947, 0x2319415, 0x616ba14d, 0x9da289bf, 0xca9e9e5b, 0x5a90aaee, 0xa549b4e3, 0x885dcdc4, 0x20947df7, 0x46cb188c, 0x8ee3d8ab, 0x1ef28594]; + + keccakf(&mut state_u64s); + keccakf_u32s(&mut state_u32s); + + assert_eq!(state_u64s, out_u64s); + assert_eq!(state_u32s, out_u32s); + } } From 4c52d3754615214d3992123a0f89100a28795475 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Tue, 30 Aug 2022 13:06:03 -0700 Subject: [PATCH 52/95] Save columns by verifying invalid opcodes in software (#701) * Save columns by verifying invalid opcodes in software * Autogenerate invalid opcode bitfield (Daniel comment) * Remove unnecessary panic label --- evm/src/cpu/columns/mod.rs | 23 +- evm/src/cpu/decode.rs | 300 ++++++++++--------- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/core/invalid.asm | 26 ++ evm/src/cpu/kernel/asm/memory/core.asm | 7 + evm/src/cpu/kernel/asm/util/basic_macros.asm | 14 + evm/src/cpu/kernel/constants.rs | 5 + evm/src/cpu/syscalls.rs | 10 +- 8 files changed, 216 insertions(+), 170 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/core/invalid.asm diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 34a02837..ad0f6a95 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -137,28 +137,7 @@ pub struct CpuColumnsView { pub is_revert: T, pub is_selfdestruct: T, - // An instruction is invalid if _any_ of the below flags is 1. - pub is_invalid_0: T, - pub is_invalid_1: T, - pub is_invalid_2: T, - pub is_invalid_3: T, - pub is_invalid_4: T, - pub is_invalid_5: T, - pub is_invalid_6: T, - pub is_invalid_7: T, - pub is_invalid_8: T, - pub is_invalid_9: T, - pub is_invalid_10: T, - pub is_invalid_11: T, - pub is_invalid_12: T, - pub is_invalid_13: T, - pub is_invalid_14: T, - pub is_invalid_15: T, - pub is_invalid_16: T, - pub is_invalid_17: T, - pub is_invalid_18: T, - pub is_invalid_19: T, - pub is_invalid_20: T, + pub is_invalid: T, /// If CPU cycle: the opcode, broken up into bits in little-endian order. pub opcode_bits: [T; 8], diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index e58b474d..7ca9a650 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -6,14 +6,6 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP}; -#[derive(PartialEq, Eq)] -enum Availability { - All, - User, - Kernel, -} -use Availability::{All, Kernel, User}; - /// List of opcode blocks /// Each block corresponds to exactly one flag, and each flag corresponds to exactly one block. /// Each block of opcodes: @@ -28,124 +20,132 @@ use Availability::{All, Kernel, User}; /// The exception is the PANIC instruction which is user-only without a corresponding kernel block. /// This makes the proof unverifiable when PANIC is executed in kernel mode, which is the intended /// behavior. -const OPCODES: [(u8, usize, Availability, usize); 113] = [ - // (start index of block, number of top bits to check (log2), availability, flag column) - (0x00, 0, All, COL_MAP.is_stop), - (0x01, 0, All, COL_MAP.is_add), - (0x02, 0, All, COL_MAP.is_mul), - (0x03, 0, All, COL_MAP.is_sub), - (0x04, 0, All, COL_MAP.is_div), - (0x05, 0, All, COL_MAP.is_sdiv), - (0x06, 0, All, COL_MAP.is_mod), - (0x07, 0, All, COL_MAP.is_smod), - (0x08, 0, All, COL_MAP.is_addmod), - (0x09, 0, All, COL_MAP.is_mulmod), - (0x0a, 0, All, COL_MAP.is_exp), - (0x0b, 0, All, COL_MAP.is_signextend), - (0x0c, 2, All, COL_MAP.is_invalid_0), // 0x0c-0x0f - (0x10, 0, All, COL_MAP.is_lt), - (0x11, 0, All, COL_MAP.is_gt), - (0x12, 0, All, COL_MAP.is_slt), - (0x13, 0, All, COL_MAP.is_sgt), - (0x14, 0, All, COL_MAP.is_eq), - (0x15, 0, All, COL_MAP.is_iszero), - (0x16, 0, All, COL_MAP.is_and), - (0x17, 0, All, COL_MAP.is_or), - (0x18, 0, All, COL_MAP.is_xor), - (0x19, 0, All, COL_MAP.is_not), - (0x1a, 0, All, COL_MAP.is_byte), - (0x1b, 0, All, COL_MAP.is_shl), - (0x1c, 0, All, COL_MAP.is_shr), - (0x1d, 0, All, COL_MAP.is_sar), - (0x1e, 1, All, COL_MAP.is_invalid_1), // 0x1e-0x1f - (0x20, 0, All, COL_MAP.is_keccak256), - (0x21, 0, All, COL_MAP.is_invalid_2), - (0x22, 1, All, COL_MAP.is_invalid_3), // 0x22-0x23 - (0x24, 2, All, COL_MAP.is_invalid_4), // 0x24-0x27 - (0x28, 3, All, COL_MAP.is_invalid_5), // 0x28-0x2f - (0x30, 0, All, COL_MAP.is_address), - (0x31, 0, All, COL_MAP.is_balance), - (0x32, 0, All, COL_MAP.is_origin), - (0x33, 0, All, COL_MAP.is_caller), - (0x34, 0, All, COL_MAP.is_callvalue), - (0x35, 0, All, COL_MAP.is_calldataload), - (0x36, 0, All, COL_MAP.is_calldatasize), - (0x37, 0, All, COL_MAP.is_calldatacopy), - (0x38, 0, All, COL_MAP.is_codesize), - (0x39, 0, All, COL_MAP.is_codecopy), - (0x3a, 0, All, COL_MAP.is_gasprice), - (0x3b, 0, All, COL_MAP.is_extcodesize), - (0x3c, 0, All, COL_MAP.is_extcodecopy), - (0x3d, 0, All, COL_MAP.is_returndatasize), - (0x3e, 0, All, COL_MAP.is_returndatacopy), - (0x3f, 0, All, COL_MAP.is_extcodehash), - (0x40, 0, All, COL_MAP.is_blockhash), - (0x41, 0, All, COL_MAP.is_coinbase), - (0x42, 0, All, COL_MAP.is_timestamp), - (0x43, 0, All, COL_MAP.is_number), - (0x44, 0, All, COL_MAP.is_difficulty), - (0x45, 0, All, COL_MAP.is_gaslimit), - (0x46, 0, All, COL_MAP.is_chainid), - (0x47, 0, All, COL_MAP.is_selfbalance), - (0x48, 0, All, COL_MAP.is_basefee), - (0x49, 0, User, COL_MAP.is_invalid_6), - (0x49, 0, Kernel, COL_MAP.is_prover_input), - (0x4a, 1, All, COL_MAP.is_invalid_7), // 0x4a-0x4b - (0x4c, 2, All, COL_MAP.is_invalid_8), // 0x4c-0x4f - (0x50, 0, All, COL_MAP.is_pop), - (0x51, 0, All, COL_MAP.is_mload), - (0x52, 0, All, COL_MAP.is_mstore), - (0x53, 0, All, COL_MAP.is_mstore8), - (0x54, 0, All, COL_MAP.is_sload), - (0x55, 0, All, COL_MAP.is_sstore), - (0x56, 0, All, COL_MAP.is_jump), - (0x57, 0, All, COL_MAP.is_jumpi), - (0x58, 0, All, COL_MAP.is_pc), - (0x59, 0, All, COL_MAP.is_msize), - (0x5a, 0, All, COL_MAP.is_gas), - (0x5b, 0, All, COL_MAP.is_jumpdest), - (0x5c, 2, User, COL_MAP.is_invalid_9), // 0x5c-5f - (0x5c, 0, Kernel, COL_MAP.is_get_state_root), - (0x5d, 0, Kernel, COL_MAP.is_set_state_root), - (0x5e, 0, Kernel, COL_MAP.is_get_receipt_root), - (0x5f, 0, Kernel, COL_MAP.is_set_receipt_root), - (0x60, 5, All, COL_MAP.is_push), // 0x60-0x7f - (0x80, 4, All, COL_MAP.is_dup), // 0x80-0x8f - (0x90, 4, All, COL_MAP.is_swap), // 0x90-0x9f - (0xa0, 0, All, COL_MAP.is_log0), - (0xa1, 0, All, COL_MAP.is_log1), - (0xa2, 0, All, COL_MAP.is_log2), - (0xa3, 0, All, COL_MAP.is_log3), - (0xa4, 0, All, COL_MAP.is_log4), - (0xa5, 0, User, COL_MAP.is_invalid_10), +/// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to +/// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. +const OPCODES: [(u8, usize, bool, usize); 92] = [ + // (start index of block, number of top bits to check (log2), kernel-only, flag column) + (0x00, 0, false, COL_MAP.is_stop), + (0x01, 0, false, COL_MAP.is_add), + (0x02, 0, false, COL_MAP.is_mul), + (0x03, 0, false, COL_MAP.is_sub), + (0x04, 0, false, COL_MAP.is_div), + (0x05, 0, false, COL_MAP.is_sdiv), + (0x06, 0, false, COL_MAP.is_mod), + (0x07, 0, false, COL_MAP.is_smod), + (0x08, 0, false, COL_MAP.is_addmod), + (0x09, 0, false, COL_MAP.is_mulmod), + (0x0a, 0, false, COL_MAP.is_exp), + (0x0b, 0, false, COL_MAP.is_signextend), + (0x10, 0, false, COL_MAP.is_lt), + (0x11, 0, false, COL_MAP.is_gt), + (0x12, 0, false, COL_MAP.is_slt), + (0x13, 0, false, COL_MAP.is_sgt), + (0x14, 0, false, COL_MAP.is_eq), + (0x15, 0, false, COL_MAP.is_iszero), + (0x16, 0, false, COL_MAP.is_and), + (0x17, 0, false, COL_MAP.is_or), + (0x18, 0, false, COL_MAP.is_xor), + (0x19, 0, false, COL_MAP.is_not), + (0x1a, 0, false, COL_MAP.is_byte), + (0x1b, 0, false, COL_MAP.is_shl), + (0x1c, 0, false, COL_MAP.is_shr), + (0x1d, 0, false, COL_MAP.is_sar), + (0x20, 0, false, COL_MAP.is_keccak256), + (0x30, 0, false, COL_MAP.is_address), + (0x31, 0, false, COL_MAP.is_balance), + (0x32, 0, false, COL_MAP.is_origin), + (0x33, 0, false, COL_MAP.is_caller), + (0x34, 0, false, COL_MAP.is_callvalue), + (0x35, 0, false, COL_MAP.is_calldataload), + (0x36, 0, false, COL_MAP.is_calldatasize), + (0x37, 0, false, COL_MAP.is_calldatacopy), + (0x38, 0, false, COL_MAP.is_codesize), + (0x39, 0, false, COL_MAP.is_codecopy), + (0x3a, 0, false, COL_MAP.is_gasprice), + (0x3b, 0, false, COL_MAP.is_extcodesize), + (0x3c, 0, false, COL_MAP.is_extcodecopy), + (0x3d, 0, false, COL_MAP.is_returndatasize), + (0x3e, 0, false, COL_MAP.is_returndatacopy), + (0x3f, 0, false, COL_MAP.is_extcodehash), + (0x40, 0, false, COL_MAP.is_blockhash), + (0x41, 0, false, COL_MAP.is_coinbase), + (0x42, 0, false, COL_MAP.is_timestamp), + (0x43, 0, false, COL_MAP.is_number), + (0x44, 0, false, COL_MAP.is_difficulty), + (0x45, 0, false, COL_MAP.is_gaslimit), + (0x46, 0, false, COL_MAP.is_chainid), + (0x47, 0, false, COL_MAP.is_selfbalance), + (0x48, 0, false, COL_MAP.is_basefee), + (0x49, 0, true, COL_MAP.is_prover_input), + (0x50, 0, false, COL_MAP.is_pop), + (0x51, 0, false, COL_MAP.is_mload), + (0x52, 0, false, COL_MAP.is_mstore), + (0x53, 0, false, COL_MAP.is_mstore8), + (0x54, 0, false, COL_MAP.is_sload), + (0x55, 0, false, COL_MAP.is_sstore), + (0x56, 0, false, COL_MAP.is_jump), + (0x57, 0, false, COL_MAP.is_jumpi), + (0x58, 0, false, COL_MAP.is_pc), + (0x59, 0, false, COL_MAP.is_msize), + (0x5a, 0, false, COL_MAP.is_gas), + (0x5b, 0, false, COL_MAP.is_jumpdest), + (0x5c, 0, true, COL_MAP.is_get_state_root), + (0x5d, 0, true, COL_MAP.is_set_state_root), + (0x5e, 0, true, COL_MAP.is_get_receipt_root), + (0x5f, 0, true, COL_MAP.is_set_receipt_root), + (0x60, 5, false, COL_MAP.is_push), // 0x60-0x7f + (0x80, 4, false, COL_MAP.is_dup), // 0x80-0x8f + (0x90, 4, false, COL_MAP.is_swap), // 0x90-0x9f + (0xa0, 0, false, COL_MAP.is_log0), + (0xa1, 0, false, COL_MAP.is_log1), + (0xa2, 0, false, COL_MAP.is_log2), + (0xa3, 0, false, COL_MAP.is_log3), + (0xa4, 0, false, COL_MAP.is_log4), // Opcode 0xa5 is PANIC when Kernel. Make the proof unverifiable by giving it no flag to decode to. - (0xa6, 1, All, COL_MAP.is_invalid_11), // 0xa6-0xa7 - (0xa8, 3, All, COL_MAP.is_invalid_12), // 0xa8-0xaf - (0xb0, 4, All, COL_MAP.is_invalid_13), // 0xb0-0xbf - (0xc0, 5, All, COL_MAP.is_invalid_14), // 0xc0-0xdf - (0xe0, 4, All, COL_MAP.is_invalid_15), // 0xe0-0xef - (0xf0, 0, All, COL_MAP.is_create), - (0xf1, 0, All, COL_MAP.is_call), - (0xf2, 0, All, COL_MAP.is_callcode), - (0xf3, 0, All, COL_MAP.is_return), - (0xf4, 0, All, COL_MAP.is_delegatecall), - (0xf5, 0, All, COL_MAP.is_create2), - (0xf6, 1, User, COL_MAP.is_invalid_16), // 0xf6-0xf7 - (0xf6, 0, Kernel, COL_MAP.is_get_context), - (0xf7, 0, Kernel, COL_MAP.is_set_context), - (0xf8, 1, User, COL_MAP.is_invalid_17), // 0xf8-0xf9 - (0xf8, 0, Kernel, COL_MAP.is_consume_gas), - (0xf9, 0, Kernel, COL_MAP.is_exit_kernel), - (0xfa, 0, All, COL_MAP.is_staticcall), - (0xfb, 0, User, COL_MAP.is_invalid_18), - (0xfb, 0, Kernel, COL_MAP.is_mload_general), - (0xfc, 0, User, COL_MAP.is_invalid_19), - (0xfc, 0, Kernel, COL_MAP.is_mstore_general), - (0xfd, 0, All, COL_MAP.is_revert), - (0xfe, 0, All, COL_MAP.is_invalid_20), - (0xff, 0, All, COL_MAP.is_selfdestruct), + (0xf0, 0, false, COL_MAP.is_create), + (0xf1, 0, false, COL_MAP.is_call), + (0xf2, 0, false, COL_MAP.is_callcode), + (0xf3, 0, false, COL_MAP.is_return), + (0xf4, 0, false, COL_MAP.is_delegatecall), + (0xf5, 0, false, COL_MAP.is_create2), + (0xf6, 0, true, COL_MAP.is_get_context), + (0xf7, 0, true, COL_MAP.is_set_context), + (0xf8, 0, true, COL_MAP.is_consume_gas), + (0xf9, 0, true, COL_MAP.is_exit_kernel), + (0xfa, 0, false, COL_MAP.is_staticcall), + (0xfb, 0, true, COL_MAP.is_mload_general), + (0xfc, 0, true, COL_MAP.is_mstore_general), + (0xfd, 0, false, COL_MAP.is_revert), + (0xff, 0, false, COL_MAP.is_selfdestruct), ]; +/// Bitfield of invalid opcodes, in little-endian order. +pub(crate) const fn invalid_opcodes_user() -> [u8; 32] { + let mut res = [u8::MAX; 32]; // Start with all opcodes marked invalid. + + let mut i = 0; + while i < OPCODES.len() { + let (block_start, lb_block_len, kernel_only, _) = OPCODES[i]; + i += 1; + + if kernel_only { + continue; + } + + let block_len = 1 << lb_block_len; + let block_start = block_start as usize; + let block_end = block_start + block_len; + let mut j = block_start; + while j < block_end { + let byte = j / u8::BITS as usize; + let bit = j % u8::BITS as usize; + res[byte] &= !(1 << bit); // Mark opcode as invalid by zeroing the bit. + j += 1; + } + } + res +} + pub fn generate(lv: &mut CpuColumnsView) { let cycle_filter = lv.is_cpu_cycle; if cycle_filter == F::ZERO { @@ -184,15 +184,19 @@ pub fn generate(lv: &mut CpuColumnsView) { assert!(kernel <= 1); let kernel = kernel != 0; - for (oc, block_length, availability, col) in OPCODES { - let available = match availability { - All => true, - User => !kernel, - Kernel => kernel, - }; + let mut any_flag_set = false; + for (oc, block_length, kernel_only, col) in OPCODES { + let available = !kernel_only || kernel; let opcode_match = top_bits[8 - block_length] == oc; - lv[col] = F::from_bool(available && opcode_match); + let flag = available && opcode_match; + lv[col] = F::from_bool(flag); + if flag && any_flag_set { + panic!("opcode matched multiple flags"); + } + any_flag_set = any_flag_set || flag; } + // is_invalid is a catch-all for opcodes we can't decode. + lv.is_invalid = F::from_bool(!any_flag_set); } /// Break up an opcode (which is 8 bits long) into its eight bits. @@ -230,21 +234,22 @@ pub fn eval_packed_generic( let flag = lv[flag_col]; yield_constr.constraint(cycle_filter * flag * (flag - P::ONES)); } + yield_constr.constraint(cycle_filter * lv.is_invalid * (lv.is_invalid - P::ONES)); // Now check that exactly one is 1. let flag_sum: P = OPCODES .into_iter() .map(|(_, _, _, flag_col)| lv[flag_col]) - .sum(); + .sum::

() + + lv.is_invalid; yield_constr.constraint(cycle_filter * (P::ONES - flag_sum)); // Finally, classify all opcodes, together with the kernel flag, into blocks - for (oc, block_length, availability, col) in OPCODES { - // 0 if the block/flag is available to us (is always available, is user-only and we are in - // user mode, or kernel-only and we are in kernel mode) and 1 otherwise. - let unavailable = match availability { - All => P::ZEROS, - User => kernel_mode, - Kernel => P::ONES - kernel_mode, + for (oc, block_length, kernel_only, col) in OPCODES { + // 0 if the block/flag is available to us (is always available or we are in kernel mode) and + // 1 otherwise. + let unavailable = match kernel_only { + false => P::ZEROS, + true => P::ONES - kernel_mode, }; // 0 if all the opcode bits match, and something in {1, ..., 8}, otherwise. let opcode_mismatch: P = lv @@ -299,6 +304,11 @@ pub fn eval_ext_circuit, const D: usize>( let constr = builder.mul_extension(cycle_filter, constr); yield_constr.constraint(builder, constr); } + { + let constr = builder.mul_sub_extension(lv.is_invalid, lv.is_invalid, lv.is_invalid); + let constr = builder.mul_extension(cycle_filter, constr); + yield_constr.constraint(builder, constr); + } // Now check that exactly one is 1. { let mut constr = builder.one_extension(); @@ -306,18 +316,18 @@ pub fn eval_ext_circuit, const D: usize>( let flag = lv[flag_col]; constr = builder.sub_extension(constr, flag); } + constr = builder.sub_extension(constr, lv.is_invalid); constr = builder.mul_extension(cycle_filter, constr); yield_constr.constraint(builder, constr); } // Finally, classify all opcodes, together with the kernel flag, into blocks - for (oc, block_length, availability, col) in OPCODES { - // 0 if the block/flag is available to us (is always available, is user-only and we are in - // user mode, or kernel-only and we are in kernel mode) and 1 otherwise. - let unavailable = match availability { - All => builder.zero_extension(), - User => kernel_mode, - Kernel => builder.sub_extension(one, kernel_mode), + for (oc, block_length, kernel_only, col) in OPCODES { + // 0 if the block/flag is available to us (is always available or we are in kernel mode) and + // 1 otherwise. + let unavailable = match kernel_only { + false => builder.zero_extension(), + true => builder.sub_extension(one, kernel_mode), }; // 0 if all the opcode bits match, and something in {1, ..., 8}, otherwise. let opcode_mismatch = lv diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index eb55238b..dda006e6 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -15,6 +15,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/core/create.asm"), include_str!("asm/core/create_addresses.asm"), include_str!("asm/core/intrinsic_gas.asm"), + include_str!("asm/core/invalid.asm"), include_str!("asm/core/nonce.asm"), include_str!("asm/core/process_txn.asm"), include_str!("asm/core/terminate.asm"), diff --git a/evm/src/cpu/kernel/asm/core/invalid.asm b/evm/src/cpu/kernel/asm/core/invalid.asm new file mode 100644 index 00000000..6a7f4c17 --- /dev/null +++ b/evm/src/cpu/kernel/asm/core/invalid.asm @@ -0,0 +1,26 @@ +global handle_invalid: + // stack: trap_info + + // if the kernel is trying to execute an invalid instruction, then we've already screwed up and + // there's no chance of getting a useful proof, so we just panic + DUP1 + // stack: trap_info, trap_info + %shr_const(32) + // stack: is_kernel, trap_info + %jumpi(panic) + + // check if the opcode that triggered this trap is _actually_ invalid + // stack: program_counter (is_kernel == 0, so trap_info == program_counter) + %mload_current_code + // stack: opcode + PUSH @INVALID_OPCODES_USER + // stack: invalid_opcodes_user, opcode + SWAP1 + // stack: opcode, invalid_opcodes_user + SHR + %and_const(1) + // stack: opcode_is_invalid + // if the opcode is indeed invalid, then perform an exceptional exit + %jumpi(fault_exception) + // otherwise, panic because this trap should not have been entered + PANIC diff --git a/evm/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm index 2c896345..73bafbee 100644 --- a/evm/src/cpu/kernel/asm/memory/core.asm +++ b/evm/src/cpu/kernel/asm/memory/core.asm @@ -26,6 +26,13 @@ // stack: (empty) %endmacro +// Load a single byte from user code. +%macro mload_current_code + // stack: offset + %mload_current(@SEGMENT_CODE) + // stack: value +%endmacro + // Load a single value from the given segment of kernel (context 0) memory. %macro mload_kernel(segment) // stack: offset diff --git a/evm/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm index e8dd9eb8..13965e39 100644 --- a/evm/src/cpu/kernel/asm/util/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/util/basic_macros.asm @@ -44,6 +44,13 @@ %endrep %endmacro +%macro and_const(c) + // stack: input, ... + PUSH $c + AND + // stack: input & c, ... +%endmacro + %macro add_const(c) // stack: input, ... PUSH $c @@ -101,6 +108,13 @@ // stack: input << c, ... %endmacro +%macro shr_const(c) + // stack: input, ... + PUSH $c + SHR + // stack: input >> c, ... +%endmacro + %macro eq_const(c) // stack: input, ... PUSH $c diff --git a/evm/src/cpu/kernel/constants.rs b/evm/src/cpu/kernel/constants.rs index 5bc5908e..98fe57c6 100644 --- a/evm/src/cpu/kernel/constants.rs +++ b/evm/src/cpu/kernel/constants.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use ethereum_types::U256; use hex_literal::hex; +use crate::cpu::decode::invalid_opcodes_user; use crate::cpu::kernel::context_metadata::ContextMetadata; use crate::cpu::kernel::global_metadata::GlobalMetadata; use crate::cpu::kernel::txn_fields::NormalizedTxnField; @@ -29,6 +30,10 @@ pub fn evm_constants() -> HashMap { for txn_field in ContextMetadata::all() { c.insert(txn_field.var_name().into(), (txn_field as u32).into()); } + c.insert( + "INVALID_OPCODES_USER".into(), + U256::from_little_endian(&invalid_opcodes_user()), + ); c } diff --git a/evm/src/cpu/syscalls.rs b/evm/src/cpu/syscalls.rs index 116713ae..b0b63be8 100644 --- a/evm/src/cpu/syscalls.rs +++ b/evm/src/cpu/syscalls.rs @@ -13,12 +13,16 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::{CpuColumnsView, COL_MAP}; use crate::cpu::kernel::aggregator::KERNEL; -const NUM_SYSCALLS: usize = 2; +const NUM_SYSCALLS: usize = 3; fn make_syscall_list() -> [(usize, usize); NUM_SYSCALLS] { let kernel = Lazy::force(&KERNEL); - [(COL_MAP.is_stop, "sys_stop"), (COL_MAP.is_exp, "sys_exp")] - .map(|(col_index, handler_name)| (col_index, kernel.global_labels[handler_name])) + [ + (COL_MAP.is_stop, "sys_stop"), + (COL_MAP.is_exp, "sys_exp"), + (COL_MAP.is_invalid, "handle_invalid"), + ] + .map(|(col_index, handler_name)| (col_index, kernel.global_labels[handler_name])) } static TRAP_LIST: Lazy<[(usize, usize); NUM_SYSCALLS]> = Lazy::new(make_syscall_list); From ccc2a56b81464054806c54ecbe8b1b855a977b70 Mon Sep 17 00:00:00 2001 From: BGluth Date: Thu, 1 Sep 2022 09:32:18 -0700 Subject: [PATCH 53/95] Added `let_chains` feature gates - Nightly decided to move this behind a feature gate and caused builds to fail. --- evm/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 0a31a7ba..4b3fc6b9 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -2,6 +2,7 @@ #![allow(clippy::needless_range_loop)] #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] +#![feature(let_chains)] #![feature(generic_const_exprs)] pub mod all_stark; From 2c77247d4314aa658c66316063c6179751fbf71b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 29 Aug 2022 18:10:51 -0700 Subject: [PATCH 54/95] Keccak sponge STARK It contains a row for each absorb step of the sponge. --- evm/src/cpu/columns/mod.rs | 18 +- evm/src/cross_table_lookup.rs | 23 +- evm/src/keccak_sponge/columns.rs | 118 +++++ evm/src/keccak_sponge/keccak_sponge_stark.rs | 441 +++++++++++++++++++ evm/src/keccak_sponge/mod.rs | 6 + evm/src/lib.rs | 1 + evm/src/stark_testing.rs | 23 +- evm/src/util.rs | 34 ++ field/src/polynomial/mod.rs | 4 + 9 files changed, 635 insertions(+), 33 deletions(-) create mode 100644 evm/src/keccak_sponge/columns.rs create mode 100644 evm/src/keccak_sponge/keccak_sponge_stark.rs create mode 100644 evm/src/keccak_sponge/mod.rs diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index ad0f6a95..567c5a97 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -3,11 +3,12 @@ use std::borrow::{Borrow, BorrowMut}; use std::fmt::Debug; -use std::mem::{size_of, transmute, transmute_copy, ManuallyDrop}; +use std::mem::{size_of, transmute}; use std::ops::{Index, IndexMut}; use crate::cpu::columns::general::CpuGeneralColumnsView; use crate::memory; +use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; mod general; @@ -157,14 +158,6 @@ pub struct CpuColumnsView { // `u8` is guaranteed to have a `size_of` of 1. pub const NUM_CPU_COLUMNS: usize = size_of::>(); -unsafe fn transmute_no_compile_time_size_checks(value: T) -> U { - debug_assert_eq!(size_of::(), size_of::()); - // Need ManuallyDrop so that `value` is not dropped by this function. - let value = ManuallyDrop::new(value); - // Copy the bit pattern. The original value is no longer safe to use. - transmute_copy(&value) -} - impl From<[T; NUM_CPU_COLUMNS]> for CpuColumnsView { fn from(value: [T; NUM_CPU_COLUMNS]) -> Self { unsafe { transmute_no_compile_time_size_checks(value) } @@ -224,12 +217,7 @@ where } const fn make_col_map() -> CpuColumnsView { - let mut indices_arr = [0; NUM_CPU_COLUMNS]; - let mut i = 0; - while i < NUM_CPU_COLUMNS { - indices_arr[i] = i; - i += 1; - } + let indices_arr = indices_arr::(); unsafe { transmute::<[usize; NUM_CPU_COLUMNS], CpuColumnsView>(indices_arr) } } diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 37a8b5a6..3d67dd58 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::iter::repeat; use anyhow::{ensure, Result}; @@ -38,8 +39,10 @@ impl Column { } } - pub fn singles>(cs: I) -> impl Iterator { - cs.into_iter().map(Self::single) + pub fn singles>>( + cs: I, + ) -> impl Iterator { + cs.into_iter().map(|c| Self::single(*c.borrow())) } pub fn constant(constant: F) -> Self { @@ -74,16 +77,20 @@ impl Column { Self::linear_combination_with_constant(iter, F::ZERO) } - pub fn le_bits>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(F::TWO.powers())) + pub fn le_bits>>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().map(|c| *c.borrow()).zip(F::TWO.powers())) } - pub fn le_bytes>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(F::from_canonical_u16(256).powers())) + pub fn le_bytes>>(cs: I) -> Self { + Self::linear_combination( + cs.into_iter() + .map(|c| *c.borrow()) + .zip(F::from_canonical_u16(256).powers()), + ) } - pub fn sum>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(repeat(F::ONE))) + pub fn sum>>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().map(|c| *c.borrow()).zip(repeat(F::ONE))) } pub fn eval(&self, v: &[P]) -> P diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs new file mode 100644 index 00000000..e564de93 --- /dev/null +++ b/evm/src/keccak_sponge/columns.rs @@ -0,0 +1,118 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::mem::{size_of, transmute}; + +use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; + +pub(crate) const KECCAK_WIDTH_BYTES: usize = 200; +pub(crate) const KECCAK_WIDTH_U32S: usize = KECCAK_WIDTH_BYTES / 4; +pub(crate) const KECCAK_RATE_BITS: usize = 1088; +pub(crate) const KECCAK_RATE_BYTES: usize = KECCAK_RATE_BITS / 8; +pub(crate) const KECCAK_RATE_U32S: usize = KECCAK_RATE_BYTES / 4; +pub(crate) const KECCAK_CAPACITY_BYTES: usize = 64; +pub(crate) const KECCAK_CAPACITY_U32S: usize = KECCAK_CAPACITY_BYTES / 4; + +#[repr(C)] +#[derive(Eq, PartialEq, Debug)] +pub(crate) struct KeccakSpongeColumnsView { + /// 1 if this row represents a dummy operation (for padding the table); 0 otherwise. + pub is_dummy: T, + + /// 1 if this row represents a full input block, i.e. one in which each byte is an input byte, + /// not a padding byte; 0 otherwise. + pub is_full_input_block: T, + + /// 1 if this row represents the final block of a sponge, in which case some or all of the bytes + /// in the block will be padding bytes; 0 otherwise. + pub is_final_block: T, + + // The address at which we will read the input block. + pub context: T, + pub segment: T, + pub virt: T, + + /// The timestamp at which inputs should be read from memory. + pub timestamp: T, + + /// The length of the original input, in bytes. + pub len: T, + + /// The number of input bytes that have already been absorbed prior to this block. + pub already_absorbed_bytes: T, + + /// If this row represents a final block row, the `i`th entry should be 1 if the final chunk of + /// input has length `i` (in other words if `len - already_absorbed == i`), otherwise 0. + /// + /// If this row represents a full input block, this should contain all 0s. + pub is_final_input_len: [T; KECCAK_RATE_BYTES], + + /// The initial rate bits of the sponge, at the start of this step. + pub original_rate_bits: [T; KECCAK_RATE_BITS], + + /// The capacity bits of the sponge, encoded as 32-bit chunks, at the start of this step. + pub original_capacity_u32s: [T; KECCAK_CAPACITY_U32S], + + /// The block being absorbed, which may contain input bytes and/or padding bytes. + pub block_bits: [T; KECCAK_RATE_BITS], + + /// The rate bits of the sponge, after the current block is xor'd in, but before the permutation + /// is applied. + pub xored_rate_u32s: [T; KECCAK_RATE_U32S], + + /// The entire state (rate + capacity) of the sponge, encoded as 32-bit chunks, after the + /// permutation is applied. + pub updated_state_u32s: [T; KECCAK_WIDTH_U32S], +} + +// `u8` is guaranteed to have a `size_of` of 1. +pub const NUM_KECCAK_SPONGE_COLUMNS: usize = size_of::>(); + +impl From<[T; NUM_KECCAK_SPONGE_COLUMNS]> for KeccakSpongeColumnsView { + fn from(value: [T; NUM_KECCAK_SPONGE_COLUMNS]) -> Self { + unsafe { transmute_no_compile_time_size_checks(value) } + } +} + +impl From> for [T; NUM_KECCAK_SPONGE_COLUMNS] { + fn from(value: KeccakSpongeColumnsView) -> Self { + unsafe { transmute_no_compile_time_size_checks(value) } + } +} + +impl Borrow> for [T; NUM_KECCAK_SPONGE_COLUMNS] { + fn borrow(&self) -> &KeccakSpongeColumnsView { + unsafe { transmute(self) } + } +} + +impl BorrowMut> for [T; NUM_KECCAK_SPONGE_COLUMNS] { + fn borrow_mut(&mut self) -> &mut KeccakSpongeColumnsView { + unsafe { transmute(self) } + } +} + +impl Borrow<[T; NUM_KECCAK_SPONGE_COLUMNS]> for KeccakSpongeColumnsView { + fn borrow(&self) -> &[T; NUM_KECCAK_SPONGE_COLUMNS] { + unsafe { transmute(self) } + } +} + +impl BorrowMut<[T; NUM_KECCAK_SPONGE_COLUMNS]> for KeccakSpongeColumnsView { + fn borrow_mut(&mut self) -> &mut [T; NUM_KECCAK_SPONGE_COLUMNS] { + unsafe { transmute(self) } + } +} + +impl Default for KeccakSpongeColumnsView { + fn default() -> Self { + [T::default(); NUM_KECCAK_SPONGE_COLUMNS].into() + } +} + +const fn make_col_map() -> KeccakSpongeColumnsView { + let indices_arr = indices_arr::(); + unsafe { + transmute::<[usize; NUM_KECCAK_SPONGE_COLUMNS], KeccakSpongeColumnsView>(indices_arr) + } +} + +pub(crate) const KECCAK_SPONGE_COL_MAP: KeccakSpongeColumnsView = make_col_map(); diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs new file mode 100644 index 00000000..32edded5 --- /dev/null +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -0,0 +1,441 @@ +use std::borrow::Borrow; +use std::iter; +use std::marker::PhantomData; +use std::mem::size_of; + +use itertools::Itertools; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::timed; +use plonky2::util::timing::TimingTree; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::kernel::keccak_util::keccakf_u32s; +use crate::cross_table_lookup::Column; +use crate::keccak_sponge::columns::*; +use crate::memory::segments::Segment; +use crate::stark::Stark; +use crate::util::{trace_rows_to_poly_values, u32_from_le_bits, u32_to_le_bits, u8_to_le_bits}; +use crate::vars::StarkEvaluationTargets; +use crate::vars::StarkEvaluationVars; + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looked_data() -> Vec> { + let cols = KECCAK_SPONGE_COL_MAP; + let outputs = Column::singles(&cols.updated_state_u32s[..KECCAK_RATE_U32S]); + Column::singles([ + cols.context, + cols.segment, + cols.virt, + cols.timestamp, + cols.len, + ]) + .chain(outputs) + .collect() +} + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looking_keccak() -> Vec> { + let input_rate_cols = (0..KECCAK_WIDTH_U32S) + .map(|i| Column::le_bits(&KECCAK_SPONGE_COL_MAP.original_rate_bits[i * 32..(i + 1) * 32])); + let input_capacity_cols = Column::singles( + (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.original_capacity_u32s[i]), + ); + let output_cols = Column::singles( + (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.updated_state_u32s[i]), + ); + input_rate_cols + .chain(input_capacity_cols) + .chain(output_cols) + .collect() +} + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { + let cols = KECCAK_SPONGE_COL_MAP; + + let mut res = vec![Column::constant(F::ONE)]; // is_read + + res.extend(Column::singles([cols.context, cols.segment, cols.virt])); + + // The i'th input byte being read. + res.push(Column::le_bits(&cols.block_bits[i * 8..(i + 1) * 8])); + + // Since we're reading a single byte, the higher limbs must be zero. + res.extend((1..8).map(|_| Column::zero())); + + res.push(Column::single(cols.timestamp)); + + assert_eq!( + res.len(), + crate::memory::memory_stark::ctl_data::().len() + ); + res +} + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looked_filter() -> Column { + // The CPU table is only interested in our final-block rows, since those contain the final + // sponge output. + Column::single(KECCAK_SPONGE_COL_MAP.is_final_block) +} + +#[allow(unused)] // TODO: Should be used soon. +/// CTL filter for reading the `i`th byte of input from memory. +pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { + // We perform the `i`th read if either + // - this is a full input block, or + // - this is a final block of length `i` or greater + let cols = KECCAK_SPONGE_COL_MAP; + Column::sum(iter::once(&cols.is_full_input_block).chain(&cols.is_final_input_len[i..])) +} + +/// Information about a Keccak sponge operation needed for witness generation. +#[derive(Debug)] +pub(crate) struct KeccakSpongeOp { + // The address at which inputs are read. + pub(crate) context: usize, + pub(crate) segment: Segment, + pub(crate) virt: usize, + + /// The timestamp at which inputs are read. + pub(crate) timestamp: usize, + + /// The length of the input, in bytes. + pub(crate) len: usize, + + /// The input that was read. + pub(crate) input: Vec, +} + +#[derive(Copy, Clone, Default)] +pub(crate) struct KeccakSpongeStark { + f: PhantomData, +} + +impl, const D: usize> KeccakSpongeStark { + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn generate_trace( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec> { + let mut timing = TimingTree::new("generate trace", log::Level::Debug); + + // Generate the witness row-wise. + let trace_rows = timed!( + &mut timing, + "generate trace rows", + self.generate_trace_rows(operations, min_rows) + ); + + let trace_polys = timed!( + &mut timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + + timing.print(); + trace_polys + } + + fn generate_trace_rows( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { + let mut rows = vec![]; + for op in operations { + rows.extend(self.generate_rows_for_op(op)); + } + + let num_rows = rows.len().max(min_rows).next_power_of_two(); + let padding_row = self.generate_padding_row(); + for _ in rows.len()..num_rows { + rows.push(padding_row); + } + rows + } + + fn generate_rows_for_op(&self, op: KeccakSpongeOp) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { + let mut rows = vec![]; + + let mut sponge_state = [0u32; KECCAK_WIDTH_U32S]; + + let mut input_blocks = op.input.chunks_exact(KECCAK_RATE_BYTES); + let mut already_absorbed_bytes = 0; + for block in input_blocks.by_ref() { + let row = self.generate_full_input_row( + &op, + already_absorbed_bytes, + sponge_state, + block.try_into().unwrap(), + ); + + // xor block into sponge_state's rate elements. + let block_u32s = block + .chunks(size_of::()) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())); + for (state_u32, block_u32) in sponge_state.iter_mut().zip(block_u32s) { + *state_u32 ^= block_u32; + } + + sponge_state = row.updated_state_u32s.map(|f| f.to_canonical_u64() as u32); + + rows.push(row.into()); + already_absorbed_bytes += KECCAK_RATE_BYTES; + } + + rows.push( + self.generate_final_row( + &op, + already_absorbed_bytes, + sponge_state, + input_blocks.remainder(), + ) + .into(), + ); + + rows + } + + fn generate_full_input_row( + &self, + op: &KeccakSpongeOp, + already_absorbed_bytes: usize, + sponge_state: [u32; KECCAK_WIDTH_U32S], + block: [u8; KECCAK_RATE_BYTES], + ) -> KeccakSpongeColumnsView { + let mut row = KeccakSpongeColumnsView { + is_full_input_block: F::ONE, + ..Default::default() + }; + + row.block_bits = block + .into_iter() + .flat_map(u8_to_le_bits) + .map(F::from_bool) + .collect_vec() + .try_into() + .unwrap(); + + Self::generate_common_fields(&mut row, op, already_absorbed_bytes, sponge_state); + row + } + + fn generate_final_row( + &self, + op: &KeccakSpongeOp, + already_absorbed_bytes: usize, + sponge_state: [u32; KECCAK_WIDTH_U32S], + final_inputs: &[u8], + ) -> KeccakSpongeColumnsView { + assert_eq!(already_absorbed_bytes + final_inputs.len(), op.len); + + let mut row = KeccakSpongeColumnsView { + is_final_block: F::ONE, + ..Default::default() + }; + + let final_input_bits = final_inputs + .iter() + .flat_map(|x| u8_to_le_bits(*x)) + .map(F::from_bool); + for (block_bit, input_bit) in row.block_bits.iter_mut().zip(final_input_bits) { + *block_bit = input_bit; + } + // pad10*1 rule + row.block_bits[final_inputs.len() * 8] = F::ONE; + row.block_bits[KECCAK_RATE_BITS - 1] = F::ONE; + + row.is_final_input_len[final_inputs.len()] = F::ONE; + + Self::generate_common_fields(&mut row, op, already_absorbed_bytes, sponge_state); + row + } + + /// Generate fields that are common to both full-input-block rows and final-block rows. + fn generate_common_fields( + row: &mut KeccakSpongeColumnsView, + op: &KeccakSpongeOp, + already_absorbed_bytes: usize, + mut sponge_state: [u32; KECCAK_WIDTH_U32S], + ) { + row.context = F::from_canonical_usize(op.context); + row.segment = F::from_canonical_usize(op.segment as usize); + row.virt = F::from_canonical_usize(op.virt); + row.timestamp = F::from_canonical_usize(op.timestamp); + row.len = F::from_canonical_usize(op.len); + row.already_absorbed_bytes = F::from_canonical_usize(already_absorbed_bytes); + + row.original_rate_bits = sponge_state[..KECCAK_RATE_U32S] + .iter() + .flat_map(|x| u32_to_le_bits(*x)) + .map(F::from_bool) + .collect_vec() + .try_into() + .unwrap(); + + row.original_capacity_u32s = sponge_state[KECCAK_RATE_U32S..] + .iter() + .map(|x| F::from_canonical_u32(*x)) + .collect_vec() + .try_into() + .unwrap(); + + let block_u32s = (0..KECCAK_RATE_U32S).map(|i| { + u32_from_le_bits( + row.block_bits[i * 32..(i + 1) * 32] + .iter() + .map(|f| *f == F::ONE) + .collect_vec() + .try_into() + .unwrap(), + ) + }); + + // xor in the block + for (state_i, block_i) in sponge_state.iter_mut().zip(block_u32s) { + *state_i ^= block_i; + } + let xored_rate_u32s: [u32; KECCAK_RATE_U32S] = sponge_state[..KECCAK_RATE_U32S] + .to_vec() + .try_into() + .unwrap(); + row.xored_rate_u32s = xored_rate_u32s.map(F::from_canonical_u32); + + keccakf_u32s(&mut sponge_state); + row.updated_state_u32s = sponge_state.map(F::from_canonical_u32); + } + + fn generate_padding_row(&self) -> [F; NUM_KECCAK_SPONGE_COLUMNS] { + // We just need is_dummy = 1; the other fields will have no effect. + KeccakSpongeColumnsView { + is_dummy: F::ONE, + ..Default::default() + } + .into() + } +} + +impl, const D: usize> Stark for KeccakSpongeStark { + const COLUMNS: usize = NUM_KECCAK_SPONGE_COLUMNS; + + fn eval_packed_generic( + &self, + vars: StarkEvaluationVars, + _yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let _local_values: &KeccakSpongeColumnsView

= vars.local_values.borrow(); + + // TODO: Each flag (full-input block, final block or dummy row) must be boolean. + // TODO: before_rate_bits, block_bits and is_final_input_len must contain booleans. + + // TODO: Sum of row type flags (full-input block, final block or dummy row) should equal 1. + + // TODO: Sum of is_final_input_len should equal is_final_block (which will be 0 or 1). + + // TODO: If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. + // TODO: If this is a final block, the next row's original sponge state should be 0 and already_absorbed_bytes = 0. + + // TODO: If this is a full-input block, the next row's address, time and len must match. + // TODO: If this is a full-input block, the next row's "before" should match our "after" state. + // TODO: If this is a full-input block, the next row's already_absorbed_bytes should be ours plus 136. + + // TODO: A dummy row is always followed by another dummy row, so the prover can't put dummy rows "in between" to avoid the above checks. + + // TODO: is_final_input_len implies `len - already_absorbed == i`. + } + + fn eval_ext_circuit( + &self, + _builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: StarkEvaluationTargets, + _yield_constr: &mut RecursiveConstraintConsumer, + ) { + let _local_values: &KeccakSpongeColumnsView> = + vars.local_values.borrow(); + + // TODO + } + + fn constraint_degree(&self) -> usize { + 3 + } +} + +#[cfg(test)] +mod tests { + use std::borrow::Borrow; + + use anyhow::Result; + use itertools::Itertools; + use keccak_hash::keccak; + use plonky2::field::goldilocks_field::GoldilocksField; + use plonky2::field::types::PrimeField64; + use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + use crate::keccak_sponge::columns::KeccakSpongeColumnsView; + use crate::keccak_sponge::keccak_sponge_stark::{KeccakSpongeOp, KeccakSpongeStark}; + use crate::memory::segments::Segment; + use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; + + #[test] + fn test_stark_degree() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakSpongeStark; + + let stark = S::default(); + test_stark_low_degree(stark) + } + + #[test] + fn test_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakSpongeStark; + + let stark = S::default(); + test_stark_circuit_constraints::(stark) + } + + #[test] + fn test_generation() -> Result<()> { + const D: usize = 2; + type F = GoldilocksField; + type S = KeccakSpongeStark; + + let input = vec![1, 2, 3]; + let expected_output = keccak(&input); + + let op = KeccakSpongeOp { + context: 0, + segment: Segment::Code, + virt: 0, + timestamp: 0, + len: input.len(), + input, + }; + let stark = S::default(); + let rows = stark.generate_rows_for_op(op); + assert_eq!(rows.len(), 1); + let last_row: &KeccakSpongeColumnsView = rows.last().unwrap().borrow(); + let output = last_row.updated_state_u32s[..8] + .iter() + .flat_map(|x| (x.to_canonical_u64() as u32).to_le_bytes()) + .collect_vec(); + + assert_eq!(output, expected_output.0); + Ok(()) + } +} diff --git a/evm/src/keccak_sponge/mod.rs b/evm/src/keccak_sponge/mod.rs new file mode 100644 index 00000000..92b7f0c1 --- /dev/null +++ b/evm/src/keccak_sponge/mod.rs @@ -0,0 +1,6 @@ +//! The Keccak sponge STARK is used to hash a variable amount of data which is read from memory. +//! It connects to the memory STARK to read input data, and to the Keccak-f STARK to evaluate the +//! permutation at each absorption step. + +pub mod columns; +pub mod keccak_sponge_stark; diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 4b3fc6b9..6f332b59 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -15,6 +15,7 @@ pub mod generation; mod get_challenges; pub mod keccak; pub mod keccak_memory; +pub mod keccak_sponge; pub mod logic; pub mod lookup; pub mod memory; diff --git a/evm/src/stark_testing.rs b/evm/src/stark_testing.rs index 5cd83e41..81b0f68f 100644 --- a/evm/src/stark_testing.rs +++ b/evm/src/stark_testing.rs @@ -60,17 +60,20 @@ where }) .collect::>(); - let constraint_eval_degree = PolynomialValues::new(constraint_evals).degree(); - let maximum_degree = WITNESS_SIZE * stark.constraint_degree() - 1; + let constraint_poly_values = PolynomialValues::new(constraint_evals); + if !constraint_poly_values.is_zero() { + let constraint_eval_degree = constraint_poly_values.degree(); + let maximum_degree = WITNESS_SIZE * stark.constraint_degree() - 1; - ensure!( - constraint_eval_degree <= maximum_degree, - "Expected degrees at most {} * {} - 1 = {}, actual {:?}", - WITNESS_SIZE, - stark.constraint_degree(), - maximum_degree, - constraint_eval_degree - ); + ensure!( + constraint_eval_degree <= maximum_degree, + "Expected degrees at most {} * {} - 1 = {}, actual {:?}", + WITNESS_SIZE, + stark.constraint_degree(), + maximum_degree, + constraint_eval_degree + ); + } Ok(()) } diff --git a/evm/src/util.rs b/evm/src/util.rs index ae5281db..89a763e0 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -1,3 +1,5 @@ +use std::mem::{size_of, transmute_copy, ManuallyDrop}; + use ethereum_types::{H160, U256}; use itertools::Itertools; use plonky2::field::extension::Extendable; @@ -67,3 +69,35 @@ pub(crate) fn h160_limbs(h160: H160) -> [F; 5] { .try_into() .unwrap() } + +pub(crate) fn u8_to_le_bits(x: u8) -> [bool; 8] { + std::array::from_fn(|i| ((x >> i) & 1) != 0) +} + +pub(crate) fn u32_to_le_bits(x: u32) -> [bool; 32] { + std::array::from_fn(|i| ((x >> i) & 1) != 0) +} + +pub(crate) fn u32_from_le_bits(bits: [bool; 32]) -> u32 { + bits.into_iter() + .rev() + .fold(0, |acc, b| (acc << 1) | b as u32) +} + +pub(crate) const fn indices_arr() -> [usize; N] { + let mut indices_arr = [0; N]; + let mut i = 0; + while i < N { + indices_arr[i] = i; + i += 1; + } + indices_arr +} + +pub(crate) unsafe fn transmute_no_compile_time_size_checks(value: T) -> U { + debug_assert_eq!(size_of::(), size_of::()); + // Need ManuallyDrop so that `value` is not dropped by this function. + let value = ManuallyDrop::new(value); + // Copy the bit pattern. The original value is no longer safe to use. + transmute_copy(&value) +} diff --git a/field/src/polynomial/mod.rs b/field/src/polynomial/mod.rs index 20f1c318..09ed69c7 100644 --- a/field/src/polynomial/mod.rs +++ b/field/src/polynomial/mod.rs @@ -37,6 +37,10 @@ impl PolynomialValues { Self::constant(F::ZERO, len) } + pub fn is_zero(&self) -> bool { + self.values.iter().all(|x| x.is_zero()) + } + /// Returns the polynomial whole value is one at the given index, and zero elsewhere. pub fn selector(len: usize, index: usize) -> Self { let mut result = Self::zero(len); From f59431da31db132b72a7d13b70156810970345d4 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 2 Sep 2022 11:59:55 -0700 Subject: [PATCH 55/95] clippy fix: 'needless borrow' --- plonky2/src/gadgets/arithmetic_extension.rs | 2 +- plonky2/src/gates/interpolation.rs | 8 ++++---- plonky2/src/gates/low_degree_interpolation.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plonky2/src/gadgets/arithmetic_extension.rs b/plonky2/src/gadgets/arithmetic_extension.rs index 97dedf28..23caeac1 100644 --- a/plonky2/src/gadgets/arithmetic_extension.rs +++ b/plonky2/src/gadgets/arithmetic_extension.rs @@ -505,7 +505,7 @@ impl, const D: usize> SimpleGenerator { fn dependencies(&self) -> Vec { let mut deps = self.numerator.to_target_array().to_vec(); - deps.extend(&self.denominator.to_target_array()); + deps.extend(self.denominator.to_target_array()); deps } diff --git a/plonky2/src/gates/interpolation.rs b/plonky2/src/gates/interpolation.rs index 1983e5aa..a619d1f2 100644 --- a/plonky2/src/gates/interpolation.rs +++ b/plonky2/src/gates/interpolation.rs @@ -100,13 +100,13 @@ impl, const D: usize> Gate for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_base(point); - constraints.extend(&(value - computed_value).to_basefield_array()); + constraints.extend((value - computed_value).to_basefield_array()); } let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(evaluation_point); - constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + constraints.extend((evaluation_value - computed_evaluation_value).to_basefield_array()); constraints } @@ -151,7 +151,7 @@ impl, const D: usize> Gate let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_scalar(builder, point); constraints.extend( - &builder + builder .sub_ext_algebra(value, computed_value) .to_ext_target_array(), ); @@ -161,7 +161,7 @@ impl, const D: usize> Gate let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(builder, evaluation_point); constraints.extend( - &builder + builder .sub_ext_algebra(evaluation_value, computed_evaluation_value) .to_ext_target_array(), ); diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index 217f4f0a..dabadfa4 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -113,7 +113,7 @@ impl, const D: usize> Gate for LowDegreeInter { let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = altered_interpolant.eval_base(point); - constraints.extend(&(value - computed_value).to_basefield_array()); + constraints.extend((value - computed_value).to_basefield_array()); } let evaluation_point_powers = (1..self.num_points()) @@ -128,7 +128,7 @@ impl, const D: usize> Gate for LowDegreeInter } let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); - constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + constraints.extend((evaluation_value - computed_evaluation_value).to_basefield_array()); constraints } @@ -225,7 +225,7 @@ impl, const D: usize> Gate for LowDegreeInter let point = builder.constant_extension(point); let computed_value = altered_interpolant.eval_scalar(builder, point); constraints.extend( - &builder + builder .sub_ext_algebra(value, computed_value) .to_ext_target_array(), ); @@ -253,7 +253,7 @@ impl, const D: usize> Gate for LowDegreeInter // let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); // let computed_evaluation_value = interpolant.eval(builder, evaluation_point); constraints.extend( - &builder + builder .sub_ext_algebra(evaluation_value, computed_evaluation_value) .to_ext_target_array(), ); From df15031145aac71ba9d1e62392d9847eee3fa07b Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 2 Sep 2022 15:40:24 -0700 Subject: [PATCH 56/95] clippy: remove unused 'peekable' --- evm/src/cpu/kernel/parser.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index 9ed578d4..35bde4b6 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -89,14 +89,14 @@ fn parse_macro_call(item: Pair) -> Item { fn parse_repeat(item: Pair) -> Item { assert_eq!(item.as_rule(), Rule::repeat); - let mut inner = item.into_inner().peekable(); + let mut inner = item.into_inner(); let count = parse_literal_u256(inner.next().unwrap()); Item::Repeat(count, inner.map(parse_item).collect()) } fn parse_stack(item: Pair) -> Item { assert_eq!(item.as_rule(), Rule::stack); - let mut inner = item.into_inner().peekable(); + let mut inner = item.into_inner(); let params = inner.next().unwrap(); assert_eq!(params.as_rule(), Rule::paramlist); From 0ac0975d9576fd85816bfa491d88090832dd17e6 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 24 Aug 2022 18:03:13 -0700 Subject: [PATCH 57/95] RandomAccessGate documentation --- plonky2/src/gates/random_access.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs index 2df392bc..b771ba86 100644 --- a/plonky2/src/gates/random_access.rs +++ b/plonky2/src/gates/random_access.rs @@ -24,9 +24,15 @@ use crate::plonk::vars::{ /// A gate for checking that a particular element of a list matches a given value. #[derive(Copy, Clone, Debug)] pub struct RandomAccessGate, const D: usize> { + // Number of bits in the index (log2 of the list size). pub bits: usize, + + // How many separate copies are packed into one gate. pub num_copies: usize, + + // Leftover wires are used as global scratch space to store constants. pub num_extra_constants: usize, + _phantom: PhantomData, } @@ -41,13 +47,18 @@ impl, const D: usize> RandomAccessGate { } pub fn new_from_config(config: &CircuitConfig, bits: usize) -> Self { + // We can access a list of 2^bits elements. let vec_size = 1 << bits; - // Need `(2 + vec_size) * num_copies` routed wires + + // We need `(2 + vec_size) * num_copies` routed wires. let max_copies = (config.num_routed_wires / (2 + vec_size)).min( - // Need `(2 + vec_size + bits) * num_copies` wires + // We need `(2 + vec_size + bits) * num_copies` wires in total. config.num_wires / (2 + vec_size + bits), ); + + // Any leftover wires can be used for constants. let max_extra_constants = config.num_routed_wires - (2 + vec_size) * max_copies; + Self::new( max_copies, bits, @@ -55,20 +66,24 @@ impl, const D: usize> RandomAccessGate { ) } + // Length of the list being accessed. fn vec_size(&self) -> usize { 1 << self.bits } + // For each copy, a wire containing the claimed index of the element. pub fn wire_access_index(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy } + // For each copy, a wire containing the element claimed to be at the index. pub fn wire_claimed_element(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy + 1 } + // For each copy, wires containing the entire list. pub fn wire_list_item(&self, i: usize, copy: usize) -> usize { debug_assert!(i < self.vec_size()); debug_assert!(copy < self.num_copies); @@ -84,6 +99,7 @@ impl, const D: usize> RandomAccessGate { self.start_extra_constants() + i } + // All above wires are routed. pub fn num_routed_wires(&self) -> usize { self.start_extra_constants() + self.num_extra_constants } From b93f92e67e8ed13ee97b1eb14e1d769e0670c275 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 09:24:53 -0700 Subject: [PATCH 58/95] comment fix --- plonky2/src/gates/random_access.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs index b771ba86..40ac955c 100644 --- a/plonky2/src/gates/random_access.rs +++ b/plonky2/src/gates/random_access.rs @@ -24,13 +24,13 @@ use crate::plonk::vars::{ /// A gate for checking that a particular element of a list matches a given value. #[derive(Copy, Clone, Debug)] pub struct RandomAccessGate, const D: usize> { - // Number of bits in the index (log2 of the list size). + /// Number of bits in the index (log2 of the list size). pub bits: usize, - // How many separate copies are packed into one gate. + /// How many separate copies are packed into one gate. pub num_copies: usize, - // Leftover wires are used as global scratch space to store constants. + /// Leftover wires are used as global scratch space to store constants. pub num_extra_constants: usize, _phantom: PhantomData, @@ -218,10 +218,12 @@ impl, const D: usize> Gate for RandomAccessGa .collect() } + // Check that the one remaining element after the folding is the claimed element. debug_assert_eq!(list_items.len(), 1); constraints.push(builder.sub_extension(list_items[0], claimed_element)); } + // Check the constant values. constraints.extend((0..self.num_extra_constants).map(|i| { builder.sub_extension( vars.local_constants[i], From ba28919d66f45abc3eb95ee8601038d7a31c06a8 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 14:00:28 -0700 Subject: [PATCH 59/95] more comment fix --- plonky2/src/gates/random_access.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs index 40ac955c..fa365f16 100644 --- a/plonky2/src/gates/random_access.rs +++ b/plonky2/src/gates/random_access.rs @@ -66,24 +66,24 @@ impl, const D: usize> RandomAccessGate { ) } - // Length of the list being accessed. + /// Length of the list being accessed. fn vec_size(&self) -> usize { 1 << self.bits } - // For each copy, a wire containing the claimed index of the element. + /// For each copy, a wire containing the claimed index of the element. pub fn wire_access_index(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy } - // For each copy, a wire containing the element claimed to be at the index. + /// For each copy, a wire containing the element claimed to be at the index. pub fn wire_claimed_element(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy + 1 } - // For each copy, wires containing the entire list. + /// For each copy, wires containing the entire list. pub fn wire_list_item(&self, i: usize, copy: usize) -> usize { debug_assert!(i < self.vec_size()); debug_assert!(copy < self.num_copies); @@ -99,7 +99,7 @@ impl, const D: usize> RandomAccessGate { self.start_extra_constants() + i } - // All above wires are routed. + /// All above wires are routed. pub fn num_routed_wires(&self) -> usize { self.start_extra_constants() + self.num_extra_constants } From 336046d87211563d3b87135a97d737d35c739f03 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 2 Sep 2022 12:01:16 -0700 Subject: [PATCH 60/95] cleanup for interpolation --- plonky2/src/fri/recursive_verifier.rs | 2 +- plonky2/src/gadgets/interpolation.rs | 178 ------- plonky2/src/gadgets/mod.rs | 1 - .../src/gates/high_degree_interpolation.rs | 363 ++++++++++++++ plonky2/src/gates/interpolation.rs | 453 ++++++------------ plonky2/src/gates/low_degree_interpolation.rs | 2 +- plonky2/src/gates/mod.rs | 1 + 7 files changed, 501 insertions(+), 499 deletions(-) delete mode 100644 plonky2/src/gadgets/interpolation.rs create mode 100644 plonky2/src/gates/high_degree_interpolation.rs diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 1a3739b4..0526ed7e 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -10,7 +10,7 @@ use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpenin use crate::fri::{FriConfig, FriParams}; use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; -use crate::gates::interpolation::HighDegreeInterpolationGate; +use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; diff --git a/plonky2/src/gadgets/interpolation.rs b/plonky2/src/gadgets/interpolation.rs deleted file mode 100644 index b22f3b59..00000000 --- a/plonky2/src/gadgets/interpolation.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::ops::Range; - -use plonky2_field::extension::Extendable; - -use crate::gates::gate::Gate; -use crate::hash::hash_types::RichField; -use crate::iop::ext_target::ExtensionTarget; -use crate::iop::target::Target; -use crate::plonk::circuit_builder::CircuitBuilder; - -/// Trait for gates which interpolate a polynomial, whose points are a (base field) coset of the multiplicative subgroup -/// with the given size, and whose values are extension field elements, given by input wires. -/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. -pub(crate) trait InterpolationGate, const D: usize>: - Gate + Copy -{ - fn new(subgroup_bits: usize) -> Self; - - fn num_points(&self) -> usize; - - /// Wire index of the coset shift. - fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } - - fn end_coeffs(&self) -> usize { - self.start_coeffs() + D * self.num_points() - } -} - -impl, const D: usize> CircuitBuilder { - /// Interpolates a polynomial, whose points are a coset of the multiplicative subgroup with the - /// given size, and whose values are given. Returns the evaluation of the interpolant at - /// `evaluation_point`. - pub(crate) fn interpolate_coset>( - &mut self, - subgroup_bits: usize, - coset_shift: Target, - values: &[ExtensionTarget], - evaluation_point: ExtensionTarget, - ) -> ExtensionTarget { - let gate = G::new(subgroup_bits); - let row = self.add_gate(gate, vec![]); - self.connect(coset_shift, Target::wire(row, gate.wire_shift())); - for (i, &v) in values.iter().enumerate() { - self.connect_extension(v, ExtensionTarget::from_range(row, gate.wires_value(i))); - } - self.connect_extension( - evaluation_point, - ExtensionTarget::from_range(row, gate.wires_evaluation_point()), - ); - - ExtensionTarget::from_range(row, gate.wires_evaluation_value()) - } -} - -#[cfg(test)] -mod tests { - use anyhow::Result; - use plonky2_field::extension::FieldExtension; - use plonky2_field::interpolation::interpolant; - use plonky2_field::types::Field; - - use crate::gates::interpolation::HighDegreeInterpolationGate; - use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; - use crate::iop::witness::PartialWitness; - use crate::plonk::circuit_builder::CircuitBuilder; - use crate::plonk::circuit_data::CircuitConfig; - use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::verifier::verify; - - #[test] - fn test_interpolate() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - type FF = >::FE; - let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - let subgroup_bits = 2; - let len = 1 << subgroup_bits; - let coset_shift = F::rand(); - let g = F::primitive_root_of_unity(subgroup_bits); - let points = F::cyclic_subgroup_coset_known_order(g, coset_shift, len); - let values = FF::rand_vec(len); - - let homogeneous_points = points - .iter() - .zip(values.iter()) - .map(|(&a, &b)| (>::from_basefield(a), b)) - .collect::>(); - - let true_interpolant = interpolant(&homogeneous_points); - - let z = FF::rand(); - let true_eval = true_interpolant.eval(z); - - let coset_shift_target = builder.constant(coset_shift); - - let value_targets = values - .iter() - .map(|&v| (builder.constant_extension(v))) - .collect::>(); - - let zt = builder.constant_extension(z); - - let eval_hd = builder.interpolate_coset::>( - subgroup_bits, - coset_shift_target, - &value_targets, - zt, - ); - let eval_ld = builder.interpolate_coset::>( - subgroup_bits, - coset_shift_target, - &value_targets, - zt, - ); - let true_eval_target = builder.constant_extension(true_eval); - builder.connect_extension(eval_hd, true_eval_target); - builder.connect_extension(eval_ld, true_eval_target); - - let data = builder.build::(); - let proof = data.prove(pw)?; - - verify(proof, &data.verifier_only, &data.common) - } -} diff --git a/plonky2/src/gadgets/mod.rs b/plonky2/src/gadgets/mod.rs index 6309eb3d..a3e50c4e 100644 --- a/plonky2/src/gadgets/mod.rs +++ b/plonky2/src/gadgets/mod.rs @@ -1,7 +1,6 @@ pub mod arithmetic; pub mod arithmetic_extension; pub mod hash; -pub mod interpolation; pub mod polynomial; pub mod random_access; pub mod range_check; diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs new file mode 100644 index 00000000..1c78a6e6 --- /dev/null +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -0,0 +1,363 @@ +use std::marker::PhantomData; +use std::ops::Range; + +use plonky2_field::extension::algebra::PolynomialCoeffsAlgebra; +use plonky2_field::extension::{Extendable, FieldExtension}; +use plonky2_field::interpolation::interpolant; +use plonky2_field::polynomial::PolynomialCoeffs; + +use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; +use crate::gates::gate::Gate; +use crate::gates::interpolation::InterpolationGate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; + +/// One of the instantiations of `InterpolationGate`: allows constraints of variable +/// degree, up to `1<, const D: usize> { + pub subgroup_bits: usize, + _phantom: PhantomData, +} + +impl, const D: usize> InterpolationGate + for HighDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } + } + + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } +} + +impl, const D: usize> HighDegreeInterpolationGate { + /// End of wire indices, exclusive. + fn end(&self) -> usize { + self.start_coeffs() + self.num_points() * D + } + + /// The domain of the points we're interpolating. + fn coset(&self, shift: F) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. + g.powers().take(size).map(move |x| x * shift) + } + + /// The domain of the points we're interpolating. + fn coset_ext(&self, shift: F::Extension) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + g.powers().take(size).map(move |x| shift.scalar_mul(x)) + } + + /// The domain of the points we're interpolating. + fn coset_ext_circuit( + &self, + builder: &mut CircuitBuilder, + shift: ExtensionTarget, + ) -> Vec> { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + g.powers() + .take(size) + .map(move |x| { + let subgroup_element = builder.constant(x); + builder.scalar_mul_ext(subgroup_element, shift) + }) + .collect() + } +} + +impl, const D: usize> Gate + for HighDegreeInterpolationGate +{ + fn id(&self) -> String { + format!("{:?}", self, D) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffsAlgebra::new(coeffs); + + let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let computed_value = interpolant.eval_base(point); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(evaluation_point); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffs::new(coeffs); + + let coset = self.coset(vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { + let value = vars.get_local_ext(self.wires_value(i)); + let computed_value = interpolant.eval_base(point); + yield_constr.many((value - computed_value).to_basefield_array()); + } + + let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(evaluation_point); + yield_constr.many((evaluation_value - computed_evaluation_value).to_basefield_array()); + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); + + let coset = self.coset_ext_circuit(builder, vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let computed_value = interpolant.eval_scalar(builder, point); + constraints.extend( + &builder + .sub_ext_algebra(value, computed_value) + .to_ext_target_array(), + ); + } + + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(builder, evaluation_point); + constraints.extend( + &builder + .sub_ext_algebra(evaluation_value, computed_evaluation_value) + .to_ext_target_array(), + ); + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec>> { + let gen = InterpolationGenerator:: { + row, + gate: *self, + _phantom: PhantomData, + }; + vec![Box::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.end() + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + // The highest power of x is `num_points - 1`, and then multiplication by the coefficient + // adds 1. + self.num_points() + } + + fn num_constraints(&self) -> usize { + // num_points * D constraints to check for consistency between the coefficients and the + // point-value pairs, plus D constraints for the evaluation value. + self.num_points() * D + D + } +} + +#[derive(Debug)] +struct InterpolationGenerator, const D: usize> { + row: usize, + gate: HighDegreeInterpolationGate, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator + for InterpolationGenerator +{ + fn dependencies(&self) -> Vec { + let local_target = |column| { + Target::Wire(Wire { + row: self.row, + column, + }) + }; + + let local_targets = |columns: Range| columns.map(local_target); + + let num_points = self.gate.num_points(); + let mut deps = Vec::with_capacity(1 + D + num_points * D); + + deps.push(local_target(self.gate.wire_shift())); + deps.extend(local_targets(self.gate.wires_evaluation_point())); + for i in 0..num_points { + deps.extend(local_targets(self.gate.wires_value(i))); + } + deps + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_wire = |column| Wire { + row: self.row, + column, + }; + + let get_local_wire = |column| witness.get_wire(local_wire(column)); + + let get_local_ext = |wire_range: Range| { + debug_assert_eq!(wire_range.len(), D); + let values = wire_range.map(get_local_wire).collect::>(); + let arr = values.try_into().unwrap(); + F::Extension::from_basefield_array(arr) + }; + + // Compute the interpolant. + let points = self.gate.coset(get_local_wire(self.gate.wire_shift())); + let points = points + .into_iter() + .enumerate() + .map(|(i, point)| (point.into(), get_local_ext(self.gate.wires_value(i)))) + .collect::>(); + let interpolant = interpolant(&points); + + for (i, &coeff) in interpolant.coeffs.iter().enumerate() { + let wires = self.gate.wires_coeff(i).map(local_wire); + out_buffer.set_ext_wires(wires, coeff); + } + + let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); + let evaluation_value = interpolant.eval(evaluation_point); + let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); + out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use anyhow::Result; + use plonky2_field::goldilocks_field::GoldilocksField; + use plonky2_field::polynomial::PolynomialCoeffs; + use plonky2_field::types::Field; + + use crate::gadgets::interpolation::InterpolationGate; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::hash::hash_types::HashOut; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::vars::EvaluationVars; + + #[test] + fn wire_indices() { + let gate = HighDegreeInterpolationGate:: { + subgroup_bits: 1, + _phantom: PhantomData, + }; + + // The exact indices aren't really important, but we want to make sure we don't have any + // overlaps or gaps. + assert_eq!(gate.wire_shift(), 0); + assert_eq!(gate.wires_value(0), 1..5); + assert_eq!(gate.wires_value(1), 5..9); + assert_eq!(gate.wires_evaluation_point(), 9..13); + assert_eq!(gate.wires_evaluation_value(), 13..17); + assert_eq!(gate.wires_coeff(0), 17..21); + assert_eq!(gate.wires_coeff(1), 21..25); + assert_eq!(gate.num_wires(), 25); + } + + #[test] + fn low_degree() { + test_low_degree::(HighDegreeInterpolationGate::new(2)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(HighDegreeInterpolationGate::new(2)) + } + + #[test] + fn test_gate_constraint() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. + fn get_wires( + gate: &HighDegreeInterpolationGate, + shift: F, + coeffs: PolynomialCoeffs, + eval_point: FF, + ) -> Vec { + let points = gate.coset(shift); + let mut v = vec![shift]; + for x in points { + v.extend(coeffs.eval(x.into()).0); + } + v.extend(eval_point.0); + v.extend(coeffs.eval(eval_point).0); + for i in 0..coeffs.len() { + v.extend(coeffs.coeffs[i].0); + } + v.iter().map(|&x| x.into()).collect() + } + + // Get a working row for InterpolationGate. + let shift = F::rand(); + let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); + let eval_point = FF::rand(); + let gate = HighDegreeInterpolationGate::::new(1); + let vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(&gate, shift, coeffs, eval_point), + public_inputs_hash: &HashOut::rand(), + }; + + assert!( + gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + } +} diff --git a/plonky2/src/gates/interpolation.rs b/plonky2/src/gates/interpolation.rs index a619d1f2..d417fa6b 100644 --- a/plonky2/src/gates/interpolation.rs +++ b/plonky2/src/gates/interpolation.rs @@ -1,361 +1,178 @@ -use std::marker::PhantomData; use std::ops::Range; -use plonky2_field::extension::algebra::PolynomialCoeffsAlgebra; -use plonky2_field::extension::{Extendable, FieldExtension}; -use plonky2_field::interpolation::interpolant; -use plonky2_field::polynomial::PolynomialCoeffs; +use plonky2_field::extension::Extendable; -use crate::gadgets::interpolation::InterpolationGate; -use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; -use crate::gates::util::StridedConstraintConsumer; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; -use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::iop::target::Target; -use crate::iop::wire::Wire; -use crate::iop::witness::{PartitionWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; -use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; -/// Interpolation gate with constraints of degree at most `1<, const D: usize> { - pub subgroup_bits: usize, - _phantom: PhantomData, -} - -impl, const D: usize> InterpolationGate - for HighDegreeInterpolationGate +/// Trait for gates which interpolate a polynomial, whose points are a (base field) coset of the multiplicative subgroup +/// with the given size, and whose values are extension field elements, given by input wires. +/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. +pub(crate) trait InterpolationGate, const D: usize>: + Gate + Copy { - fn new(subgroup_bits: usize) -> Self { - Self { - subgroup_bits, - _phantom: PhantomData, - } - } + fn new(subgroup_bits: usize) -> Self; - fn num_points(&self) -> usize { - 1 << self.subgroup_bits - } -} + fn num_points(&self) -> usize; -impl, const D: usize> HighDegreeInterpolationGate { - /// End of wire indices, exclusive. - fn end(&self) -> usize { - self.start_coeffs() + self.num_points() * D - } - - /// The domain of the points we're interpolating. - fn coset(&self, shift: F) -> impl Iterator { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. - g.powers().take(size).map(move |x| x * shift) - } - - /// The domain of the points we're interpolating. - fn coset_ext(&self, shift: F::Extension) -> impl Iterator { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - g.powers().take(size).map(move |x| shift.scalar_mul(x)) - } - - /// The domain of the points we're interpolating. - fn coset_ext_circuit( - &self, - builder: &mut CircuitBuilder, - shift: ExtensionTarget, - ) -> Vec> { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - g.powers() - .take(size) - .map(move |x| { - let subgroup_element = builder.constant(x); - builder.scalar_mul_ext(subgroup_element, shift) - }) - .collect() - } -} - -impl, const D: usize> Gate - for HighDegreeInterpolationGate -{ - fn id(&self) -> String { - format!("{:?}", self, D) - } - - fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { - let mut constraints = Vec::with_capacity(self.num_constraints()); - - let coeffs = (0..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffsAlgebra::new(coeffs); - - let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { - let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = interpolant.eval_base(point); - constraints.extend((value - computed_value).to_basefield_array()); - } - - let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); - let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(evaluation_point); - constraints.extend((evaluation_value - computed_evaluation_value).to_basefield_array()); - - constraints - } - - fn eval_unfiltered_base_one( - &self, - vars: EvaluationVarsBase, - mut yield_constr: StridedConstraintConsumer, - ) { - let coeffs = (0..self.num_points()) - .map(|i| vars.get_local_ext(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffs::new(coeffs); - - let coset = self.coset(vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { - let value = vars.get_local_ext(self.wires_value(i)); - let computed_value = interpolant.eval_base(point); - yield_constr.many((value - computed_value).to_basefield_array()); - } - - let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); - let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(evaluation_point); - yield_constr.many((evaluation_value - computed_evaluation_value).to_basefield_array()); - } - - fn eval_unfiltered_circuit( - &self, - builder: &mut CircuitBuilder, - vars: EvaluationTargets, - ) -> Vec> { - let mut constraints = Vec::with_capacity(self.num_constraints()); - - let coeffs = (0..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); - - let coset = self.coset_ext_circuit(builder, vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { - let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = interpolant.eval_scalar(builder, point); - constraints.extend( - builder - .sub_ext_algebra(value, computed_value) - .to_ext_target_array(), - ); - } - - let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); - let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(builder, evaluation_point); - constraints.extend( - builder - .sub_ext_algebra(evaluation_value, computed_evaluation_value) - .to_ext_target_array(), - ); - - constraints - } - - fn generators(&self, row: usize, _local_constants: &[F]) -> Vec>> { - let gen = InterpolationGenerator:: { - row, - gate: *self, - _phantom: PhantomData, - }; - vec![Box::new(gen.adapter())] - } - - fn num_wires(&self) -> usize { - self.end() - } - - fn num_constants(&self) -> usize { + /// Wire index of the coset shift. + fn wire_shift(&self) -> usize { 0 } - fn degree(&self) -> usize { - // The highest power of x is `num_points - 1`, and then multiplication by the coefficient - // adds 1. - self.num_points() + fn start_values(&self) -> usize { + 1 } - fn num_constraints(&self) -> usize { - // num_points * D constraints to check for consistency between the coefficients and the - // point-value pairs, plus D constraints for the evaluation value. - self.num_points() * D + D + /// Wire indices of the `i`th interpolant value. + fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } + + fn end_coeffs(&self) -> usize { + self.start_coeffs() + D * self.num_points() } } -#[derive(Debug)] -struct InterpolationGenerator, const D: usize> { - row: usize, - gate: HighDegreeInterpolationGate, - _phantom: PhantomData, -} - -impl, const D: usize> SimpleGenerator - for InterpolationGenerator -{ - fn dependencies(&self) -> Vec { - let local_target = |column| { - Target::Wire(Wire { - row: self.row, - column, - }) - }; - - let local_targets = |columns: Range| columns.map(local_target); - - let num_points = self.gate.num_points(); - let mut deps = Vec::with_capacity(1 + D + num_points * D); - - deps.push(local_target(self.gate.wire_shift())); - deps.extend(local_targets(self.gate.wires_evaluation_point())); - for i in 0..num_points { - deps.extend(local_targets(self.gate.wires_value(i))); +impl, const D: usize> CircuitBuilder { + /// Interpolates a polynomial, whose points are a coset of the multiplicative subgroup with the + /// given size, and whose values are given. Returns the evaluation of the interpolant at + /// `evaluation_point`. + pub(crate) fn interpolate_coset>( + &mut self, + subgroup_bits: usize, + coset_shift: Target, + values: &[ExtensionTarget], + evaluation_point: ExtensionTarget, + ) -> ExtensionTarget { + let gate = G::new(subgroup_bits); + let row = self.add_gate(gate, vec![]); + self.connect(coset_shift, Target::wire(row, gate.wire_shift())); + for (i, &v) in values.iter().enumerate() { + self.connect_extension(v, ExtensionTarget::from_range(row, gate.wires_value(i))); } - deps - } + self.connect_extension( + evaluation_point, + ExtensionTarget::from_range(row, gate.wires_evaluation_point()), + ); - fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let local_wire = |column| Wire { - row: self.row, - column, - }; - - let get_local_wire = |column| witness.get_wire(local_wire(column)); - - let get_local_ext = |wire_range: Range| { - debug_assert_eq!(wire_range.len(), D); - let values = wire_range.map(get_local_wire).collect::>(); - let arr = values.try_into().unwrap(); - F::Extension::from_basefield_array(arr) - }; - - // Compute the interpolant. - let points = self.gate.coset(get_local_wire(self.gate.wire_shift())); - let points = points - .into_iter() - .enumerate() - .map(|(i, point)| (point.into(), get_local_ext(self.gate.wires_value(i)))) - .collect::>(); - let interpolant = interpolant(&points); - - for (i, &coeff) in interpolant.coeffs.iter().enumerate() { - let wires = self.gate.wires_coeff(i).map(local_wire); - out_buffer.set_ext_wires(wires, coeff); - } - - let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); - let evaluation_value = interpolant.eval(evaluation_point); - let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); - out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); + ExtensionTarget::from_range(row, gate.wires_evaluation_value()) } } #[cfg(test)] mod tests { - use std::marker::PhantomData; - use anyhow::Result; - use plonky2_field::goldilocks_field::GoldilocksField; - use plonky2_field::polynomial::PolynomialCoeffs; + use plonky2_field::extension::FieldExtension; + use plonky2_field::interpolation::interpolant; use plonky2_field::types::Field; - use crate::gadgets::interpolation::InterpolationGate; - use crate::gates::gate::Gate; - use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; - use crate::gates::interpolation::HighDegreeInterpolationGate; - use crate::hash::hash_types::HashOut; + use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::vars::EvaluationVars; + use crate::plonk::verifier::verify; #[test] - fn wire_indices() { - let gate = HighDegreeInterpolationGate:: { - subgroup_bits: 1, - _phantom: PhantomData, - }; - - // The exact indices aren't really important, but we want to make sure we don't have any - // overlaps or gaps. - assert_eq!(gate.wire_shift(), 0); - assert_eq!(gate.wires_value(0), 1..5); - assert_eq!(gate.wires_value(1), 5..9); - assert_eq!(gate.wires_evaluation_point(), 9..13); - assert_eq!(gate.wires_evaluation_value(), 13..17); - assert_eq!(gate.wires_coeff(0), 17..21); - assert_eq!(gate.wires_coeff(1), 21..25); - assert_eq!(gate.num_wires(), 25); - } - - #[test] - fn low_degree() { - test_low_degree::(HighDegreeInterpolationGate::new(2)); - } - - #[test] - fn eval_fns() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - test_eval_fns::(HighDegreeInterpolationGate::new(2)) - } - - #[test] - fn test_gate_constraint() { + fn test_interpolate() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; type FF = >::FE; + let config = CircuitConfig::standard_recursion_config(); + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); - /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. - fn get_wires( - gate: &HighDegreeInterpolationGate, - shift: F, - coeffs: PolynomialCoeffs, - eval_point: FF, - ) -> Vec { - let points = gate.coset(shift); - let mut v = vec![shift]; - for x in points { - v.extend(coeffs.eval(x.into()).0); - } - v.extend(eval_point.0); - v.extend(coeffs.eval(eval_point).0); - for i in 0..coeffs.len() { - v.extend(coeffs.coeffs[i].0); - } - v.iter().map(|&x| x.into()).collect() - } + let subgroup_bits = 2; + let len = 1 << subgroup_bits; + let coset_shift = F::rand(); + let g = F::primitive_root_of_unity(subgroup_bits); + let points = F::cyclic_subgroup_coset_known_order(g, coset_shift, len); + let values = FF::rand_vec(len); - // Get a working row for InterpolationGate. - let shift = F::rand(); - let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); - let eval_point = FF::rand(); - let gate = HighDegreeInterpolationGate::::new(1); - let vars = EvaluationVars { - local_constants: &[], - local_wires: &get_wires(&gate, shift, coeffs, eval_point), - public_inputs_hash: &HashOut::rand(), - }; + let homogeneous_points = points + .iter() + .zip(values.iter()) + .map(|(&a, &b)| (>::from_basefield(a), b)) + .collect::>(); - assert!( - gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), - "Gate constraints are not satisfied." + let true_interpolant = interpolant(&homogeneous_points); + + let z = FF::rand(); + let true_eval = true_interpolant.eval(z); + + let coset_shift_target = builder.constant(coset_shift); + + let value_targets = values + .iter() + .map(|&v| (builder.constant_extension(v))) + .collect::>(); + + let zt = builder.constant_extension(z); + + let eval_hd = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, ); + let eval_ld = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, + ); + let true_eval_target = builder.constant_extension(true_eval); + builder.connect_extension(eval_hd, true_eval_target); + builder.connect_extension(eval_ld, true_eval_target); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) } } diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index dabadfa4..3ffe5922 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -7,9 +7,9 @@ use plonky2_field::interpolation::interpolant; use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; -use crate::gadgets::interpolation::InterpolationGate; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::util::StridedConstraintConsumer; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; diff --git a/plonky2/src/gates/mod.rs b/plonky2/src/gates/mod.rs index 48e319ef..1d2fc058 100644 --- a/plonky2/src/gates/mod.rs +++ b/plonky2/src/gates/mod.rs @@ -7,6 +7,7 @@ pub mod base_sum; pub mod constant; pub mod exponentiation; pub mod gate; +pub mod high_degree_interpolation; pub mod interpolation; pub mod low_degree_interpolation; pub mod multiplication_extension; From 3e388658280e5716924a3530875fad836179cef9 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 31 Aug 2022 14:31:36 -0700 Subject: [PATCH 61/95] documentation --- plonky2/src/gates/low_degree_interpolation.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index 3ffe5922..6e738bab 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -20,8 +20,9 @@ use crate::iop::witness::{PartitionWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; -/// Interpolation gate with constraints of degree 2. -/// `eval_unfiltered_recursively` uses more gates than `HighDegreeInterpolationGate`. +/// One of the instantiations of `InterpolationGate`: all constraints are degree <= 2. +/// The lower degree is a tradeoff for more gates (`eval_unfiltered_recursively` for +/// this version uses more gates than `LowDegreeInterpolationGate`). #[derive(Copy, Clone, Debug)] pub struct LowDegreeInterpolationGate, const D: usize> { pub subgroup_bits: usize, From 1ca46f76e56cafc4826a7950aec0092914a2ba94 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 31 Aug 2022 14:35:40 -0700 Subject: [PATCH 62/95] fixes --- plonky2/src/fri/recursive_verifier.rs | 2 +- plonky2/src/gates/high_degree_interpolation.rs | 2 +- plonky2/src/gates/low_degree_interpolation.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 0526ed7e..11e151ec 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -8,7 +8,7 @@ use crate::fri::proof::{ }; use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpeningsTarget}; use crate::fri::{FriConfig, FriParams}; -use crate::gadgets::interpolation::InterpolationGate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs index 1c78a6e6..57f42545 100644 --- a/plonky2/src/gates/high_degree_interpolation.rs +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -277,10 +277,10 @@ mod tests { use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; - use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::gates::interpolation::InterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use crate::plonk::vars::EvaluationVars; diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index 6e738bab..3edc4175 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -388,9 +388,9 @@ mod tests { use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; - use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::interpolation::InterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; From c80bc9f2b495920f75fae81077b362504576743f Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 31 Aug 2022 14:38:03 -0700 Subject: [PATCH 63/95] fmt --- plonky2/src/fri/recursive_verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 11e151ec..ac7e3a87 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -8,9 +8,9 @@ use crate::fri::proof::{ }; use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpeningsTarget}; use crate::fri::{FriConfig, FriParams}; -use crate::gates::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; From dc69d6afbd378315c1b29f56d2c764fdeb11e403 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 2 Sep 2022 12:01:55 -0700 Subject: [PATCH 64/95] clippy fix: 'needless borrow' --- plonky2/src/gates/high_degree_interpolation.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs index 57f42545..bcdf2276 100644 --- a/plonky2/src/gates/high_degree_interpolation.rs +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -102,13 +102,13 @@ impl, const D: usize> Gate for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_base(point); - constraints.extend(&(value - computed_value).to_basefield_array()); + constraints.extend((value - computed_value).to_basefield_array()); } let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(evaluation_point); - constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + constraints.extend((evaluation_value - computed_evaluation_value).to_basefield_array()); constraints } @@ -153,7 +153,7 @@ impl, const D: usize> Gate let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_scalar(builder, point); constraints.extend( - &builder + builder .sub_ext_algebra(value, computed_value) .to_ext_target_array(), ); @@ -163,7 +163,7 @@ impl, const D: usize> Gate let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(builder, evaluation_point); constraints.extend( - &builder + builder .sub_ext_algebra(evaluation_value, computed_evaluation_value) .to_ext_target_array(), ); From 0a3455ce48ad5de41a10a9b632ce7e413a91d76d Mon Sep 17 00:00:00 2001 From: BGluth Date: Fri, 2 Sep 2022 16:18:54 -0700 Subject: [PATCH 65/95] Added a few derives to `Trie` types - A downstream project needed `Hash` on `Nibbles`, but I also thought it made sense to derive a few other core types as well. --- evm/src/generation/partial_trie.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evm/src/generation/partial_trie.rs b/evm/src/generation/partial_trie.rs index 96751310..5e52e1e0 100644 --- a/evm/src/generation/partial_trie.rs +++ b/evm/src/generation/partial_trie.rs @@ -1,5 +1,6 @@ use ethereum_types::U256; +#[derive(Clone, Debug)] /// A partial trie, or a sub-trie thereof. This mimics the structure of an Ethereum trie, except /// with an additional `Hash` node type, representing a node whose data is not needed to process /// our transaction. @@ -22,6 +23,7 @@ pub enum PartialTrie { Leaf { nibbles: Nibbles, value: Vec }, } +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] /// A sequence of nibbles. pub struct Nibbles { /// The number of nibbles in this sequence. From d392ec04e70fd4cf1280e381aa93a9cf3f06af53 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 3 Sep 2022 23:02:06 -0700 Subject: [PATCH 66/95] Feedback --- evm/src/keccak_sponge/columns.rs | 3 -- evm/src/keccak_sponge/keccak_sponge_stark.rs | 56 ++++++++------------ 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs index e564de93..1cf39138 100644 --- a/evm/src/keccak_sponge/columns.rs +++ b/evm/src/keccak_sponge/columns.rs @@ -14,9 +14,6 @@ pub(crate) const KECCAK_CAPACITY_U32S: usize = KECCAK_CAPACITY_BYTES / 4; #[repr(C)] #[derive(Eq, PartialEq, Debug)] pub(crate) struct KeccakSpongeColumnsView { - /// 1 if this row represents a dummy operation (for padding the table); 0 otherwise. - pub is_dummy: T, - /// 1 if this row represents a full input block, i.e. one in which each byte is an input byte, /// not a padding byte; 0 otherwise. pub is_full_input_block: T, diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs index 32edded5..19c80696 100644 --- a/evm/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -1,7 +1,6 @@ use std::borrow::Borrow; use std::iter; use std::marker::PhantomData; -use std::mem::size_of; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -26,7 +25,7 @@ use crate::vars::StarkEvaluationVars; #[allow(unused)] // TODO: Should be used soon. pub(crate) fn ctl_looked_data() -> Vec> { let cols = KECCAK_SPONGE_COL_MAP; - let outputs = Column::singles(&cols.updated_state_u32s[..KECCAK_RATE_U32S]); + let outputs = Column::singles(&cols.updated_state_u32s[..8]); Column::singles([ cols.context, cols.segment, @@ -40,10 +39,10 @@ pub(crate) fn ctl_looked_data() -> Vec> { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn ctl_looking_keccak() -> Vec> { - let input_rate_cols = (0..KECCAK_WIDTH_U32S) + let input_rate_cols = (0..KECCAK_RATE_U32S) .map(|i| Column::le_bits(&KECCAK_SPONGE_COL_MAP.original_rate_bits[i * 32..(i + 1) * 32])); let input_capacity_cols = Column::singles( - (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.original_capacity_u32s[i]), + (0..KECCAK_CAPACITY_U32S).map(|i| KECCAK_SPONGE_COL_MAP.original_capacity_u32s[i]), ); let output_cols = Column::singles( (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.updated_state_u32s[i]), @@ -60,7 +59,13 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { let mut res = vec![Column::constant(F::ONE)]; // is_read - res.extend(Column::singles([cols.context, cols.segment, cols.virt])); + res.extend(Column::singles([cols.context, cols.segment])); + + // The address of the byte being read is `virt + already_absorbed_bytes + i`. + res.push(Column::linear_combination_with_constant( + [(cols.virt, F::ONE), (cols.already_absorbed_bytes, F::ONE)], + F::from_canonical_usize(i), + )); // The i'th input byte being read. res.push(Column::le_bits(&cols.block_bits[i * 8..(i + 1) * 8])); @@ -148,17 +153,13 @@ impl, const D: usize> KeccakSpongeStark { operations: Vec, min_rows: usize, ) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { - let mut rows = vec![]; - for op in operations { - rows.extend(self.generate_rows_for_op(op)); - } - - let num_rows = rows.len().max(min_rows).next_power_of_two(); - let padding_row = self.generate_padding_row(); - for _ in rows.len()..num_rows { - rows.push(padding_row); - } - rows + let num_rows = operations.len().max(min_rows).next_power_of_two(); + operations + .into_iter() + .flat_map(|op| self.generate_rows_for_op(op)) + .chain(iter::repeat(self.generate_padding_row())) + .take(num_rows) + .collect() } fn generate_rows_for_op(&self, op: KeccakSpongeOp) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { @@ -176,14 +177,6 @@ impl, const D: usize> KeccakSpongeStark { block.try_into().unwrap(), ); - // xor block into sponge_state's rate elements. - let block_u32s = block - .chunks(size_of::()) - .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())); - for (state_u32, block_u32) in sponge_state.iter_mut().zip(block_u32s) { - *state_u32 ^= block_u32; - } - sponge_state = row.updated_state_u32s.map(|f| f.to_canonical_u64() as u32); rows.push(row.into()); @@ -291,7 +284,7 @@ impl, const D: usize> KeccakSpongeStark { u32_from_le_bits( row.block_bits[i * 32..(i + 1) * 32] .iter() - .map(|f| *f == F::ONE) + .map(Field::is_one) .collect_vec() .try_into() .unwrap(), @@ -313,12 +306,9 @@ impl, const D: usize> KeccakSpongeStark { } fn generate_padding_row(&self) -> [F; NUM_KECCAK_SPONGE_COLUMNS] { - // We just need is_dummy = 1; the other fields will have no effect. - KeccakSpongeColumnsView { - is_dummy: F::ONE, - ..Default::default() - } - .into() + // The default instance has is_full_input_block = is_final_block = 0, + // indicating that it's a dummy/padding row. + KeccakSpongeColumnsView::default().into() } } @@ -335,11 +325,9 @@ impl, const D: usize> Stark for KeccakSpongeS { let _local_values: &KeccakSpongeColumnsView

= vars.local_values.borrow(); - // TODO: Each flag (full-input block, final block or dummy row) must be boolean. + // TODO: Each flag (full-input block, final block or implied dummy flag) must be boolean. // TODO: before_rate_bits, block_bits and is_final_input_len must contain booleans. - // TODO: Sum of row type flags (full-input block, final block or dummy row) should equal 1. - // TODO: Sum of is_final_input_len should equal is_final_block (which will be 0 or 1). // TODO: If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. From c9cfcecc9fa08b9405a50ec19724487b69e6d90e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 16:53:04 -0700 Subject: [PATCH 67/95] Logic CTL for xor --- evm/src/cross_table_lookup.rs | 4 + evm/src/keccak_sponge/columns.rs | 10 +- evm/src/keccak_sponge/keccak_sponge_stark.rs | 115 +++++++++++++------ evm/src/util.rs | 14 --- 4 files changed, 86 insertions(+), 57 deletions(-) diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 3d67dd58..83f2083d 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -56,6 +56,10 @@ impl Column { Self::constant(F::ZERO) } + pub fn one() -> Self { + Self::constant(F::ONE) + } + pub fn linear_combination_with_constant>( iter: I, constant: F, diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs index 1cf39138..3fb944fc 100644 --- a/evm/src/keccak_sponge/columns.rs +++ b/evm/src/keccak_sponge/columns.rs @@ -42,16 +42,16 @@ pub(crate) struct KeccakSpongeColumnsView { /// If this row represents a full input block, this should contain all 0s. pub is_final_input_len: [T; KECCAK_RATE_BYTES], - /// The initial rate bits of the sponge, at the start of this step. - pub original_rate_bits: [T; KECCAK_RATE_BITS], + /// The initial rate part of the sponge, at the start of this step. + pub original_rate_u32s: [T; KECCAK_RATE_U32S], - /// The capacity bits of the sponge, encoded as 32-bit chunks, at the start of this step. + /// The capacity part of the sponge, encoded as 32-bit chunks, at the start of this step. pub original_capacity_u32s: [T; KECCAK_CAPACITY_U32S], /// The block being absorbed, which may contain input bytes and/or padding bytes. - pub block_bits: [T; KECCAK_RATE_BITS], + pub block_bytes: [T; KECCAK_RATE_BYTES], - /// The rate bits of the sponge, after the current block is xor'd in, but before the permutation + /// The rate part of the sponge, after the current block is xor'd in, but before the permutation /// is applied. pub xored_rate_u32s: [T; KECCAK_RATE_U32S], diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs index 19c80696..f1bb86df 100644 --- a/evm/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -1,6 +1,7 @@ use std::borrow::Borrow; -use std::iter; +use std::iter::{once, repeat}; use std::marker::PhantomData; +use std::mem::size_of; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -11,6 +12,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::timed; use plonky2::util::timing::TimingTree; +use plonky2_util::ceil_div_usize; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::kernel::keccak_util::keccakf_u32s; @@ -18,7 +20,7 @@ use crate::cross_table_lookup::Column; use crate::keccak_sponge::columns::*; use crate::memory::segments::Segment; use crate::stark::Stark; -use crate::util::{trace_rows_to_poly_values, u32_from_le_bits, u32_to_le_bits, u8_to_le_bits}; +use crate::util::trace_rows_to_poly_values; use crate::vars::StarkEvaluationTargets; use crate::vars::StarkEvaluationVars; @@ -39,18 +41,16 @@ pub(crate) fn ctl_looked_data() -> Vec> { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn ctl_looking_keccak() -> Vec> { - let input_rate_cols = (0..KECCAK_RATE_U32S) - .map(|i| Column::le_bits(&KECCAK_SPONGE_COL_MAP.original_rate_bits[i * 32..(i + 1) * 32])); - let input_capacity_cols = Column::singles( - (0..KECCAK_CAPACITY_U32S).map(|i| KECCAK_SPONGE_COL_MAP.original_capacity_u32s[i]), - ); - let output_cols = Column::singles( - (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.updated_state_u32s[i]), - ); - input_rate_cols - .chain(input_capacity_cols) - .chain(output_cols) - .collect() + let cols = KECCAK_SPONGE_COL_MAP; + Column::singles( + [ + cols.original_rate_u32s.as_slice(), + &cols.original_capacity_u32s, + &cols.updated_state_u32s, + ] + .concat(), + ) + .collect() } #[allow(unused)] // TODO: Should be used soon. @@ -68,7 +68,7 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { )); // The i'th input byte being read. - res.push(Column::le_bits(&cols.block_bits[i * 8..(i + 1) * 8])); + res.push(Column::single(cols.block_bytes[i])); // Since we're reading a single byte, the higher limbs must be zero. res.extend((1..8).map(|_| Column::zero())); @@ -82,6 +82,49 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { res } +/// CTL for performing the `i`th logic CTL. Since we need to do 136 byte XORs, and the logic CTL can +/// XOR 32 bytes per CTL, there are 5 such CTLs. +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looking_logic(i: usize) -> Vec> { + const U32S_PER_CTL: usize = 8; + const U8S_PER_CTL: usize = 32; + + debug_assert!(i < ceil_div_usize(KECCAK_RATE_BYTES, U8S_PER_CTL)); + let cols = KECCAK_SPONGE_COL_MAP; + + let mut res = vec![ + Column::zero(), // is_and + Column::zero(), // is_or + Column::one(), // is_xor + ]; + + // Input 0 contains some of the sponge's original rate chunks. If this is the last CTL, we won't + // need to use all of the CTL's inputs, so we will pass some zeros. + res.extend( + Column::singles(&cols.original_rate_u32s[i * U32S_PER_CTL..]) + .chain(repeat(Column::zero())) + .take(U32S_PER_CTL), + ); + + // Input 1 contains some of block's chunks. Again, for the last CTL it will include some zeros. + res.extend( + cols.block_bytes[i * U8S_PER_CTL..] + .chunks(size_of::()) + .map(|chunk| Column::le_bytes(chunk)) + .chain(repeat(Column::zero())) + .take(U8S_PER_CTL), + ); + + // The output contains the XOR'd rate part. + res.extend( + Column::singles(&cols.xored_rate_u32s[i * U32S_PER_CTL..]) + .chain(repeat(Column::zero())) + .take(U32S_PER_CTL), + ); + + res +} + #[allow(unused)] // TODO: Should be used soon. pub(crate) fn ctl_looked_filter() -> Column { // The CPU table is only interested in our final-block rows, since those contain the final @@ -96,7 +139,7 @@ pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { // - this is a full input block, or // - this is a final block of length `i` or greater let cols = KECCAK_SPONGE_COL_MAP; - Column::sum(iter::once(&cols.is_full_input_block).chain(&cols.is_final_input_len[i..])) + Column::sum(once(&cols.is_full_input_block).chain(&cols.is_final_input_len[i..])) } /// Information about a Keccak sponge operation needed for witness generation. @@ -157,7 +200,7 @@ impl, const D: usize> KeccakSpongeStark { operations .into_iter() .flat_map(|op| self.generate_rows_for_op(op)) - .chain(iter::repeat(self.generate_padding_row())) + .chain(repeat(self.generate_padding_row())) .take(num_rows) .collect() } @@ -208,13 +251,7 @@ impl, const D: usize> KeccakSpongeStark { ..Default::default() }; - row.block_bits = block - .into_iter() - .flat_map(u8_to_le_bits) - .map(F::from_bool) - .collect_vec() - .try_into() - .unwrap(); + row.block_bytes = block.map(F::from_canonical_u8); Self::generate_common_fields(&mut row, op, already_absorbed_bytes, sponge_state); row @@ -234,16 +271,18 @@ impl, const D: usize> KeccakSpongeStark { ..Default::default() }; - let final_input_bits = final_inputs - .iter() - .flat_map(|x| u8_to_le_bits(*x)) - .map(F::from_bool); - for (block_bit, input_bit) in row.block_bits.iter_mut().zip(final_input_bits) { - *block_bit = input_bit; + for (block_byte, input_byte) in row.block_bytes.iter_mut().zip(final_inputs) { + *block_byte = F::from_canonical_u8(*input_byte); } + // pad10*1 rule - row.block_bits[final_inputs.len() * 8] = F::ONE; - row.block_bits[KECCAK_RATE_BITS - 1] = F::ONE; + if final_inputs.len() == KECCAK_RATE_BYTES - 1 { + // Both 1s are placed in the same byte. + row.block_bytes[final_inputs.len()] = F::from_canonical_u8(0b10000001); + } else { + row.block_bytes[final_inputs.len()] = F::ONE; + row.block_bytes[KECCAK_RATE_BYTES - 1] = F::ONE; + } row.is_final_input_len[final_inputs.len()] = F::ONE; @@ -252,6 +291,7 @@ impl, const D: usize> KeccakSpongeStark { } /// Generate fields that are common to both full-input-block rows and final-block rows. + /// Also updates the sponge state with a single absorption. fn generate_common_fields( row: &mut KeccakSpongeColumnsView, op: &KeccakSpongeOp, @@ -265,10 +305,9 @@ impl, const D: usize> KeccakSpongeStark { row.len = F::from_canonical_usize(op.len); row.already_absorbed_bytes = F::from_canonical_usize(already_absorbed_bytes); - row.original_rate_bits = sponge_state[..KECCAK_RATE_U32S] + row.original_rate_u32s = sponge_state[..KECCAK_RATE_U32S] .iter() - .flat_map(|x| u32_to_le_bits(*x)) - .map(F::from_bool) + .map(|x| F::from_canonical_u32(*x)) .collect_vec() .try_into() .unwrap(); @@ -281,10 +320,10 @@ impl, const D: usize> KeccakSpongeStark { .unwrap(); let block_u32s = (0..KECCAK_RATE_U32S).map(|i| { - u32_from_le_bits( - row.block_bits[i * 32..(i + 1) * 32] + u32::from_le_bytes( + row.block_bytes[i * 4..(i + 1) * 4] .iter() - .map(Field::is_one) + .map(|x| x.to_canonical_u64() as u8) .collect_vec() .try_into() .unwrap(), diff --git a/evm/src/util.rs b/evm/src/util.rs index 89a763e0..12aead46 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -70,20 +70,6 @@ pub(crate) fn h160_limbs(h160: H160) -> [F; 5] { .unwrap() } -pub(crate) fn u8_to_le_bits(x: u8) -> [bool; 8] { - std::array::from_fn(|i| ((x >> i) & 1) != 0) -} - -pub(crate) fn u32_to_le_bits(x: u32) -> [bool; 32] { - std::array::from_fn(|i| ((x >> i) & 1) != 0) -} - -pub(crate) fn u32_from_le_bits(bits: [bool; 32]) -> u32 { - bits.into_iter() - .rev() - .fold(0, |acc, b| (acc << 1) | b as u32) -} - pub(crate) const fn indices_arr() -> [usize; N] { let mut indices_arr = [0; N]; let mut i = 0; From 46cf46ccd88c046e0a284647de3cf5ac01ec90fc Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 16:56:17 -0700 Subject: [PATCH 68/95] Minor --- evm/src/keccak_sponge/columns.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs index 3fb944fc..08194e87 100644 --- a/evm/src/keccak_sponge/columns.rs +++ b/evm/src/keccak_sponge/columns.rs @@ -5,8 +5,7 @@ use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; pub(crate) const KECCAK_WIDTH_BYTES: usize = 200; pub(crate) const KECCAK_WIDTH_U32S: usize = KECCAK_WIDTH_BYTES / 4; -pub(crate) const KECCAK_RATE_BITS: usize = 1088; -pub(crate) const KECCAK_RATE_BYTES: usize = KECCAK_RATE_BITS / 8; +pub(crate) const KECCAK_RATE_BYTES: usize = 136; pub(crate) const KECCAK_RATE_U32S: usize = KECCAK_RATE_BYTES / 4; pub(crate) const KECCAK_CAPACITY_BYTES: usize = 64; pub(crate) const KECCAK_CAPACITY_U32S: usize = KECCAK_CAPACITY_BYTES / 4; @@ -51,8 +50,8 @@ pub(crate) struct KeccakSpongeColumnsView { /// The block being absorbed, which may contain input bytes and/or padding bytes. pub block_bytes: [T; KECCAK_RATE_BYTES], - /// The rate part of the sponge, after the current block is xor'd in, but before the permutation - /// is applied. + /// The rate part of the sponge, encoded as 32-bit chunks, after the current block is xor'd in, + /// but before the permutation is applied. pub xored_rate_u32s: [T; KECCAK_RATE_U32S], /// The entire state (rate + capacity) of the sponge, encoded as 32-bit chunks, after the From 496581cfa1fe6497d7184685d1937c35764261d8 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 17:07:06 -0700 Subject: [PATCH 69/95] fix --- evm/src/keccak_sponge/keccak_sponge_stark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs index f1bb86df..afde02c2 100644 --- a/evm/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -281,7 +281,7 @@ impl, const D: usize> KeccakSpongeStark { row.block_bytes[final_inputs.len()] = F::from_canonical_u8(0b10000001); } else { row.block_bytes[final_inputs.len()] = F::ONE; - row.block_bytes[KECCAK_RATE_BYTES - 1] = F::ONE; + row.block_bytes[KECCAK_RATE_BYTES - 1] = F::from_canonical_u8(0b10000000); } row.is_final_input_len[final_inputs.len()] = F::ONE; From 99999f1697d2492c5b193f35b8539f0f0a352d3f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 22:28:45 -0700 Subject: [PATCH 70/95] Use `ceil_div_usize` for `PACKED_LEN` --- evm/src/logic.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evm/src/logic.rs b/evm/src/logic.rs index 2499101b..2fa9c810 100644 --- a/evm/src/logic.rs +++ b/evm/src/logic.rs @@ -7,6 +7,7 @@ use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use plonky2_util::ceil_div_usize; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; @@ -19,7 +20,7 @@ const VAL_BITS: usize = 256; // Number of bits stored per field element. Ensure that this fits; it is not checked. pub(crate) const PACKED_LIMB_BITS: usize = 32; // Number of field elements needed to store each input/output at the specified packing. -const PACKED_LEN: usize = (VAL_BITS + PACKED_LIMB_BITS - 1) / PACKED_LIMB_BITS; +const PACKED_LEN: usize = ceil_div_usize(VAL_BITS, PACKED_LIMB_BITS); pub(crate) mod columns { use std::cmp::min; From aaf7ace3961fb6b72949c83a0a147b2a891e8c2b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 22:31:56 -0700 Subject: [PATCH 71/95] Remove `JUMPDEST`s --- evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm | 8 -------- evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm | 6 ------ evm/src/cpu/kernel/asm/curve/common.asm | 1 - evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm | 7 ------- evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm | 5 ----- evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm | 4 ---- evm/src/cpu/kernel/asm/memory/memcpy.asm | 2 -- evm/src/cpu/kernel/asm/rlp/decode.asm | 9 --------- evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 3 --- evm/src/cpu/kernel/asm/transactions/router.asm | 2 -- evm/src/cpu/kernel/asm/transactions/type_0.asm | 1 - evm/src/cpu/kernel/asm/transactions/type_1.asm | 1 - evm/src/cpu/kernel/asm/transactions/type_2.asm | 1 - evm/src/cpu/kernel/asm/util/assertions.asm | 1 - 14 files changed, 51 deletions(-) diff --git a/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm index 15f9df05..dda82109 100644 --- a/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm @@ -9,7 +9,6 @@ global ec_add: // PUSH 1 // PUSH 0x1bf9384aa3f0b3ad763aee81940cacdde1af71617c06f46e11510f14f3d5d121 // PUSH 0xe7313274bb29566ff0c8220eb9841de1d96c2923c6a4028f7dd3c6a14cee770 - JUMPDEST // stack: x0, y0, x1, y1, retdest // Check if points are valid BN254 points. @@ -38,7 +37,6 @@ global ec_add: // BN254 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points. global ec_add_valid_points: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Check if the first point is the identity. @@ -92,7 +90,6 @@ global ec_add_valid_points: // BN254 elliptic curve addition. // Assumption: (x0,y0) == (0,0) ec_add_first_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) %stack (x0, y0, x1, y1, retdest) -> (retdest, x1, y1) @@ -101,7 +98,6 @@ ec_add_first_zero: // BN254 elliptic curve addition. // Assumption: (x1,y1) == (0,0) ec_add_snd_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x0,y0) @@ -111,7 +107,6 @@ ec_add_snd_zero: // BN254 elliptic curve addition. // Assumption: lambda = (y0 - y1)/(x0 - x1) ec_add_valid_points_with_lambda: - JUMPDEST // stack: lambda, x0, y0, x1, y1, retdest // Compute x2 = lambda^2 - x1 - x0 @@ -159,7 +154,6 @@ ec_add_valid_points_with_lambda: // BN254 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points and x0 == x1 ec_add_equal_first_coord: - JUMPDEST // stack: x0, y0, x1, y1, retdest with x0 == x1 // Check if the points are equal @@ -188,7 +182,6 @@ ec_add_equal_first_coord: // Assumption: x0 == x1 and y0 == y1 // Standard doubling formula. ec_add_equal_points: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Compute lambda = 3/2 * x0^2 / y0 @@ -216,7 +209,6 @@ ec_add_equal_points: // Assumption: (x0,y0) is a valid point. // Standard doubling formula. global ec_double: - JUMPDEST // stack: x0, y0, retdest DUP2 // stack: y0, x0, y0, retdest diff --git a/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm index 62cf2235..b1472812 100644 --- a/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm +++ b/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm @@ -6,7 +6,6 @@ global ec_mul: // PUSH 0xd // PUSH 2 // PUSH 1 - JUMPDEST // stack: x, y, s, retdest DUP2 // stack: y, x, y, s, retdest @@ -29,7 +28,6 @@ global ec_mul: // Same algorithm as in `exp.asm` ec_mul_valid_point: - JUMPDEST // stack: x, y, s, retdest DUP3 // stack: s, x, y, s, retdest @@ -38,7 +36,6 @@ ec_mul_valid_point: %jump(ret_zero_ec_mul) step_case: - JUMPDEST // stack: x, y, s, retdest PUSH recursion_return // stack: recursion_return, x, y, s, retdest @@ -58,12 +55,10 @@ step_case: // Assumption: 2(x,y) = (x',y') step_case_contd: - JUMPDEST // stack: x', y', s / 2, recursion_return, x, y, s, retdest %jump(ec_mul_valid_point) recursion_return: - JUMPDEST // stack: x', y', x, y, s, retdest SWAP4 // stack: s, y', x, y, x', retdest @@ -96,6 +91,5 @@ recursion_return: JUMP odd_scalar: - JUMPDEST // stack: x', y', x, y, retdest %jump(ec_add_valid_points) diff --git a/evm/src/cpu/kernel/asm/curve/common.asm b/evm/src/cpu/kernel/asm/curve/common.asm index 107dc63c..9e273c15 100644 --- a/evm/src/cpu/kernel/asm/curve/common.asm +++ b/evm/src/cpu/kernel/asm/curve/common.asm @@ -1,5 +1,4 @@ global ret_zero_ec_mul: - JUMPDEST // stack: x, y, s, retdest %pop3 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm index 7f9c1fff..790fb116 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm @@ -3,7 +3,6 @@ // Secp256k1 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points. global ec_add_valid_points_secp: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Check if the first point is the identity. @@ -57,7 +56,6 @@ global ec_add_valid_points_secp: // Secp256k1 elliptic curve addition. // Assumption: (x0,y0) == (0,0) ec_add_first_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) @@ -72,7 +70,6 @@ ec_add_first_zero: // Secp256k1 elliptic curve addition. // Assumption: (x1,y1) == (0,0) ec_add_snd_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) @@ -93,7 +90,6 @@ ec_add_snd_zero: // Secp256k1 elliptic curve addition. // Assumption: lambda = (y0 - y1)/(x0 - x1) ec_add_valid_points_with_lambda: - JUMPDEST // stack: lambda, x0, y0, x1, y1, retdest // Compute x2 = lambda^2 - x1 - x0 @@ -150,7 +146,6 @@ ec_add_valid_points_with_lambda: // Secp256k1 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points and x0 == x1 ec_add_equal_first_coord: - JUMPDEST // stack: x0, y0, x1, y1, retdest with x0 == x1 // Check if the points are equal @@ -179,7 +174,6 @@ ec_add_equal_first_coord: // Assumption: x0 == x1 and y0 == y1 // Standard doubling formula. ec_add_equal_points: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Compute lambda = 3/2 * x0^2 / y0 @@ -207,7 +201,6 @@ ec_add_equal_points: // Assumption: (x0,y0) is a valid point. // Standard doubling formula. global ec_double_secp: - JUMPDEST // stack: x0, y0, retdest DUP2 // stack: y0, x0, y0, retdest diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm index f0825e88..892d57c0 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm @@ -1,6 +1,5 @@ // Same algorithm as in `exp.asm` global ec_mul_valid_point_secp: - JUMPDEST // stack: x, y, s, retdest %stack (x,y) -> (x,y,x,y) %ec_isidentity @@ -13,7 +12,6 @@ global ec_mul_valid_point_secp: %jump(ret_zero_ec_mul) step_case: - JUMPDEST // stack: x, y, s, retdest PUSH recursion_return // stack: recursion_return, x, y, s, retdest @@ -33,12 +31,10 @@ step_case: // Assumption: 2(x,y) = (x',y') step_case_contd: - JUMPDEST // stack: x', y', s / 2, recursion_return, x, y, s, retdest %jump(ec_mul_valid_point_secp) recursion_return: - JUMPDEST // stack: x', y', x, y, s, retdest SWAP4 // stack: s, y', x, y, x', retdest @@ -71,6 +67,5 @@ recursion_return: JUMP odd_scalar: - JUMPDEST // stack: x', y', x, y, retdest %jump(ec_add_valid_points_secp) diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm index 538a86dc..96e177ff 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm @@ -1,6 +1,5 @@ // ecrecover precompile. global ecrecover: - JUMPDEST // stack: hash, v, r, s, retdest // Check if inputs are valid. @@ -47,7 +46,6 @@ global ecrecover: // let u2 = -hash * r_inv; // return u1*P + u2*GENERATOR; ecrecover_valid_input: - JUMPDEST // stack: hash, y, r, s, retdest // Compute u1 = s * r^(-1) @@ -83,7 +81,6 @@ ecrecover_valid_input: // ecrecover precompile. // Assumption: (X,Y) = u1 * P. Result is (X,Y) + u2*GENERATOR ecrecover_with_first_point: - JUMPDEST // stack: X, Y, hash, r^(-1), retdest %secp_scalar // stack: p, X, Y, hash, r^(-1), retdest @@ -132,7 +129,6 @@ ecrecover_with_first_point: // Take a public key (PKx, PKy) and return the associated address KECCAK256(PKx || PKy)[-20:]. pubkey_to_addr: - JUMPDEST // stack: PKx, PKy, retdest PUSH 0 // stack: 0, PKx, PKy, retdest diff --git a/evm/src/cpu/kernel/asm/memory/memcpy.asm b/evm/src/cpu/kernel/asm/memory/memcpy.asm index 0a390736..3feca35d 100644 --- a/evm/src/cpu/kernel/asm/memory/memcpy.asm +++ b/evm/src/cpu/kernel/asm/memory/memcpy.asm @@ -4,7 +4,6 @@ // DST = (dst_ctx, dst_segment, dst_addr). // These tuple definitions are used for brevity in the stack comments below. global memcpy: - JUMPDEST // stack: DST, SRC, count, retdest DUP7 // stack: count, DST, SRC, count, retdest @@ -44,7 +43,6 @@ global memcpy: %jump(memcpy) memcpy_finish: - JUMPDEST // stack: DST, SRC, count, retdest %pop7 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index 0388276a..5749aee7 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -12,7 +12,6 @@ // Pre stack: pos, retdest // Post stack: pos', len global decode_rlp_string_len: - JUMPDEST // stack: pos, retdest DUP1 %mload_current(@SEGMENT_RLP_RAW) @@ -32,7 +31,6 @@ global decode_rlp_string_len: JUMP decode_rlp_string_len_medium: - JUMPDEST // String is 0-55 bytes long. First byte contains the len. // stack: first_byte, pos, retdest %sub_const(0x80) @@ -44,7 +42,6 @@ decode_rlp_string_len_medium: JUMP decode_rlp_string_len_large: - JUMPDEST // String is >55 bytes long. First byte contains the len of the len. // stack: first_byte, pos, retdest %sub_const(0xb7) @@ -69,7 +66,6 @@ decode_rlp_string_len_large: // bytes, so that the result can be returned as a single word on the stack. // As per the spec, scalars must not have leading zeros. global decode_rlp_scalar: - JUMPDEST // stack: pos, retdest PUSH decode_int_given_len // stack: decode_int_given_len, pos, retdest @@ -91,7 +87,6 @@ global decode_rlp_scalar: // Pre stack: pos, retdest // Post stack: pos', len global decode_rlp_list_len: - JUMPDEST // stack: pos, retdest DUP1 %mload_current(@SEGMENT_RLP_RAW) @@ -116,7 +111,6 @@ global decode_rlp_list_len: JUMP decode_rlp_list_len_big: - JUMPDEST // The length of the length is first_byte - 0xf7. // stack: first_byte, pos', retdest %sub_const(0xf7) @@ -137,7 +131,6 @@ decode_rlp_list_len_big: // Pre stack: pos, len, retdest // Post stack: pos', int decode_int_given_len: - JUMPDEST %stack (pos, len, retdest) -> (pos, len, pos, retdest) ADD // stack: end_pos, pos, retdest @@ -147,7 +140,6 @@ decode_int_given_len: // stack: acc, pos, end_pos, retdest decode_int_given_len_loop: - JUMPDEST // stack: acc, pos, end_pos, retdest DUP3 DUP3 @@ -171,6 +163,5 @@ decode_int_given_len_loop: %jump(decode_int_given_len_loop) decode_int_given_len_finish: - JUMPDEST %stack (acc, pos, end_pos, retdest) -> (retdest, pos, acc) JUMP diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index ae75e3d7..db474b9b 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -5,7 +5,6 @@ // Post stack: (empty) global read_rlp_to_memory: - JUMPDEST // stack: retdest PROVER_INPUT // Read the RLP blob length from the prover tape. // stack: len, retdest @@ -13,7 +12,6 @@ global read_rlp_to_memory: // stack: pos, len, retdest read_rlp_to_memory_loop: - JUMPDEST // stack: pos, len, retdest DUP2 DUP2 @@ -32,7 +30,6 @@ read_rlp_to_memory_loop: %jump(read_rlp_to_memory_loop) read_rlp_to_memory_finish: - JUMPDEST // stack: pos, len, retdest %pop2 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm index 01a65fec..47a899c9 100644 --- a/evm/src/cpu/kernel/asm/transactions/router.asm +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -3,7 +3,6 @@ // jump to the appropriate transaction parsing method. global route_txn: - JUMPDEST // stack: (empty) // First load transaction data into memory, where it will be parsed. PUSH read_txn_from_memory @@ -11,7 +10,6 @@ global route_txn: // At this point, the raw txn data is in memory. read_txn_from_memory: - JUMPDEST // stack: (empty) // We will peak at the first byte to determine what type of transaction this is. diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 7c8488f7..3f258624 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -12,7 +12,6 @@ // keccak256(rlp([nonce, gas_price, gas_limit, to, value, data])) global process_type_0_txn: - JUMPDEST // stack: (empty) PUSH 0 // initial pos // stack: pos diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm index 5b9d2cdf..9d45c1e4 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -7,6 +7,5 @@ // data, access_list])) global process_type_1_txn: - JUMPDEST // stack: (empty) PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index 9807f88f..b2a862c1 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -8,6 +8,5 @@ // access_list])) global process_type_2_txn: - JUMPDEST // stack: (empty) PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/util/assertions.asm b/evm/src/cpu/kernel/asm/util/assertions.asm index 69193e5f..0051219c 100644 --- a/evm/src/cpu/kernel/asm/util/assertions.asm +++ b/evm/src/cpu/kernel/asm/util/assertions.asm @@ -1,7 +1,6 @@ // It is convenient to have a single panic routine, which we can jump to from // anywhere. global panic: - JUMPDEST PANIC // Consumes the top element and asserts that it is zero. From 1c2e94f9fccb8e1de2c5a8a3ac5ce46f43775795 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 22:46:16 -0700 Subject: [PATCH 72/95] Compute answers to FRI queries in parallel It shaved off much less than a millisecond, so it's rather negligible, but the code came out simpler so might as well. --- plonky2/src/fri/prover.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs index 39e25869..71efe98a 100644 --- a/plonky2/src/fri/prover.rs +++ b/plonky2/src/fri/prover.rs @@ -149,15 +149,12 @@ fn fri_prover_query_rounds< n: usize, fri_params: &FriParams, ) -> Vec> { - (0..fri_params.config.num_query_rounds) - .map(|_| { - fri_prover_query_round::( - initial_merkle_trees, - trees, - challenger, - n, - fri_params, - ) + challenger + .get_n_challenges(fri_params.config.num_query_rounds) + .into_par_iter() + .map(|rand| { + let x_index = rand.to_canonical_u64() as usize % n; + fri_prover_query_round::(initial_merkle_trees, trees, x_index, fri_params) }) .collect() } @@ -169,13 +166,10 @@ fn fri_prover_query_round< >( initial_merkle_trees: &[&MerkleTree], trees: &[MerkleTree], - challenger: &mut Challenger, - n: usize, + mut x_index: usize, fri_params: &FriParams, ) -> FriQueryRound { let mut query_steps = Vec::new(); - let x = challenger.get_challenge(); - let mut x_index = x.to_canonical_u64() as usize % n; let initial_proof = initial_merkle_trees .iter() .map(|t| (t.get(x_index).to_vec(), t.prove(x_index))) From 9b259cb9172229ddd35d9a12f0c4d4dccf7303ed Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 5 Sep 2022 10:12:23 -0700 Subject: [PATCH 73/95] Feedback --- evm/src/cpu/kernel/asm/exp.asm | 3 --- evm/src/cpu/kernel/interpreter.rs | 6 +++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/evm/src/cpu/kernel/asm/exp.asm b/evm/src/cpu/kernel/asm/exp.asm index 3640b2f6..f025e312 100644 --- a/evm/src/cpu/kernel/asm/exp.asm +++ b/evm/src/cpu/kernel/asm/exp.asm @@ -10,7 +10,6 @@ /// Note that this correctly handles exp(0, 0) == 1. global exp: - jumpdest // stack: x, e, retdest dup2 // stack: e, x, e, retdest @@ -27,7 +26,6 @@ global exp: jump step_case: - jumpdest // stack: x, e, retdest push recursion_return // stack: recursion_return, x, e, retdest @@ -43,7 +41,6 @@ step_case: // stack: x * x, e / 2, recursion_return, x, e, retdest %jump(exp) recursion_return: - jumpdest // stack: exp(x * x, e / 2), x, e, retdest push 2 // stack: 2, exp(x * x, e / 2), x, e, retdest diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 17be0523..64d70529 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -250,7 +250,7 @@ impl<'a> Interpreter<'a> { 0x58 => todo!(), // "GETPC", 0x59 => todo!(), // "MSIZE", 0x5a => todo!(), // "GAS", - 0x5b => (), // "JUMPDEST", + 0x5b => self.run_jumpdest(), // "JUMPDEST", 0x5c => todo!(), // "GET_STATE_ROOT", 0x5d => todo!(), // "SET_STATE_ROOT", 0x5e => todo!(), // "GET_RECEIPT_ROOT", @@ -490,6 +490,10 @@ impl<'a> Interpreter<'a> { } } + fn run_jumpdest(&mut self) { + assert!(!self.kernel_mode, "JUMPDEST is not needed in kernel code"); + } + fn jump_to(&mut self, offset: usize) { // The JUMPDEST rule is not enforced in kernel mode. if !self.kernel_mode && self.jumpdests.binary_search(&offset).is_err() { From f496711b2142d2400bca00dd39015c8e90073504 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 14:55:51 -0400 Subject: [PATCH 74/95] add flat_map_iter to maybe_rayon & feature-gate it --- maybe_rayon/src/lib.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/maybe_rayon/src/lib.rs b/maybe_rayon/src/lib.rs index 1a9bd823..b7dddc0e 100644 --- a/maybe_rayon/src/lib.rs +++ b/maybe_rayon/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "parallel"))] use std::{ - iter::{IntoIterator, Iterator}, + iter::{IntoIterator, Iterator, FlatMap}, slice::{Chunks, ChunksExact, ChunksExactMut, ChunksMut}, }; @@ -223,13 +223,22 @@ impl MaybeParChunksMut for [T] { } } +#[cfg(not(feature = "parallel"))] pub trait ParallelIteratorMock { type Item; fn find_any

(self, predicate: P) -> Option where P: Fn(&Self::Item) -> bool + Sync + Send; + + fn flat_map_iter(self, map_op: F) -> FlatMap + where + Self: Sized, + U: IntoIterator, + F: Fn(Self::Item) -> U; + } +#[cfg(not(feature = "parallel"))] impl ParallelIteratorMock for T { type Item = T::Item; @@ -239,6 +248,15 @@ impl ParallelIteratorMock for T { { self.find(predicate) } + + fn flat_map_iter(self, map_op: F) -> FlatMap + where + Self: Sized, + U: IntoIterator, + F: Fn(Self::Item) -> U + { + self.flat_map(map_op) + } } #[cfg(feature = "parallel")] From e72152eed80e06769cdb97d5c292e5f65cbf0942 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 14:56:48 -0400 Subject: [PATCH 75/95] fix default features in starky & evm --- evm/Cargo.toml | 2 +- plonky2/src/hash/hash_types.rs | 2 +- starky/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index db774345..230cd5a8 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" edition = "2021" [dependencies] -plonky2 = { path = "../plonky2" } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "rand_chacha", "timing", "gate_testing"] } plonky2_util = { path = "../util" } anyhow = "1.0.40" env_logger = "0.9.0" diff --git a/plonky2/src/hash/hash_types.rs b/plonky2/src/hash/hash_types.rs index 14303ad3..f416732a 100644 --- a/plonky2/src/hash/hash_types.rs +++ b/plonky2/src/hash/hash_types.rs @@ -115,7 +115,7 @@ pub struct MerkleCapTarget(pub Vec); pub struct BytesHash(pub [u8; N]); impl BytesHash { - #[cfg(feature = "parallel")] + #[cfg(feature = "rand")] pub fn rand_from_rng(rng: &mut R) -> Self { let mut buf = [0; N]; rng.fill_bytes(&mut buf); diff --git a/starky/Cargo.toml b/starky/Cargo.toml index 80a26bfc..4bc12d31 100644 --- a/starky/Cargo.toml +++ b/starky/Cargo.toml @@ -9,7 +9,7 @@ default = ["parallel"] parallel = ["maybe_rayon/parallel"] [dependencies] -plonky2 = { path = "../plonky2" } +plonky2 = { path = "../plonky2", default-features = false } plonky2_util = { path = "../util" } anyhow = "1.0.40" env_logger = "0.9.0" From aaba931e4db788cf7a79aa224762761c183ab174 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 14:58:42 -0400 Subject: [PATCH 76/95] fmt --- maybe_rayon/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/maybe_rayon/src/lib.rs b/maybe_rayon/src/lib.rs index b7dddc0e..d24ba2e5 100644 --- a/maybe_rayon/src/lib.rs +++ b/maybe_rayon/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "parallel"))] use std::{ - iter::{IntoIterator, Iterator, FlatMap}, + iter::{FlatMap, IntoIterator, Iterator}, slice::{Chunks, ChunksExact, ChunksExactMut, ChunksMut}, }; @@ -235,7 +235,6 @@ pub trait ParallelIteratorMock { Self: Sized, U: IntoIterator, F: Fn(Self::Item) -> U; - } #[cfg(not(feature = "parallel"))] @@ -253,7 +252,7 @@ impl ParallelIteratorMock for T { where Self: Sized, U: IntoIterator, - F: Fn(Self::Item) -> U + F: Fn(Self::Item) -> U, { self.flat_map(map_op) } From aa0f0f6e75359981ca98f4607475939804d8f447 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 15:10:55 -0400 Subject: [PATCH 77/95] add other features back --- starky/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starky/Cargo.toml b/starky/Cargo.toml index 4bc12d31..700a3248 100644 --- a/starky/Cargo.toml +++ b/starky/Cargo.toml @@ -9,7 +9,7 @@ default = ["parallel"] parallel = ["maybe_rayon/parallel"] [dependencies] -plonky2 = { path = "../plonky2", default-features = false } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing", "rand_chacha"] } plonky2_util = { path = "../util" } anyhow = "1.0.40" env_logger = "0.9.0" From 6f98d6bc0316f843cfaa383f45f1eeb4b38ead54 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 7 Sep 2022 16:46:27 +0200 Subject: [PATCH 78/95] Comment batch opening --- plonky2/src/fri/oracle.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs index 1f5b648f..efe34939 100644 --- a/plonky2/src/fri/oracle.rs +++ b/plonky2/src/fri/oracle.rs @@ -180,7 +180,13 @@ impl, C: GenericConfig, const D: usize> // Final low-degree polynomial that goes into FRI. let mut final_poly = PolynomialCoeffs::empty(); + // Each batch `i` consists of an opening point `z_i` and polynomials `{f_ij}_j` to be opened at that point. + // For each batch, we compute the composition polynomial `F_i = sum alpha^j f_ij`, + // where `alpha` is a random challenge in the extension field. + // The final polynomial is then computed as `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i)` + // where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum. for FriBatchInfo { point, polynomials } in &instance.batches { + // Collect the coefficients of all the polynomials in `polynomials`. let polys_coeff = polynomials.iter().map(|fri_poly| { &oracles[fri_poly.oracle_index].polynomials[fri_poly.polynomial_index] }); From 7fbbd301a7d8a3f9025a714d16cebf4f0bbe5f3f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 7 Sep 2022 07:46:43 -0700 Subject: [PATCH 79/95] Update paper --- plonky2/plonky2.pdf | Bin 235098 -> 236419 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/plonky2/plonky2.pdf b/plonky2/plonky2.pdf index ad0cef0228082e7ce26f6c059dd66618c0e43235..8f0f9ece13a2e3997927736de6205d178a18a4dd 100644 GIT binary patch delta 59696 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

DRgc90bOT@M3*{@+bAF( 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%0D5j)tc+UHQ z?pm9{u4Q&8=1+@OTemggzNg|Lon=~U4Egj&o{nc^y!y`GI3##M6@sYN84=UnV=9pu zYI(4!cDXdhOzMR8R8Dv}(2;Qo=Md8|A+LUHj}W)!Sw3-g||v!5FC>h*RM(Z?ro zp6#GF)T3%uJPR;Exei7=`x75m_59(LT$d&A<~DeF9G|PNuHICgxo-h8@7Nz(f?whc zrpNRNX)6nsrd75cRS8wWQ$W6J?qIocc!LL@H$k8LAo|K*_S4g11g>1LZ!WC{@sWsN7Bz<+KGhi1{J6@-Yx z>0T>!-onWpriM$1*G!EY04cFKn*Wy%94~MvofxbA8>ivWs_)l!kH$Jv+*iFrXt?3=R2R}y#+X2xHB|krh#LQjTRGf|5SFy+ zF`!(y;J?=6Q`2l9x_ig1M52sl0r#xZa4MZ1B_P$Rc2;H}Lwk%t>i*p-uRaN^mW@PT z{BV9e*Bz3YTvz2boehv*WN#6!SUVXezH(YuST|>RGXY}7YSv3)WTNQ3No>; z;AGfD$hT$sk9;qL7$wYAMzxNIzN??k_VfTM3F)4EZ~3eRU($7_7KV$b*+@iojO3)X zdQs{)Bw;5yO&NiKhJmxVd*V6x^Qa`H+PXm=tYn;+w2sUNJa@iCt?tyiTqedGfUwi= z<-L~j`E%8F{9aBO`-ZDKO(6zq3Y>WZj_ltnPWW4$=Kno!Ms--C*9Up1_HnrR@xj&u z#{=OXh&d9TYUz@DM+xuN(Jh7`YOfyd`%<4qI30ml>ASpX{U>OH5@_H}X|rlb_qCJ> z!Y0-4J`{-^+@R2O~)z;9$x#Az9 zRt8XvB;jArQ#^y+@nwGw#HqsZtUB4tQu8j(I(}zO5!rvEVZCukfHpX_Um*6R<(h`_ zD3R&l6H!#SAV4v=72hBbf3iLN2h?e=e5l;0T65RP%a-FkZOs7_7^m($EBRZNw@wX{Lv9xa|(P)JUD+#Y53yAjIa7OQ65T z>;E*;^>q_KAkw?++i=fQE_&oPaJc#4c+th%aI=Yua!YWsuyC@oi?T~_vx>2DaEfqq@NlznO7I90|Noch zA^q>|fQO5V_`k4X6sfWf3d$H^kGy3y8dfdpC6&!pYI-LMr}J2Dbd{Fsr}Og>5yUW! zCZsTwI zxB8?2==Q+Y@Ny`1OmU8N`*~}nV2$9`Xjjy737wGEIMJ7v_ZJoBcrkKHy2gJ9IHLp<*S%YWCW`(B}d1RR-tr_UvOxP?q zzmyNK!(Nx1=g3F5;{q`qgX)pbiPvG~8E0eNVeJGef(e^R*b7C4t|alr7RW#;;OHjH zY86YdGj=dzp_LawnCd9`H$G}LSXvA=%k|7Ct%_PN%x_&xu3z}()Px9R)5~<0+)%W> z$F`HHzKXW4N>y%DLU+p2YM%jt{UVOAh=E4TkjTMgyVN+)WBpE!kl9dW{W#o6(*aF} z_}KvTUH*0m#vob4S(PO_Z7(#r1|jWB){HMryYS%lPXpr9UCNABwO3(;U)T0*mH=51 zfdm$8)y^eDpb~{sqzS?ffLg0wZB|Z%4VnR?S){?R;yKZOsiYbrgLjeJEiG4|;6Y); z4?;etkh8pP(MR^uT65CNG(k;L6UeT;?r#wP7q(f%aXhf1XlcHm=)JPZ=p&)x&{z)r z=G*6!3s1w){9m1(z0>DO<}lVKr%!XR4=?Q<`XTmbc|LfM=4jmwxt<97B~IfH)MqWoJX}4 zzxBf7qo&pfwHK+^Ys-DH#ae)ng|>&^1Y@+*II8{vjF#H$PHB6;$ye>xYwZH{Y62ls z0t!y#lr3y|k&fJSXV7NtM@-}O$=n#Xt%M93p!c6u6C&^Xf_ zeir4t=nH)P-)5mdv6S3*_xaeiP+9$0vnZ{BM*Y>dEou6Fi+MIj%pQJ#zksX(u*Uyv z95+%Jea~wRbVj5g{4~U9!D-=iAeZ8y)a6OsCUGXEd~TmML< Vv!nqC5ja@5IpHZNBorm#{{xy6phW-x delta 58255 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 T4M~W|%Eif!Ku#{MD1q=lZgBWM From 8fd4fc430446b38fcb3b21312c47985b58b90153 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 7 Sep 2022 16:49:23 +0200 Subject: [PATCH 80/95] Minor --- plonky2/src/fri/structure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/fri/structure.rs b/plonky2/src/fri/structure.rs index 1a37a1b2..d5c2c81c 100644 --- a/plonky2/src/fri/structure.rs +++ b/plonky2/src/fri/structure.rs @@ -42,7 +42,7 @@ pub struct FriBatchInfoTarget { #[derive(Copy, Clone, Debug)] pub struct FriPolynomialInfo { - /// Index into `FriInstanceInfoTarget`'s `oracles` list. + /// Index into `FriInstanceInfo`'s `oracles` list. pub oracle_index: usize, /// Index of the polynomial within the oracle. pub polynomial_index: usize, From f1e21ffb5d4acdf22067829fd7eba1d3611689d1 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 7 Sep 2022 20:57:38 +0200 Subject: [PATCH 81/95] More comment --- plonky2/src/fri/oracle.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs index efe34939..75f8847a 100644 --- a/plonky2/src/fri/oracle.rs +++ b/plonky2/src/fri/oracle.rs @@ -185,6 +185,8 @@ impl, C: GenericConfig, const D: usize> // where `alpha` is a random challenge in the extension field. // The final polynomial is then computed as `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i)` // where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum. + // There are usually two batches for the openings at `zeta` and `g * zeta`. + // The oracles used in Plonky2 are given in `FRI_ORACLES` in `plonky2/src/plonk/plonk_common.rs`. for FriBatchInfo { point, polynomials } in &instance.batches { // Collect the coefficients of all the polynomials in `polynomials`. let polys_coeff = polynomials.iter().map(|fri_poly| { From 19162db596f248cba25f56954ee45fff51c61118 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 7 Sep 2022 15:09:56 -0700 Subject: [PATCH 82/95] Tweak features --- evm/Cargo.toml | 6 +++--- starky/Cargo.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 230cd5a8..1e8f3c56 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -5,8 +5,9 @@ version = "0.1.0" edition = "2021" [dependencies] -plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "rand_chacha", "timing", "gate_testing"] } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } +maybe_rayon = { path = "../maybe_rayon" } anyhow = "1.0.40" env_logger = "0.9.0" ethereum-types = "0.13.1" @@ -17,7 +18,6 @@ log = "0.4.14" once_cell = "1.13.0" pest = "2.1.3" pest_derive = "2.1.0" -maybe_rayon = { path = "../maybe_rayon" } rand = "0.8.5" rand_chacha = "0.3.1" rlp = "0.5.1" @@ -31,7 +31,7 @@ hex = "0.4.3" [features] default = ["parallel"] asmtools = ["hex"] -parallel = ["maybe_rayon/parallel"] +parallel = ["plonky2/parallel", "maybe_rayon/parallel"] [[bin]] name = "assemble" diff --git a/starky/Cargo.toml b/starky/Cargo.toml index 700a3248..43bea53e 100644 --- a/starky/Cargo.toml +++ b/starky/Cargo.toml @@ -6,13 +6,13 @@ edition = "2021" [features] default = ["parallel"] -parallel = ["maybe_rayon/parallel"] +parallel = ["plonky2/parallel", "maybe_rayon/parallel"] [dependencies] -plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing", "rand_chacha"] } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } +maybe_rayon = { path = "../maybe_rayon"} anyhow = "1.0.40" env_logger = "0.9.0" itertools = "0.10.0" log = "0.4.14" -maybe_rayon = { path = "../maybe_rayon"} From fdb6cafe18b5f2eaaecfe60c792b2d775d0691ed Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 15 Aug 2022 10:28:23 -0700 Subject: [PATCH 83/95] Fill in call_common routine --- evm/src/cpu/kernel/asm/core/call.asm | 85 +++++++++++++++++++----- evm/src/cpu/kernel/asm/core/transfer.asm | 12 ++++ 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index 3cbbb441..1b8a535f 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -2,21 +2,21 @@ // Creates a new sub context and executes the code of the given account. global call: - // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size, retdest %address %stack (self, gas, address, value) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (1, value, 0, gas, self, address, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (0, 1, value, self, address, address, gas) %jump(call_common) // Creates a new sub context as if calling itself, but with the code of the // given account. In particular the storage remains the same. global call_code: - // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size, retdest %address %stack (self, gas, address, value) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (1, value, 0, gas, self, self, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (0, 1, value, self, self, address, gas) %jump(call_common) // Creates a new sub context and executes the code of the given account. @@ -25,35 +25,86 @@ global call_code: // are CREATE, CREATE2, LOG0, LOG1, LOG2, LOG3, LOG4, SSTORE, SELFDESTRUCT and // CALL if the value sent is not 0. global static_all: - // stack: gas, address, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, args_offset, args_size, ret_offset, ret_size, retdest %address %stack (self, gas, address) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (0, 0, 1, gas, self, address, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (1, 0, 0, self, address, address, gas) %jump(call_common) // Creates a new sub context as if calling itself, but with the code of the // given account. In particular the storage, the current sender and the current // value remain the same. global delegate_call: - // stack: gas, address, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, args_offset, args_size, ret_offset, ret_size, retdest %address %sender %callvalue %stack (self, sender, value, gas, address) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (0, value, 0, gas, sender, self, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (0, 0, value, sender, self, address, gas) %jump(call_common) call_common: - // stack: should_transfer_value, value, static, gas, sender, storage, code_addr, args_offset, args_size, ret_offset, ret_size - // TODO: Set all the appropriate metadata fields... + // stack: static, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest %create_context - // stack: new_ctx, after_call + // Store the static flag in metadata. + %stack (new_ctx, static) -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_STATIC, static, new_ctx) + MSTORE_GENERAL + // stack: new_ctx, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store the address in metadata. + %stack (new_ctx, should_transfer_value, value, sender, address) + -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_ADDRESS, address, + new_ctx, should_transfer_value, value, sender, address) + MSTORE_GENERAL + // stack: new_ctx, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store the caller in metadata. + %stack (new_ctx, should_transfer_value, value, sender) + -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_CALLER, sender, + new_ctx, should_transfer_value, value, sender) + MSTORE_GENERAL + // stack: new_ctx, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store the call value field in metadata. + %stack (new_ctx, should_transfer_value, value, sender, address) = + -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_CALL_VALUE, value, + should_transfer_value, sender, address, value, new_ctx) + MSTORE_GENERAL + // stack: should_transfer_value, sender, address, value, new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + %maybe_transfer_eth + // stack: new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store parent context in metadata. + GET_CONTEXT + PUSH @CTX_METADATA_PARENT_CONTEXT + PUSH @SEGMENT_CONTEXT_METADATA + DUP4 // new_ctx + MSTORE_GENERAL + // stack: new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store parent PC = after_call. + %stack (new_ctx) -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_PARENT_PC, after_call, new_ctx) + MSTORE_GENERAL + // stack: new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // TODO: Populate CALLDATA + // TODO: Save parent gas and set child gas + // TODO: Populate code + + // TODO: Temporary, remove after above steps are done. + %stack (new_ctx, code_addr, gas, args_offset, args_size) -> (new_ctx) + // stack: new_ctx, ret_offset, ret_size, retdest + // Now, switch to the new context and go to usermode with PC=0. + DUP1 // new_ctx SET_CONTEXT - PUSH 0 + PUSH 0 // jump dest EXIT_KERNEL after_call: - // TODO: Set RETURNDATA etc. + // stack: new_ctx, ret_offset, ret_size, retdest + // TODO: Set RETURNDATA. + // TODO: Return to caller w/ EXIT_KERNEL. diff --git a/evm/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm index 41057aff..0ed48f4d 100644 --- a/evm/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -14,3 +14,15 @@ global transfer_eth: %jump(transfer_eth) %%after: %endmacro + +// Pre stack: should_transfer, from, to, amount +// Post stack: (empty) +%macro maybe_transfer_eth + %jumpi(%%transfer) + // We're skipping the transfer, so just pop the arguments and return. + %pop3 + %jump(%%after) +%%transfer: + %transfer_eth +%%after: +%endmacro From 0b9881c5e388dea4ddd7cdc7b48688ffb47a79b0 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 9 Sep 2022 12:05:58 -0700 Subject: [PATCH 84/95] blocks in stack manipulation --- evm/src/cpu/kernel/assembler.rs | 12 ++++++ evm/src/cpu/kernel/ast.rs | 10 ++++- evm/src/cpu/kernel/evm_asm.pest | 5 ++- evm/src/cpu/kernel/parser.rs | 23 +++++++--- .../cpu/kernel/stack/stack_manipulation.rs | 43 +++++++++++++++---- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index f5175c41..f52ae29d 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -551,6 +551,7 @@ mod tests { let dup1 = get_opcode("DUP1"); let swap1 = get_opcode("SWAP1"); let swap2 = get_opcode("SWAP2"); + let swap3 = get_opcode("SWAP3"); let push_label = get_push_opcode(BYTES_PER_OFFSET); let kernel = parse_and_assemble(&["%stack (a) -> (a)"]); @@ -562,6 +563,17 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); + let kernel = parse_and_assemble(&["%stack (a, (b: 3), c) -> (c)"]); + assert_eq!(kernel.code, vec![pop, pop, pop, pop]); + + let kernel = parse_and_assemble(&["%stack ((a: 2), (b: 2)) -> (b, a)"]); + assert_eq!(kernel.code, vec![swap1, swap3, swap1, swap2]); + + let kernel1 = parse_and_assemble(&["%stack ((a: 3), (b: 3), c) -> (c, b, a)"]); + let kernel2 = + parse_and_assemble(&["%stack (a, b, c, d, e, f, g) -> (g, d, e, f, a, b, c)"]); + assert_eq!(kernel1.code, kernel2.code); + let mut consts = HashMap::new(); consts.insert("LIFE".into(), 42.into()); parse_and_assemble_ext(&["%stack (a, b) -> (b, @LIFE)"], consts, true); diff --git a/evm/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs index 24cf01e1..bad60d03 100644 --- a/evm/src/cpu/kernel/ast.rs +++ b/evm/src/cpu/kernel/ast.rs @@ -19,7 +19,7 @@ pub(crate) enum Item { /// The first list gives names to items on the top of the stack. /// The second list specifies replacement items. /// Example: `(a, b, c) -> (c, 5, 0x20, @SOME_CONST, a)`. - StackManipulation(Vec, Vec), + StackManipulation(Vec, Vec), /// Declares a global label. GlobalLabelDeclaration(String), /// Declares a label that is local to the current file. @@ -36,6 +36,14 @@ pub(crate) enum Item { Bytes(Vec), } +/// The left hand side of a %stack stack-manipulation macro. +#[derive(Eq, PartialEq, Clone, Debug)] +pub(crate) enum StackPlaceholder { + Identifier(String), + Block(String, usize), +} + +/// The right hand side of a %stack stack-manipulation macro. #[derive(Eq, PartialEq, Clone, Debug)] pub(crate) enum StackReplacement { /// Can be either a named item or a label. diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 8ea7de4b..227e2466 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -21,7 +21,10 @@ macro_call = ${ "%" ~ !(^"macro" | ^"endmacro" | ^"rep" | ^"endrep" | ^"stack") repeat = { ^"%rep" ~ literal ~ item* ~ ^"%endrep" } paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } -stack = { ^"%stack" ~ paramlist ~ "->" ~ stack_replacements } +stack = { ^"%stack" ~ stack_placeholders ~ "->" ~ stack_replacements } +stack_placeholders = { "(" ~ stack_placeholder ~ ("," ~ stack_placeholder)* ~ ")" } +stack_placeholder = { identifier | stack_block } +stack_block = { "(" ~ identifier ~ ":" ~ literal_decimal ~ ")" } stack_replacements = { "(" ~ stack_replacement ~ ("," ~ stack_replacement)* ~ ")" } stack_replacement = { literal | identifier | constant | macro_label | variable } global_label_decl = ${ ^"GLOBAL " ~ identifier ~ ":" } diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index 35bde4b6..89da016c 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -4,6 +4,7 @@ use ethereum_types::U256; use pest::iterators::Pair; use pest::Parser; +use super::ast::StackPlaceholder; use crate::cpu::kernel::ast::{File, Item, PushTarget, StackReplacement}; /// Parses EVM assembly code. @@ -99,14 +100,11 @@ fn parse_stack(item: Pair) -> Item { let mut inner = item.into_inner(); let params = inner.next().unwrap(); - assert_eq!(params.as_rule(), Rule::paramlist); + assert_eq!(params.as_rule(), Rule::stack_placeholders); let replacements = inner.next().unwrap(); assert_eq!(replacements.as_rule(), Rule::stack_replacements); - let params = params - .into_inner() - .map(|param| param.as_str().to_string()) - .collect(); + let params = params.into_inner().map(parse_stack_placeholder).collect(); let replacements = replacements .into_inner() .map(parse_stack_replacement) @@ -114,6 +112,21 @@ fn parse_stack(item: Pair) -> Item { Item::StackManipulation(params, replacements) } +fn parse_stack_placeholder(target: Pair) -> StackPlaceholder { + assert_eq!(target.as_rule(), Rule::stack_placeholder); + let inner = target.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::identifier => StackPlaceholder::Identifier(inner.as_str().into()), + Rule::stack_block => { + let mut block = inner.into_inner(); + let identifier = block.next().unwrap().as_str(); + let length = block.next().unwrap().as_str().parse().unwrap(); + StackPlaceholder::Block(identifier.to_string(), length) + } + _ => panic!("Unexpected {:?}", inner.as_rule()), + } +} + fn parse_stack_replacement(target: Pair) -> StackReplacement { assert_eq!(target.as_rule(), Rule::stack_replacement); let inner = target.into_inner().next().unwrap(); diff --git a/evm/src/cpu/kernel/stack/stack_manipulation.rs b/evm/src/cpu/kernel/stack/stack_manipulation.rs index 9f685953..faec7e04 100644 --- a/evm/src/cpu/kernel/stack/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack/stack_manipulation.rs @@ -1,13 +1,13 @@ use std::cmp::Ordering; use std::collections::hash_map::Entry::{Occupied, Vacant}; -use std::collections::{BinaryHeap, HashMap}; +use std::collections::{BinaryHeap, HashMap, HashSet}; use std::hash::Hash; use itertools::Itertools; use crate::cpu::columns::NUM_CPU_COLUMNS; use crate::cpu::kernel::assembler::BYTES_PER_OFFSET; -use crate::cpu::kernel::ast::{Item, PushTarget, StackReplacement}; +use crate::cpu::kernel::ast::{Item, PushTarget, StackPlaceholder, StackReplacement}; use crate::cpu::kernel::stack::permutations::{get_stack_ops_for_perm, is_permutation}; use crate::cpu::kernel::stack::stack_manipulation::StackOp::Pop; use crate::cpu::kernel::utils::u256_to_trimmed_be_bytes; @@ -25,25 +25,50 @@ pub(crate) fn expand_stack_manipulation(body: Vec) -> Vec { expanded } -fn expand(names: Vec, replacements: Vec) -> Vec { +fn expand(names: Vec, replacements: Vec) -> Vec { + let mut stack_blocks = HashMap::new(); + let mut stack_names = HashSet::new(); + let mut src = names .iter() .cloned() - .map(StackItem::NamedItem) + .flat_map(|item| match item { + StackPlaceholder::Identifier(name) => { + stack_names.insert(name.clone()); + vec![StackItem::NamedItem(name)] + } + StackPlaceholder::Block(name, n) => { + stack_blocks.insert(name.clone(), n); + (0..n) + .map(|i| { + let literal_name = format!("block_{}_{}", name, i); + StackItem::NamedItem(literal_name) + }) + .collect_vec() + } + }) .collect_vec(); let mut dst = replacements .into_iter() - .map(|item| match item { + .flat_map(|item| match item { StackReplacement::Identifier(name) => { // May be either a named item or a label. Named items have precedence. - if names.contains(&name) { - StackItem::NamedItem(name) + if stack_blocks.contains_key(&name) { + let n = *stack_blocks.get(&name).unwrap(); + (0..n) + .map(|i| { + let literal_name = format!("block_{}_{}", name, i); + StackItem::NamedItem(literal_name) + }) + .collect_vec() + } else if stack_names.contains(&name) { + vec![StackItem::NamedItem(name)] } else { - StackItem::PushTarget(PushTarget::Label(name)) + vec![StackItem::PushTarget(PushTarget::Label(name))] } } - StackReplacement::Literal(n) => StackItem::PushTarget(PushTarget::Literal(n)), + StackReplacement::Literal(n) => vec![StackItem::PushTarget(PushTarget::Literal(n))], StackReplacement::MacroLabel(_) | StackReplacement::MacroVar(_) | StackReplacement::Constant(_) => { From cae5f4870cd3c57b777a7d8c6470e17f3151f246 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Sat, 10 Sep 2022 13:20:30 -0700 Subject: [PATCH 85/95] Stack pointer + underflow/overflow checks (#710) * Stack pointer + underflow/overflow checks * Daniel comments * Extra docs --- evm/src/cpu/columns/mod.rs | 7 ++ evm/src/cpu/control_flow.rs | 39 ++++++--- evm/src/cpu/cpu_stark.rs | 7 +- evm/src/cpu/mod.rs | 1 + evm/src/cpu/stack_bounds.rs | 157 ++++++++++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 evm/src/cpu/stack_bounds.rs diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 567c5a97..93e93ce6 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -38,6 +38,13 @@ pub struct CpuColumnsView { /// If CPU cycle: The program counter for the current instruction. pub program_counter: T, + /// If CPU cycle: The stack length. + pub stack_len: T, + + /// If CPU cycle: A prover-provided value needed to show that the instruction does not cause the + /// stack to underflow or overflow. + pub stack_len_bounds_aux: T, + /// If CPU cycle: We're in kernel (privileged) mode. pub is_kernel_mode: T, diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index e6ded598..5a43f7cf 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -68,14 +68,16 @@ pub fn eval_packed_generic( lv.is_cpu_cycle * is_native_instruction * (lv.is_kernel_mode - nv.is_kernel_mode), ); - // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU - // cycle row is route_txn (the entry point of our kernel) and it is in kernel mode. + // If a non-CPU cycle row is followed by a CPU cycle row, then: + // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - execution is in kernel mode, and + // - the stack is empty. + let is_last_noncpu_cycle = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle; let pc_diff = nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["route_txn"]); - yield_constr.constraint_transition((lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * pc_diff); - yield_constr.constraint_transition( - (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * (nv.is_kernel_mode - P::ONES), - ); + yield_constr.constraint_transition(is_last_noncpu_cycle * pc_diff); + yield_constr.constraint_transition(is_last_noncpu_cycle * (nv.is_kernel_mode - P::ONES)); + yield_constr.constraint_transition(is_last_noncpu_cycle * nv.stack_len); // The last row must be a CPU cycle row. yield_constr.constraint_last_row(lv.is_cpu_cycle - P::ONES); @@ -115,17 +117,32 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr.constraint_transition(builder, kernel_constr); } - // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU - // cycle row is route_txn (the entry point of our kernel) and it is in kernel mode. + // If a non-CPU cycle row is followed by a CPU cycle row, then: + // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - execution is in kernel mode, and + // - the stack is empty. { - let filter = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); + let is_last_noncpu_cycle = + builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); + + // Start at `route_txn`. let route_txn = builder.constant_extension(F::Extension::from_canonical_usize( KERNEL.global_labels["route_txn"], )); let pc_diff = builder.sub_extension(nv.program_counter, route_txn); - let pc_constr = builder.mul_extension(filter, pc_diff); + let pc_constr = builder.mul_extension(is_last_noncpu_cycle, pc_diff); yield_constr.constraint_transition(builder, pc_constr); - let kernel_constr = builder.mul_sub_extension(filter, nv.is_kernel_mode, filter); + + // Start in kernel mode + let kernel_constr = builder.mul_sub_extension( + is_last_noncpu_cycle, + nv.is_kernel_mode, + is_last_noncpu_cycle, + ); + yield_constr.constraint_transition(builder, kernel_constr); + + // Start with empty stack + let kernel_constr = builder.mul_extension(is_last_noncpu_cycle, nv.stack_len); yield_constr.constraint_transition(builder, kernel_constr); } diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 9fd4792d..9949b044 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -9,7 +9,9 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; -use crate::cpu::{bootstrap_kernel, control_flow, decode, jumps, simple_logic, syscalls}; +use crate::cpu::{ + bootstrap_kernel, control_flow, decode, jumps, simple_logic, stack_bounds, syscalls, +}; use crate::cross_table_lookup::Column; use crate::memory::NUM_CHANNELS; use crate::stark::Stark; @@ -94,6 +96,7 @@ impl CpuStark { let local_values: &mut CpuColumnsView<_> = local_values.borrow_mut(); decode::generate(local_values); simple_logic::generate(local_values); + stack_bounds::generate(local_values); // Must come after `decode`. } } @@ -115,6 +118,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark(lv: &mut CpuColumnsView) { + let cycle_filter = lv.is_cpu_cycle; + if cycle_filter == F::ZERO { + return; + } + + let check_underflow: F = DECREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let check_overflow: F = INCREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let no_check = F::ONE - (check_underflow + check_overflow); + + let disallowed_len = check_overflow * F::from_canonical_u64(MAX_USER_STACK_SIZE) - no_check; + let diff = lv.stack_len - disallowed_len; + + let user_mode = F::ONE - lv.is_kernel_mode; + let rhs = user_mode + check_underflow; + + lv.stack_len_bounds_aux = match diff.try_inverse() { + Some(diff_inv) => diff_inv * rhs, // `rhs` may be a value other than 1 or 0 + None => { + assert_eq!(rhs, F::ZERO); + F::ZERO + } + } +} + +pub fn eval_packed( + lv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + // `check_underflow`, `check_overflow`, and `no_check` are mutually exclusive. + let check_underflow: P = DECREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let check_overflow: P = INCREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let no_check = P::ONES - (check_underflow + check_overflow); + + // If `check_underflow`, then the instruction we are executing pops a value from the stack + // without reading it from memory, and the usual underflow checks do not work. We must show that + // `lv.stack_len` is not 0. We choose to perform this check whether or not we're in kernel mode. + // (The check in kernel mode is not necessary if the kernel is correct, but this is an easy + // sanity check. + // If `check_overflow`, then the instruction we are executing increases the stack length by 1. + // If we are in user mode, then we must show that the stack length is not currently + // `MAX_USER_STACK_SIZE`, as this is the maximum for the user stack. Note that this check must + // not run in kernel mode as the kernel's stack length is unrestricted. + // If `no_check`, then we don't need to check anything. The constraint is written to always + // test that `lv.stack_len` does not equal _something_ so we just show that it's not -1, which + // is always true. + + // 0 if `check_underflow`, `MAX_USER_STACK_SIZE` if `check_overflow`, and -1 if `no_check`. + let disallowed_len = + check_overflow * P::Scalar::from_canonical_u64(MAX_USER_STACK_SIZE) - no_check; + // This `lhs` must equal some `rhs`. If `rhs` is nonzero, then this shows that `lv.stack_len` is + // not `disallowed_len`. + let lhs = (lv.stack_len - disallowed_len) * lv.stack_len_bounds_aux; + + // We want this constraint to be active if we're in user mode OR the instruction might overflow. + // (In other words, we want to _skip_ overflow checks in kernel mode). + let user_mode = P::ONES - lv.is_kernel_mode; + // `rhs` is may be 0, 1, or 2. It's 0 if we're in kernel mode and we would be checking for + // overflow. + // Note: if `user_mode` and `check_underflow` then, `rhs` is 2. This is fine: we're still + // showing that `lv.stack_len - disallowed_len` is nonzero. + let rhs = user_mode + check_underflow; + + yield_constr.constraint(lv.is_cpu_cycle * (lhs - rhs)); +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + let one = builder.one_extension(); + let max_stack_size = + builder.constant_extension(F::from_canonical_u64(MAX_USER_STACK_SIZE).into()); + + // `check_underflow`, `check_overflow`, and `no_check` are mutually exclusive. + let check_underflow = builder.add_many_extension(DECREMENTING_FLAGS.map(|i| lv[i])); + let check_overflow = builder.add_many_extension(INCREMENTING_FLAGS.map(|i| lv[i])); + let no_check = { + let any_check = builder.add_extension(check_underflow, check_overflow); + builder.sub_extension(one, any_check) + }; + + // If `check_underflow`, then the instruction we are executing pops a value from the stack + // without reading it from memory, and the usual underflow checks do not work. We must show that + // `lv.stack_len` is not 0. We choose to perform this check whether or not we're in kernel mode. + // (The check in kernel mode is not necessary if the kernel is correct, but this is an easy + // sanity check. + // If `check_overflow`, then the instruction we are executing increases the stack length by 1. + // If we are in user mode, then we must show that the stack length is not currently + // `MAX_USER_STACK_SIZE`, as this is the maximum for the user stack. Note that this check must + // not run in kernel mode as the kernel's stack length is unrestricted. + // If `no_check`, then we don't need to check anything. The constraint is written to always + // test that `lv.stack_len` does not equal _something_ so we just show that it's not -1, which + // is always true. + + // 0 if `check_underflow`, `MAX_USER_STACK_SIZE` if `check_overflow`, and -1 if `no_check`. + let disallowed_len = builder.mul_sub_extension(check_overflow, max_stack_size, no_check); + // This `lhs` must equal some `rhs`. If `rhs` is nonzero, then this shows that `lv.stack_len` is + // not `disallowed_len`. + let lhs = { + let diff = builder.sub_extension(lv.stack_len, disallowed_len); + builder.mul_extension(diff, lv.stack_len_bounds_aux) + }; + + // We want this constraint to be active if we're in user mode OR the instruction might overflow. + // (In other words, we want to _skip_ overflow checks in kernel mode). + let user_mode = builder.sub_extension(one, lv.is_kernel_mode); + // `rhs` is may be 0, 1, or 2. It's 0 if we're in kernel mode and we would be checking for + // overflow. + // Note: if `user_mode` and `check_underflow` then, `rhs` is 2. This is fine: we're still + // showing that `lv.stack_len - disallowed_len` is nonzero. + let rhs = builder.add_extension(user_mode, check_underflow); + + let constr = { + let diff = builder.sub_extension(lhs, rhs); + builder.mul_extension(lv.is_cpu_cycle, diff) + }; + yield_constr.constraint(builder, constr); +} From 11666f02d2e7262716d43e0d9c13d409d67c19b5 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Sun, 11 Sep 2022 19:16:39 +0200 Subject: [PATCH 86/95] Clippy --- plonky2/src/util/serialization.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/util/serialization.rs b/plonky2/src/util/serialization.rs index da03213c..978134b6 100644 --- a/plonky2/src/util/serialization.rs +++ b/plonky2/src/util/serialization.rs @@ -282,7 +282,7 @@ impl Buffer { arity: usize, compressed: bool, ) -> Result> { - let evals = self.read_field_ext_vec::(arity - if compressed { 1 } else { 0 })?; + let evals = self.read_field_ext_vec::(arity - usize::from(compressed))?; let merkle_proof = self.read_merkle_proof()?; Ok(FriQueryStep { evals, From a930c1a8234e619e908315a09d6daf997b8b7a39 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 12 Sep 2022 08:09:17 +0200 Subject: [PATCH 87/95] s/l1/l0 --- evm/src/recursive_verifier.rs | 12 ++++++------ evm/src/verifier.rs | 18 +++++++++--------- field/src/zero_poly_coset.rs | 4 ++-- plonky2/src/plonk/plonk_common.rs | 12 ++++++------ plonky2/src/plonk/vanishing_poly.rs | 22 +++++++++++----------- starky/src/recursive_verifier.rs | 12 ++++++------ starky/src/verifier.rs | 18 +++++++++--------- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index 800ee461..000efce9 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -181,8 +181,8 @@ fn verify_stark_proof_with_challenges_circuit< let degree_bits = proof.recover_degree_bits(inner_config); let zeta_pow_deg = builder.exp_power_of_2_extension(challenges.stark_zeta, degree_bits); let z_h_zeta = builder.sub_extension(zeta_pow_deg, one); - let (l_1, l_last) = - eval_l_1_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); + let (l_0, l_last) = + eval_l_0_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); let last = builder.constant_extension(F::Extension::primitive_root_of_unity(degree_bits).inverse()); let z_last = builder.sub_extension(challenges.stark_zeta, last); @@ -191,7 +191,7 @@ fn verify_stark_proof_with_challenges_circuit< builder.zero_extension(), challenges.stark_alphas.clone(), z_last, - l_1, + l_0, l_last, ); @@ -254,7 +254,7 @@ fn verify_stark_proof_with_challenges_circuit< ); } -fn eval_l_1_and_l_last_circuit, const D: usize>( +fn eval_l_0_and_l_last_circuit, const D: usize>( builder: &mut CircuitBuilder, log_n: usize, x: ExtensionTarget, @@ -263,12 +263,12 @@ fn eval_l_1_and_l_last_circuit, const D: usize>( let n = builder.constant_extension(F::Extension::from_canonical_usize(1 << log_n)); let g = builder.constant_extension(F::Extension::primitive_root_of_unity(log_n)); let one = builder.one_extension(); - let l_1_deno = builder.mul_sub_extension(n, x, n); + let l_0_deno = builder.mul_sub_extension(n, x, n); let l_last_deno = builder.mul_sub_extension(g, x, one); let l_last_deno = builder.mul_extension(n, l_last_deno); ( - builder.div_extension(z_x, l_1_deno), + builder.div_extension(z_x, l_0_deno), builder.div_extension(z_x, l_last_deno), ) } diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 9b56422d..3f5a5a88 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -133,7 +133,7 @@ where }; let degree_bits = proof.recover_degree_bits(config); - let (l_1, l_last) = eval_l_1_and_l_last(degree_bits, challenges.stark_zeta); + let (l_0, l_last) = eval_l_0_and_l_last(degree_bits, challenges.stark_zeta); let last = F::primitive_root_of_unity(degree_bits).inverse(); let z_last = challenges.stark_zeta - last.into(); let mut consumer = ConstraintConsumer::::new( @@ -143,7 +143,7 @@ where .map(|&alpha| F::Extension::from_basefield(alpha)) .collect::>(), z_last, - l_1, + l_0, l_last, ); let num_permutation_zs = stark.num_permutation_batches(config); @@ -204,10 +204,10 @@ where Ok(()) } -/// Evaluate the Lagrange polynomials `L_1` and `L_n` at a point `x`. -/// `L_1(x) = (x^n - 1)/(n * (x - 1))` -/// `L_n(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. -fn eval_l_1_and_l_last(log_n: usize, x: F) -> (F, F) { +/// Evaluate the Lagrange polynomials `L_0` and `L_(n-1)` at a point `x`. +/// `L_0(x) = (x^n - 1)/(n * (x - 1))` +/// `L_(n-1)(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. +fn eval_l_0_and_l_last(log_n: usize, x: F) -> (F, F) { let n = F::from_canonical_usize(1 << log_n); let g = F::primitive_root_of_unity(log_n); let z_x = x.exp_power_of_2(log_n) - F::ONE; @@ -222,10 +222,10 @@ mod tests { use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; - use crate::verifier::eval_l_1_and_l_last; + use crate::verifier::eval_l_0_and_l_last; #[test] - fn test_eval_l_1_and_l_last() { + fn test_eval_l_0_and_l_last() { type F = GoldilocksField; let log_n = 5; let n = 1 << log_n; @@ -234,7 +234,7 @@ mod tests { let expected_l_first_x = PolynomialValues::selector(n, 0).ifft().eval(x); let expected_l_last_x = PolynomialValues::selector(n, n - 1).ifft().eval(x); - let (l_first_x, l_last_x) = eval_l_1_and_l_last(log_n, x); + let (l_first_x, l_last_x) = eval_l_0_and_l_last(log_n, x); assert_eq!(l_first_x, expected_l_first_x); assert_eq!(l_last_x, expected_l_last_x); } diff --git a/field/src/zero_poly_coset.rs b/field/src/zero_poly_coset.rs index 18cc3238..8d63bc69 100644 --- a/field/src/zero_poly_coset.rs +++ b/field/src/zero_poly_coset.rs @@ -51,8 +51,8 @@ impl ZeroPolyOnCoset { packed } - /// Returns `L_1(x) = Z_H(x)/(n * (x - 1))` with `x = w^i`. - pub fn eval_l1(&self, i: usize, x: F) -> F { + /// Returns `L_0(x) = Z_H(x)/(n * (x - 1))` with `x = w^i`. + pub fn eval_l_0(&self, i: usize, x: F) -> F { // Could also precompute the inverses using Montgomery. self.eval(i) * (self.n * (x - F::ONE)).inverse() } diff --git a/plonky2/src/plonk/plonk_common.rs b/plonky2/src/plonk/plonk_common.rs index 4f92d732..e947353b 100644 --- a/plonky2/src/plonk/plonk_common.rs +++ b/plonky2/src/plonk/plonk_common.rs @@ -64,31 +64,31 @@ pub(crate) fn eval_zero_poly(n: usize, x: F) -> F { x.exp_u64(n as u64) - F::ONE } -/// Evaluate the Lagrange basis `L_1` with `L_1(1) = 1`, and `L_1(x) = 0` for other members of an +/// Evaluate the Lagrange basis `L_0` with `L_0(1) = 1`, and `L_0(x) = 0` for other members of the /// order `n` multiplicative subgroup. -pub(crate) fn eval_l_1(n: usize, x: F) -> F { +pub(crate) fn eval_l_0(n: usize, x: F) -> F { if x.is_one() { // The code below would divide by zero, since we have (x - 1) in both the numerator and // denominator. return F::ONE; } - // L_1(x) = (x^n - 1) / (n * (x - 1)) + // L_0(x) = (x^n - 1) / (n * (x - 1)) // = Z(x) / (n * (x - 1)) eval_zero_poly(n, x) / (F::from_canonical_usize(n) * (x - F::ONE)) } -/// Evaluates the Lagrange basis L_1(x), which has L_1(1) = 1 and vanishes at all other points in +/// Evaluates the Lagrange basis L_0(x), which has L_0(1) = 1 and vanishes at all other points in /// the order-`n` subgroup. /// /// Assumes `x != 1`; if `x` could be 1 then this is unsound. -pub(crate) fn eval_l_1_circuit, const D: usize>( +pub(crate) fn eval_l_0_circuit, const D: usize>( builder: &mut CircuitBuilder, n: usize, x: ExtensionTarget, x_pow_n: ExtensionTarget, ) -> ExtensionTarget { - // L_1(x) = (x^n - 1) / (n * (x - 1)) + // L_0(x) = (x^n - 1) / (n * (x - 1)) // = Z(x) / (n * (x - 1)) let one = builder.one_extension(); let neg_one = builder.neg_one(); diff --git a/plonky2/src/plonk/vanishing_poly.rs b/plonky2/src/plonk/vanishing_poly.rs index ab0ba53b..303f698b 100644 --- a/plonky2/src/plonk/vanishing_poly.rs +++ b/plonky2/src/plonk/vanishing_poly.rs @@ -10,7 +10,7 @@ use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CommonCircuitData; use crate::plonk::config::GenericConfig; use crate::plonk::plonk_common; -use crate::plonk::plonk_common::eval_l_1_circuit; +use crate::plonk::plonk_common::eval_l_0_circuit; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBaseBatch}; use crate::util::partial_products::{check_partial_products, check_partial_products_circuit}; use crate::util::reducing::ReducingFactorTarget; @@ -41,17 +41,17 @@ pub(crate) fn eval_vanishing_poly< let constraint_terms = evaluate_gate_constraints(common_data, vars); - // The L_1(x) (Z(x) - 1) vanishing terms. + // The L_0(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::new(); // The terms checking the partial products. let mut vanishing_partial_products_terms = Vec::new(); - let l1_x = plonk_common::eval_l_1(common_data.degree(), x); + let l_0_x = plonk_common::eval_l_0(common_data.degree(), x); for i in 0..common_data.config.num_challenges { let z_x = local_zs[i]; let z_gx = next_zs[i]; - vanishing_z_1_terms.push(l1_x * (z_x - F::Extension::ONE)); + vanishing_z_1_terms.push(l_0_x * (z_x - F::Extension::ONE)); let numerator_values = (0..common_data.config.num_routed_wires) .map(|j| { @@ -135,7 +135,7 @@ pub(crate) fn eval_vanishing_poly_base_batch< let mut numerator_values = Vec::with_capacity(num_routed_wires); let mut denominator_values = Vec::with_capacity(num_routed_wires); - // The L_1(x) (Z(x) - 1) vanishing terms. + // The L_0(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::with_capacity(num_challenges); // The terms checking the partial products. let mut vanishing_partial_products_terms = Vec::new(); @@ -152,11 +152,11 @@ pub(crate) fn eval_vanishing_poly_base_batch< let constraint_terms = PackedStridedView::new(&constraint_terms_batch, n, k); - let l1_x = z_h_on_coset.eval_l1(index, x); + let l_0_x = z_h_on_coset.eval_l_0(index, x); for i in 0..num_challenges { let z_x = local_zs[i]; let z_gx = next_zs[i]; - vanishing_z_1_terms.push(l1_x * z_x.sub_one()); + vanishing_z_1_terms.push(l_0_x * z_x.sub_one()); numerator_values.extend((0..num_routed_wires).map(|j| { let wire_value = vars.local_wires[j]; @@ -332,12 +332,12 @@ pub(crate) fn eval_vanishing_poly_circuit< evaluate_gate_constraints_circuit(builder, common_data, vars,) ); - // The L_1(x) (Z(x) - 1) vanishing terms. + // The L_0(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::new(); // The terms checking the partial products. let mut vanishing_partial_products_terms = Vec::new(); - let l1_x = eval_l_1_circuit(builder, common_data.degree(), x, x_pow_deg); + let l_0_x = eval_l_0_circuit(builder, common_data.degree(), x, x_pow_deg); // Holds `k[i] * x`. let mut s_ids = Vec::new(); @@ -350,8 +350,8 @@ pub(crate) fn eval_vanishing_poly_circuit< let z_x = local_zs[i]; let z_gx = next_zs[i]; - // L_1(x) Z(x) = 0. - vanishing_z_1_terms.push(builder.mul_sub_extension(l1_x, z_x, l1_x)); + // L_0(x) (Z(x) - 1) = 0. + vanishing_z_1_terms.push(builder.mul_sub_extension(l_0_x, z_x, l_0_x)); let mut numerator_values = Vec::new(); let mut denominator_values = Vec::new(); diff --git a/starky/src/recursive_verifier.rs b/starky/src/recursive_verifier.rs index 7f20d89b..04858d55 100644 --- a/starky/src/recursive_verifier.rs +++ b/starky/src/recursive_verifier.rs @@ -102,8 +102,8 @@ fn verify_stark_proof_with_challenges_circuit< let zeta_pow_deg = builder.exp_power_of_2_extension(challenges.stark_zeta, degree_bits); let z_h_zeta = builder.sub_extension(zeta_pow_deg, one); - let (l_1, l_last) = - eval_l_1_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); + let (l_0, l_last) = + eval_l_0_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); let last = builder.constant_extension(F::Extension::primitive_root_of_unity(degree_bits).inverse()); let z_last = builder.sub_extension(challenges.stark_zeta, last); @@ -112,7 +112,7 @@ fn verify_stark_proof_with_challenges_circuit< builder.zero_extension(), challenges.stark_alphas, z_last, - l_1, + l_0, l_last, ); @@ -170,7 +170,7 @@ fn verify_stark_proof_with_challenges_circuit< ); } -fn eval_l_1_and_l_last_circuit, const D: usize>( +fn eval_l_0_and_l_last_circuit, const D: usize>( builder: &mut CircuitBuilder, log_n: usize, x: ExtensionTarget, @@ -179,12 +179,12 @@ fn eval_l_1_and_l_last_circuit, const D: usize>( let n = builder.constant_extension(F::Extension::from_canonical_usize(1 << log_n)); let g = builder.constant_extension(F::Extension::primitive_root_of_unity(log_n)); let one = builder.one_extension(); - let l_1_deno = builder.mul_sub_extension(n, x, n); + let l_0_deno = builder.mul_sub_extension(n, x, n); let l_last_deno = builder.mul_sub_extension(g, x, one); let l_last_deno = builder.mul_extension(n, l_last_deno); ( - builder.div_extension(z_x, l_1_deno), + builder.div_extension(z_x, l_0_deno), builder.div_extension(z_x, l_last_deno), ) } diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs index 306d3d14..efb3d29c 100644 --- a/starky/src/verifier.rs +++ b/starky/src/verifier.rs @@ -78,7 +78,7 @@ where .unwrap(), }; - let (l_1, l_last) = eval_l_1_and_l_last(degree_bits, challenges.stark_zeta); + let (l_0, l_last) = eval_l_0_and_l_last(degree_bits, challenges.stark_zeta); let last = F::primitive_root_of_unity(degree_bits).inverse(); let z_last = challenges.stark_zeta - last.into(); let mut consumer = ConstraintConsumer::::new( @@ -88,7 +88,7 @@ where .map(|&alpha| F::Extension::from_basefield(alpha)) .collect::>(), z_last, - l_1, + l_0, l_last, ); let permutation_data = stark.uses_permutation_args().then(|| PermutationCheckVars { @@ -144,10 +144,10 @@ where Ok(()) } -/// Evaluate the Lagrange polynomials `L_1` and `L_n` at a point `x`. -/// `L_1(x) = (x^n - 1)/(n * (x - 1))` -/// `L_n(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. -fn eval_l_1_and_l_last(log_n: usize, x: F) -> (F, F) { +/// Evaluate the Lagrange polynomials `L_0` and `L_(n-1)` at a point `x`. +/// `L_0(x) = (x^n - 1)/(n * (x - 1))` +/// `L_(n-1)(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. +fn eval_l_0_and_l_last(log_n: usize, x: F) -> (F, F) { let n = F::from_canonical_usize(1 << log_n); let g = F::primitive_root_of_unity(log_n); let z_x = x.exp_power_of_2(log_n) - F::ONE; @@ -189,10 +189,10 @@ mod tests { use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; - use crate::verifier::eval_l_1_and_l_last; + use crate::verifier::eval_l_0_and_l_last; #[test] - fn test_eval_l_1_and_l_last() { + fn test_eval_l_0_and_l_last() { type F = GoldilocksField; let log_n = 5; let n = 1 << log_n; @@ -201,7 +201,7 @@ mod tests { let expected_l_first_x = PolynomialValues::selector(n, 0).ifft().eval(x); let expected_l_last_x = PolynomialValues::selector(n, n - 1).ifft().eval(x); - let (l_first_x, l_last_x) = eval_l_1_and_l_last(log_n, x); + let (l_first_x, l_last_x) = eval_l_0_and_l_last(log_n, x); assert_eq!(l_first_x, expected_l_first_x); assert_eq!(l_last_x, expected_l_last_x); } From b25986ce57e39782bf0451b72cf831e58931d95f Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Tue, 13 Sep 2022 22:03:08 -0700 Subject: [PATCH 88/95] parentheses change --- evm/src/cpu/kernel/assembler.rs | 8 ++++---- evm/src/cpu/kernel/evm_asm.pest | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index f52ae29d..da71d5ce 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -562,14 +562,14 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); - - let kernel = parse_and_assemble(&["%stack (a, (b: 3), c) -> (c)"]); + + let kernel = parse_and_assemble(&["%stack (a, b: 3, c) -> (c)"]); assert_eq!(kernel.code, vec![pop, pop, pop, pop]); - let kernel = parse_and_assemble(&["%stack ((a: 2), (b: 2)) -> (b, a)"]); + let kernel = parse_and_assemble(&["%stack (a: 2, b: 2) -> (b, a)"]); assert_eq!(kernel.code, vec![swap1, swap3, swap1, swap2]); - let kernel1 = parse_and_assemble(&["%stack ((a: 3), (b: 3), c) -> (c, b, a)"]); + let kernel1 = parse_and_assemble(&["%stack (a: 3, b: 3, c) -> (c, b, a)"]); let kernel2 = parse_and_assemble(&["%stack (a, b, c, d, e, f, g) -> (g, d, e, f, a, b, c)"]); assert_eq!(kernel1.code, kernel2.code); diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 227e2466..89d06e74 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -23,8 +23,8 @@ paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } stack = { ^"%stack" ~ stack_placeholders ~ "->" ~ stack_replacements } stack_placeholders = { "(" ~ stack_placeholder ~ ("," ~ stack_placeholder)* ~ ")" } -stack_placeholder = { identifier | stack_block } -stack_block = { "(" ~ identifier ~ ":" ~ literal_decimal ~ ")" } +stack_placeholder = { stack_block | identifier } +stack_block = { identifier ~ ":" ~ literal_decimal } stack_replacements = { "(" ~ stack_replacement ~ ("," ~ stack_replacement)* ~ ")" } stack_replacement = { literal | identifier | constant | macro_label | variable } global_label_decl = ${ ^"GLOBAL " ~ identifier ~ ":" } From a5f34d9a2efa41a8946f1073ddf8be6119da7347 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Tue, 13 Sep 2022 22:03:19 -0700 Subject: [PATCH 89/95] fix --- evm/src/cpu/kernel/assembler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index da71d5ce..0471bf99 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -562,7 +562,7 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); - + let kernel = parse_and_assemble(&["%stack (a, b: 3, c) -> (c)"]); assert_eq!(kernel.code, vec![pop, pop, pop, pop]); From dc145501fdd8af104d81a7584c73f633644a4020 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 14 Sep 2022 10:10:08 +0200 Subject: [PATCH 90/95] Remove `num_virtual_targets` from `CommonCircuitData` --- plonky2/src/iop/generator.rs | 1 - plonky2/src/iop/witness.rs | 9 ++------- plonky2/src/plonk/circuit_builder.rs | 1 - plonky2/src/plonk/circuit_data.rs | 2 -- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/plonky2/src/iop/generator.rs b/plonky2/src/iop/generator.rs index 5bedf13d..3614b2e4 100644 --- a/plonky2/src/iop/generator.rs +++ b/plonky2/src/iop/generator.rs @@ -31,7 +31,6 @@ pub(crate) fn generate_partial_witness< let mut witness = PartitionWitness::new( config.num_wires, common_data.degree(), - common_data.num_virtual_targets, &prover_data.representative_map, ); diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs index caa22c33..e7f21241 100644 --- a/plonky2/src/iop/witness.rs +++ b/plonky2/src/iop/witness.rs @@ -278,14 +278,9 @@ pub struct PartitionWitness<'a, F: Field> { } impl<'a, F: Field> PartitionWitness<'a, F> { - pub fn new( - num_wires: usize, - degree: usize, - num_virtual_targets: usize, - representative_map: &'a [usize], - ) -> Self { + pub fn new(num_wires: usize, degree: usize, representative_map: &'a [usize]) -> Self { Self { - values: vec![None; degree * num_wires + num_virtual_targets], + values: vec![None; representative_map.len()], representative_map, num_wires, degree, diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index ca68af9c..579a9017 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -814,7 +814,6 @@ impl, const D: usize> CircuitBuilder { quotient_degree_factor, num_gate_constraints, num_constants, - num_virtual_targets: self.virtual_target_index, num_public_inputs, k_is, num_partial_products, diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index fb839978..20697d36 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -265,8 +265,6 @@ pub struct CommonCircuitData< /// The number of constant wires. pub(crate) num_constants: usize, - pub(crate) num_virtual_targets: usize, - pub(crate) num_public_inputs: usize, /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. From 7d9e81362d8bc849698668f4d42d182d786a6d25 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Thu, 15 Sep 2022 14:59:16 -0700 Subject: [PATCH 91/95] Python prototype of cache-oblivious FFT (#722) --- projects/cache-friendly-fft/__init__.py | 229 +++++++++++++++++++++++ projects/cache-friendly-fft/transpose.py | 61 ++++++ projects/cache-friendly-fft/util.py | 6 + 3 files changed, 296 insertions(+) create mode 100644 projects/cache-friendly-fft/__init__.py create mode 100644 projects/cache-friendly-fft/transpose.py create mode 100644 projects/cache-friendly-fft/util.py diff --git a/projects/cache-friendly-fft/__init__.py b/projects/cache-friendly-fft/__init__.py new file mode 100644 index 00000000..08f1acac --- /dev/null +++ b/projects/cache-friendly-fft/__init__.py @@ -0,0 +1,229 @@ +import numpy as np + +from transpose import transpose_square +from util import lb_exact + + +def _interleave(x, scratch): + """Interleave the elements in an array in-place. + + For example, if `x` is `array([1, 2, 3, 4, 5, 6, 7, 8])`, then its + contents will be rearranged to `array([1, 5, 2, 6, 3, 7, 4, 8])`. + + `scratch` is an externally-allocated buffer, whose `dtype` matches + `x` and whose length is at least half the length of `x`. + """ + assert len(x.shape) == len(scratch.shape) == 1 + + n, = x.shape + assert n % 2 == 0 + + half_n = n // 2 + assert scratch.shape[0] >= half_n + + assert x.dtype == scratch.dtype + scratch = scratch[:half_n] + + scratch[:] = x[:half_n] # Save the first half of `x`. + for i in range(half_n): + x[2 * i] = scratch[i] + x[2 * i + 1] = x[half_n + i] + + +def _deinterleave(x, scratch): + """Deinterleave the elements in an array in-place. + + For example, if `x` is `array([1, 2, 3, 4, 5, 6, 7, 8])`, then its + contents will be rearranged to `array([1, 3, 5, 7, 2, 4, 6, 8])`. + + `scratch` is an externally-allocated buffer, whose `dtype` matches + `x` and whose length is at least half the length of `x`. + """ + assert len(x.shape) == len(scratch.shape) == 1 + + n, = x.shape + assert n % 2 == 0 + + half_n = n // 2 + assert scratch.shape[0] >= half_n + + assert x.dtype == scratch.dtype + scratch = scratch[:half_n] + + for i in range(half_n): + x[i] = x[2 * i] + scratch[i] = x[2 * i + 1] + x[half_n:] = scratch + + +def _fft_inplace_evenpow(x, scratch): + """In-place FFT of length 2^even""" + # Reshape `x` to a square matrix in row-major order. + vec_len = x.shape[0] + n = 1 << (lb_exact(vec_len) >> 1) # Matrix dimension + x.shape = n, n, 1 + + # We want to recursively apply FFT to every column. Because `x` is + # in row-major order, we transpose it to make the columns contiguous + # in memory, then recurse, and finally transpose it back. While the + # row is in cache, we also multiply by the twiddle factors. + transpose_square(x) + for i, row in enumerate(x[..., 0]): + _fft_inplace(row, scratch) + # Multiply by the twiddle factors + for j in range(n): + row[j] *= np.exp(-2j * np.pi * (i * j) / vec_len) + transpose_square(x) + + # Now recursively apply FFT to the rows. + for row in x[..., 0]: + _fft_inplace(row, scratch) + + # Transpose again before returning. + transpose_square(x) + + +def _fft_inplace_oddpow(x, scratch): + """In-place FFT of length 2^odd""" + # This code is based on `_fft_inplace_evenpow`, but it has to + # account for some additional complications. + + vec_len = x.shape[0] + # `vec_len` is an odd power of 2, so we cannot reshape `x` to a + # matrix square. Instead, we'll (conceptually) reshape it to a + # matrix that's twice as wide as it is high. E.g., `[1 ... 8]` + # becomes `[1 2 3 4]` + # `[5 6 7 8]`. + col_len = 1 << (lb_exact(vec_len) >> 1) + row_len = col_len << 1 + + # We can only perform efficient, in-place transposes on square + # matrices, so we will actually treat this as a square matrix of + # 2-tuples, e.g. `[(1 2) (3 4)]` + # `[(5 6) (7 8)]`. + # Note that we can currently `.reshape` it to our intended wide + # matrix (although this is broken by transposition). + x.shape = col_len, col_len, 2 + + # We want to apply FFT to each column. We transpose our + # matrix-of-tuples and get something like `[(1 2) (5 6)]` + # `[(3 4) (7 8)]`. + # Note that each row of the transposed matrix represents two columns + # of the original matrix. We can deinterleave the values to recover + # the original columns. + transpose_square(x) + + for i, row_pair in enumerate(x): + # `row_pair` represents two columns of the original matrix. + # Their values must be deinterleaved to recover the columns. + row_pair.shape = row_len, + _deinterleave(row_pair, scratch) + # The below are rows of the transposed matrix(/cols of the + # original matrix. + row0 = row_pair[:col_len] + row1 = row_pair[col_len:] + + # Apply FFT and twiddle factors to each. + _fft_inplace(row0, scratch) + for j in range(col_len): + row0[j] *= np.exp(-2j * np.pi * ((2 * i) * j) / vec_len) + _fft_inplace(row1, scratch) + for j in range(col_len): + row1[j] *= np.exp(-2j * np.pi * ((2 * i + 1) * j) / vec_len) + + # Re-interleave them and transpose back. + _interleave(row_pair, scratch) + + transpose_square(x) + + # Recursively apply FFT to each row of the matrix. + for row in x: + # Turn vec of 2-tuples into vec of single elements. + row.shape = row_len, + _fft_inplace(row, scratch) + + # Transpose again before returning. This again involves + # deinterleaving. + transpose_square(x) + for row_pair in x: + row_pair.shape = row_len, + _deinterleave(row_pair, scratch) + + +def _fft_inplace(x, scratch): + """In-place FFT.""" + # Avoid modifying the shape of the original. + # This does not copy the buffer. + x = x.view() + assert x.flags['C_CONTIGUOUS'] + + n, = x.shape + if n == 1: + return + if n == 2: + x0, x1 = x + x[0] = x0 + x1 + x[1] = x0 - x1 + return + + lb_n = lb_exact(n) + is_odd = lb_n & 1 != 0 + if is_odd: + _fft_inplace_oddpow(x, scratch) + else: + _fft_inplace_evenpow(x, scratch) + + +def _scrach_length(lb_n): + """Find the amount of scratch space required to run the FFT. + + Layers where the input's length is an even power of two do not + require scratch space, but the layers where that power is odd do. + """ + if lb_n == 0: + # Length-1 input. + return 0 + # Repeatedly halve lb_n as long as it's even. This is the same as + # `n = sqrt(n)`, where the `sqrt` is exact. + while lb_n & 1 == 0: + lb_n >>= 1 + # `lb_n` is now odd, so `n` is not an even power of 2. + lb_res = (lb_n - 1) >> 1 + if lb_res == 0: + # Special case (n == 2 or n == 4): no scratch needed. + return 0 + return 1 << lb_res + + +def fft(x): + """Returns the FFT of `x`. + + This is a wrapper around an in-place routine, provided for user + convenience. + """ + n, = x.shape + lb_n = lb_exact(n) # Raises if not a power of 2. + # We have one scratch buffer for the whole algorithm. If we were to + # parallelize it, we'd need one thread-local buffer for each worker + # thread. + scratch_len = _scrach_length(lb_n) + if scratch_len == 0: + scratch = None + else: + scratch = np.empty_like(x, shape=scratch_len, order='C', subok=False) + + res = x.copy(order='C') + _fft_inplace(res, scratch) + + return res + + +if __name__ == "__main__": + LENGTH = 1 << 10 + v = np.random.normal(size=LENGTH).astype(complex) + print(v) + numpy_fft = np.fft.fft(v) + print(numpy_fft) + our_fft = fft(v) + print(our_fft) + print(np.isclose(numpy_fft, our_fft).all()) diff --git a/projects/cache-friendly-fft/transpose.py b/projects/cache-friendly-fft/transpose.py new file mode 100644 index 00000000..ea20bf6b --- /dev/null +++ b/projects/cache-friendly-fft/transpose.py @@ -0,0 +1,61 @@ +from util import lb_exact + + +def _swap_transpose_square(a, b): + """Transpose two square matrices in-place and swap them. + + The matrices must be a of shape `(n, n, m)`, where the `m` dimension + may be of arbitrary length and is not moved. + """ + assert len(a.shape) == len(b.shape) == 3 + n = a.shape[0] + m = a.shape[2] + assert n == a.shape[1] == b.shape[0] == b.shape[1] + assert m == b.shape[2] + + if n == 0: + return + if n == 1: + # Swap the two matrices (transposition is a no-op). + a = a[0, 0] + b = b[0, 0] + # Recall that each element of the matrix is an `m`-vector. Swap + # all `m` elements. + for i in range(m): + a[i], b[i] = b[i], a[i] + return + + half_n = n >> 1 + # Transpose and swap top-left of `a` with top-left of `b`. + _swap_transpose_square(a[:half_n, :half_n], b[:half_n, :half_n]) + # ...top-right of `a` with bottom-left of `b`. + _swap_transpose_square(a[:half_n, half_n:], b[half_n:, :half_n]) + # ...bottom-left of `a` with top-right of `b`. + _swap_transpose_square(a[half_n:, :half_n], b[:half_n, half_n:]) + # ...bottom-right of `a` with bottom-right of `b`. + _swap_transpose_square(a[half_n:, half_n:], b[half_n:, half_n:]) + + +def transpose_square(a): + """In-place transpose of a square matrix. + + The matrix must be a of shape `(n, n, m)`, where the `m` dimension + may be of arbitrary length and is not moved. + """ + if len(a.shape) != 3: + raise ValueError("a must be a matrix of batches") + n, n_, _ = a.shape + if n != n_: + raise ValueError("a must be square") + lb_exact(n) + + if n <= 1: + return # Base case: no-op + + half_n = n >> 1 + # Transpose top-left quarter in-place. + transpose_square(a[:half_n, :half_n]) + # Transpose top-right and bottom-left quarters and swap them. + _swap_transpose_square(a[:half_n, half_n:], a[half_n:, :half_n]) + # Transpose bottom-right quarter in-place. + transpose_square(a[half_n:, half_n:]) diff --git a/projects/cache-friendly-fft/util.py b/projects/cache-friendly-fft/util.py new file mode 100644 index 00000000..50118827 --- /dev/null +++ b/projects/cache-friendly-fft/util.py @@ -0,0 +1,6 @@ +def lb_exact(n): + """Returns `log2(n)`, raising if `n` is not a power of 2.""" + lb = n.bit_length() - 1 + if lb < 0 or n != 1 << lb: + raise ValueError(f"{n} is not a power of 2") + return lb From 9d1d179eb16c3db7e6fb04c7bf9fe5338c9b9120 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Sat, 17 Sep 2022 10:47:55 -0700 Subject: [PATCH 92/95] Verify that comparison output is zero or one (#715) --- evm/src/arithmetic/compare.rs | 56 ++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/evm/src/arithmetic/compare.rs b/evm/src/arithmetic/compare.rs index 8410cade..a6566db5 100644 --- a/evm/src/arithmetic/compare.rs +++ b/evm/src/arithmetic/compare.rs @@ -45,6 +45,14 @@ pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) lv[CMP_OUTPUT] = F::from_canonical_u64(br); } +fn eval_packed_generic_check_is_one_bit( + yield_constr: &mut ConstraintConsumer

, + filter: P, + x: P, +) { + yield_constr.constraint(filter * x * (x - P::ONES)); +} + pub(crate) fn eval_packed_generic_lt( yield_constr: &mut ConstraintConsumer

, is_op: P, @@ -69,15 +77,31 @@ pub fn eval_packed_generic( range_check_error!(CMP_INPUT_0, 16); range_check_error!(CMP_INPUT_1, 16); range_check_error!(CMP_AUX_INPUT, 16); - range_check_error!([CMP_OUTPUT], 1); + + let is_lt = lv[IS_LT]; + let is_gt = lv[IS_GT]; let input0 = CMP_INPUT_0.map(|c| lv[c]); let input1 = CMP_INPUT_1.map(|c| lv[c]); let aux = CMP_AUX_INPUT.map(|c| lv[c]); let output = lv[CMP_OUTPUT]; - eval_packed_generic_lt(yield_constr, lv[IS_LT], input0, input1, aux, output); - eval_packed_generic_lt(yield_constr, lv[IS_GT], input1, input0, aux, output); + let is_cmp = is_lt + is_gt; + eval_packed_generic_check_is_one_bit(yield_constr, is_cmp, output); + + eval_packed_generic_lt(yield_constr, is_lt, input0, input1, aux, output); + eval_packed_generic_lt(yield_constr, is_gt, input1, input0, aux, output); +} + +fn eval_ext_circuit_check_is_one_bit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + yield_constr: &mut RecursiveConstraintConsumer, + filter: ExtensionTarget, + x: ExtensionTarget, +) { + let constr = builder.mul_sub_extension(x, x, x); + let filtered_constr = builder.mul_extension(filter, constr); + yield_constr.constraint(builder, filtered_constr); } #[allow(clippy::needless_collect)] @@ -117,29 +141,19 @@ pub fn eval_ext_circuit, const D: usize>( lv: &[ExtensionTarget; NUM_ARITH_COLUMNS], yield_constr: &mut RecursiveConstraintConsumer, ) { + let is_lt = lv[IS_LT]; + let is_gt = lv[IS_GT]; + let input0 = CMP_INPUT_0.map(|c| lv[c]); let input1 = CMP_INPUT_1.map(|c| lv[c]); let aux = CMP_AUX_INPUT.map(|c| lv[c]); let output = lv[CMP_OUTPUT]; - eval_ext_circuit_lt( - builder, - yield_constr, - lv[IS_LT], - input0, - input1, - aux, - output, - ); - eval_ext_circuit_lt( - builder, - yield_constr, - lv[IS_GT], - input1, - input0, - aux, - output, - ); + let is_cmp = builder.add_extension(is_lt, is_gt); + eval_ext_circuit_check_is_one_bit(builder, yield_constr, is_cmp, output); + + eval_ext_circuit_lt(builder, yield_constr, is_lt, input0, input1, aux, output); + eval_ext_circuit_lt(builder, yield_constr, is_gt, input1, input0, aux, output); } #[cfg(test)] From 3007b5e779dde133218bcefaec8773781688b66b Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 19 Sep 2022 11:25:21 +0200 Subject: [PATCH 93/95] Fix DTH_ROOT for degree 1 extension --- field/src/extension/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/field/src/extension/mod.rs b/field/src/extension/mod.rs index f54d669c..ed596764 100644 --- a/field/src/extension/mod.rs +++ b/field/src/extension/mod.rs @@ -22,8 +22,8 @@ pub trait OEF: FieldExtension { } impl OEF<1> for F { - const W: Self::BaseField = F::ZERO; - const DTH_ROOT: Self::BaseField = F::ZERO; + const W: Self::BaseField = F::ONE; + const DTH_ROOT: Self::BaseField = F::ONE; } pub trait Frobenius: OEF { @@ -80,8 +80,8 @@ pub trait Extendable: Field + Sized { impl + FieldExtension<1, BaseField = F>> Extendable<1> for F { type Extension = F; - const W: Self = F::ZERO; - const DTH_ROOT: Self = F::ZERO; + const W: Self = F::ONE; + const DTH_ROOT: Self = F::ONE; const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; 1] = [F::MULTIPLICATIVE_GROUP_GENERATOR]; const EXT_POWER_OF_TWO_GENERATOR: [Self; 1] = [F::POWER_OF_TWO_GENERATOR]; } From d7d8803d0a5101bd0dbeb9ebf1dea23b154629e1 Mon Sep 17 00:00:00 2001 From: BGluth Date: Mon, 19 Sep 2022 11:05:48 -0600 Subject: [PATCH 94/95] Replaced `PartialTrie` definitions with `eth-trie-utils` crate - There were enough dependencies that it made sense to move `PartialTrie` logic to its own crate. --- evm/Cargo.toml | 2 ++ evm/src/generation/mod.rs | 5 +++-- evm/src/generation/partial_trie.rs | 34 ------------------------------ evm/src/proof.rs | 3 ++- evm/tests/transfer_to_new_addr.rs | 2 +- 5 files changed, 8 insertions(+), 38 deletions(-) delete mode 100644 evm/src/generation/partial_trie.rs diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 1e8f3c56..9d1bfa02 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } +eth-trie-utils = { git = "https://github.com/mir-protocol/eth-trie-utils.git", rev = "3ca443fd18e3f6d209dd96cbad851e05ae058b34" } maybe_rayon = { path = "../maybe_rayon" } anyhow = "1.0.40" env_logger = "0.9.0" @@ -21,6 +22,7 @@ pest_derive = "2.1.0" rand = "0.8.5" rand_chacha = "0.3.1" rlp = "0.5.1" +serde = { version = "1.0.144", features = ["derive"] } keccak-hash = "0.9.0" tiny-keccak = "2.0.2" diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 5b0b3c8f..baf2ec32 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,15 +1,16 @@ +use eth_trie_utils::partial_trie::PartialTrie; use ethereum_types::Address; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use serde::{Deserialize, Serialize}; use crate::all_stark::{AllStark, NUM_TABLES}; use crate::config::StarkConfig; use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; use crate::cpu::columns::NUM_CPU_COLUMNS; use crate::cpu::kernel::global_metadata::GlobalMetadata; -use crate::generation::partial_trie::PartialTrie; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::memory::NUM_CHANNELS; @@ -17,9 +18,9 @@ use crate::proof::{BlockMetadata, PublicValues, TrieRoots}; use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; -pub mod partial_trie; pub(crate) mod state; +#[derive(Clone, Debug, Deserialize, Serialize)] /// Inputs needed for trace generation. pub struct GenerationInputs { pub signed_txns: Vec>, diff --git a/evm/src/generation/partial_trie.rs b/evm/src/generation/partial_trie.rs deleted file mode 100644 index 5e52e1e0..00000000 --- a/evm/src/generation/partial_trie.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ethereum_types::U256; - -#[derive(Clone, Debug)] -/// A partial trie, or a sub-trie thereof. This mimics the structure of an Ethereum trie, except -/// with an additional `Hash` node type, representing a node whose data is not needed to process -/// our transaction. -pub enum PartialTrie { - /// An empty trie. - Empty, - /// The digest of trie whose data does not need to be stored. - Hash(U256), - /// A branch node, which consists of 16 children and an optional value. - Branch { - children: [Box; 16], - value: Option, - }, - /// An extension node, which consists of a list of nibbles and a single child. - Extension { - nibbles: Nibbles, - child: Box, - }, - /// A leaf node, which consists of a list of nibbles and a value. - Leaf { nibbles: Nibbles, value: Vec }, -} - -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -/// A sequence of nibbles. -pub struct Nibbles { - /// The number of nibbles in this sequence. - pub count: usize, - /// A packed encoding of these nibbles. Only the first (least significant) `4 * count` bits are - /// used. The rest are unused and should be zero. - pub packed: U256, -} diff --git a/evm/src/proof.rs b/evm/src/proof.rs index a5bc61a7..81614e67 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -12,6 +12,7 @@ use plonky2::hash::merkle_tree::MerkleCap; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::target::Target; use plonky2::plonk::config::GenericConfig; +use serde::{Deserialize, Serialize}; use crate::all_stark::NUM_TABLES; use crate::config::StarkConfig; @@ -58,7 +59,7 @@ pub struct TrieRoots { pub receipts_root: U256, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct BlockMetadata { pub block_beneficiary: Address, pub block_timestamp: U256, diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index ecb71076..1cd79194 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -1,10 +1,10 @@ +use eth_trie_utils::partial_trie::PartialTrie; use hex_literal::hex; use plonky2::field::goldilocks_field::GoldilocksField; use plonky2::plonk::config::PoseidonGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::partial_trie::PartialTrie; use plonky2_evm::generation::GenerationInputs; use plonky2_evm::proof::BlockMetadata; use plonky2_evm::prover::prove; From 4d873cdaf5b1c2c929582e2929302c1990e72375 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 13:38:02 -0700 Subject: [PATCH 95/95] zkEVM spec --- evm/spec/.gitignore | 7 ++++ evm/spec/Makefile | 20 ++++++++++ evm/spec/bibliography.bib | 20 ++++++++++ evm/spec/framework.tex | 39 +++++++++++++++++++ evm/spec/instructions.tex | 8 ++++ evm/spec/introduction.tex | 3 ++ evm/spec/tables.tex | 9 +++++ evm/spec/tables/arithmetic.tex | 4 ++ evm/spec/tables/cpu.tex | 4 ++ evm/spec/tables/keccak-f.tex | 4 ++ evm/spec/tables/keccak-sponge.tex | 4 ++ evm/spec/tables/logic.tex | 4 ++ evm/spec/tables/memory.tex | 61 ++++++++++++++++++++++++++++++ evm/spec/tries.tex | 4 ++ evm/spec/zkevm.pdf | Bin 0 -> 146723 bytes evm/spec/zkevm.tex | 59 +++++++++++++++++++++++++++++ 16 files changed, 250 insertions(+) create mode 100644 evm/spec/.gitignore create mode 100644 evm/spec/Makefile create mode 100644 evm/spec/bibliography.bib create mode 100644 evm/spec/framework.tex create mode 100644 evm/spec/instructions.tex create mode 100644 evm/spec/introduction.tex create mode 100644 evm/spec/tables.tex create mode 100644 evm/spec/tables/arithmetic.tex create mode 100644 evm/spec/tables/cpu.tex create mode 100644 evm/spec/tables/keccak-f.tex create mode 100644 evm/spec/tables/keccak-sponge.tex create mode 100644 evm/spec/tables/logic.tex create mode 100644 evm/spec/tables/memory.tex create mode 100644 evm/spec/tries.tex create mode 100644 evm/spec/zkevm.pdf create mode 100644 evm/spec/zkevm.tex diff --git a/evm/spec/.gitignore b/evm/spec/.gitignore new file mode 100644 index 00000000..ba6d4007 --- /dev/null +++ b/evm/spec/.gitignore @@ -0,0 +1,7 @@ +## Files generated by pdflatex, bibtex, etc. +*.aux +*.log +*.out +*.toc +*.bbl +*.blg diff --git a/evm/spec/Makefile b/evm/spec/Makefile new file mode 100644 index 00000000..97954528 --- /dev/null +++ b/evm/spec/Makefile @@ -0,0 +1,20 @@ +DOCNAME=zkevm + +all: pdf + +.PHONY: clean + +quick: + pdflatex $(DOCNAME).tex + +pdf: + pdflatex $(DOCNAME).tex + bibtex $(DOCNAME).aux + pdflatex $(DOCNAME).tex + pdflatex $(DOCNAME).tex + +view: pdf + open $(DOCNAME).pdf + +clean: + rm -f *.blg *.bbl *.aux *.log diff --git a/evm/spec/bibliography.bib b/evm/spec/bibliography.bib new file mode 100644 index 00000000..41fa56b8 --- /dev/null +++ b/evm/spec/bibliography.bib @@ -0,0 +1,20 @@ +@misc{stark, + author = {Eli Ben-Sasson and + Iddo Bentov and + Yinon Horesh and + Michael Riabzev}, + title = {Scalable, transparent, and post-quantum secure computational integrity}, + howpublished = {Cryptology ePrint Archive, Report 2018/046}, + year = {2018}, + note = {\url{https://ia.cr/2018/046}}, +} + +@misc{plonk, + author = {Ariel Gabizon and + Zachary J. Williamson and + Oana Ciobotaru}, + title = {PLONK: Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge}, + howpublished = {Cryptology ePrint Archive, Report 2019/953}, + year = {2019}, + note = {\url{https://ia.cr/2019/953}}, +} diff --git a/evm/spec/framework.tex b/evm/spec/framework.tex new file mode 100644 index 00000000..122e1c75 --- /dev/null +++ b/evm/spec/framework.tex @@ -0,0 +1,39 @@ +\section{STARK framework} +\label{framework} + + +\subsection{Cost model} + +Our zkEVM is designed for efficient verification by STARKs \cite{stark}, particularly by an AIR with degree 3 constraints. In this model, the prover bottleneck is typically constructing Merkle trees, particularly constructing the tree containing low-degree extensions of witness polynomials. + +More specifically, we target a constraint system of degree 3. + + +\subsection{Field selection} +\label{field} +Our zkEVM is designed to have its execution traces encoded in a particular prime field $\mathbb{F}_p$, with $p = 2^{64} - 2^{32} + 1$. A nice property of this field is that it can represent the results of many common \texttt{u32} operations. For example, (widening) \texttt{u32} multiplication has a maximum value of $(2^{32} - 1)^2$, which is less than $p$. In fact a \texttt{u32} multiply-add has a maximum value of $p - 1$, so the result can be represented with a single field element, although if we were to add a carry in bit, this no longer holds. + +This field also enables a very efficient reduction method. Observe that +$$ +2^{64} \equiv 2^{32} - 1 \pmod p +$$ +and consequently +\begin{align*} + 2^{96} &\equiv 2^{32} (2^{32} - 1) \pmod p \\ + &\equiv 2^{64} - 2^{32} \pmod p \\ + &\equiv -1 \pmod p. +\end{align*} +To reduce a 128-bit number $n$, we first rewrite $n$ as $n_0 + 2^{64} n_1 + 2^{96} n_2$, where $n_0$ is 64 bits and $n_1, n_2$ are 32 bits each. Then +\begin{align*} + n &\equiv n_0 + 2^{64} n_1 + 2^{96} n_2 \pmod p \\ + &\equiv n_0 + (2^{32} - 1) n_1 - n_2 \pmod p +\end{align*} +After computing $(2^{32} - 1) n_1$, which can be done with a shift and subtraction, we add the first two terms, subtracting $p$ if overflow occurs. We then subtract $n_2$, adding $p$ if underflow occurs. + +At this point we have reduced $n$ to a \texttt{u64}. This partial reduction is adequate for most purposes, but if we needed the result in canonical form, we would perform a final conditional subtraction. + + +\subsection{Cross-table lookups} +\label{ctl} + +TODO diff --git a/evm/spec/instructions.tex b/evm/spec/instructions.tex new file mode 100644 index 00000000..ea096982 --- /dev/null +++ b/evm/spec/instructions.tex @@ -0,0 +1,8 @@ +\section{Privileged instructions} +\label{privileged-instructions} + +\begin{enumerate} + \item[0xFB.] \texttt{MLOAD\_GENERAL}. Returns + \item[0xFC.] \texttt{MSTORE\_GENERAL}. Returns + \item[TODO.] \texttt{STACK\_SIZE}. Returns +\end{enumerate} diff --git a/evm/spec/introduction.tex b/evm/spec/introduction.tex new file mode 100644 index 00000000..cb969a16 --- /dev/null +++ b/evm/spec/introduction.tex @@ -0,0 +1,3 @@ +\section{Introduction} + +TODO diff --git a/evm/spec/tables.tex b/evm/spec/tables.tex new file mode 100644 index 00000000..92ee1d2a --- /dev/null +++ b/evm/spec/tables.tex @@ -0,0 +1,9 @@ +\section{Tables} +\label{tables} + +\input{tables/cpu} +\input{tables/arithmetic} +\input{tables/logic} +\input{tables/memory} +\input{tables/keccak-f} +\input{tables/keccak-sponge} diff --git a/evm/spec/tables/arithmetic.tex b/evm/spec/tables/arithmetic.tex new file mode 100644 index 00000000..eafed3ba --- /dev/null +++ b/evm/spec/tables/arithmetic.tex @@ -0,0 +1,4 @@ +\subsection{Arithmetic} +\label{arithmetic} + +TODO diff --git a/evm/spec/tables/cpu.tex b/evm/spec/tables/cpu.tex new file mode 100644 index 00000000..76c8be07 --- /dev/null +++ b/evm/spec/tables/cpu.tex @@ -0,0 +1,4 @@ +\subsection{CPU} +\label{cpu} + +TODO diff --git a/evm/spec/tables/keccak-f.tex b/evm/spec/tables/keccak-f.tex new file mode 100644 index 00000000..76e9e9f4 --- /dev/null +++ b/evm/spec/tables/keccak-f.tex @@ -0,0 +1,4 @@ +\subsection{Keccak-f} +\label{keccak-f} + +This table computes the Keccak-f[1600] permutation. diff --git a/evm/spec/tables/keccak-sponge.tex b/evm/spec/tables/keccak-sponge.tex new file mode 100644 index 00000000..29f71ba1 --- /dev/null +++ b/evm/spec/tables/keccak-sponge.tex @@ -0,0 +1,4 @@ +\subsection{Keccak sponge} +\label{keccak-sponge} + +This table computes the Keccak256 hash, a sponge-based hash built on top of the Keccak-f[1600] permutation. diff --git a/evm/spec/tables/logic.tex b/evm/spec/tables/logic.tex new file mode 100644 index 00000000..b430c95d --- /dev/null +++ b/evm/spec/tables/logic.tex @@ -0,0 +1,4 @@ +\subsection{Logic} +\label{logic} + +TODO diff --git a/evm/spec/tables/memory.tex b/evm/spec/tables/memory.tex new file mode 100644 index 00000000..9653f391 --- /dev/null +++ b/evm/spec/tables/memory.tex @@ -0,0 +1,61 @@ +\subsection{Memory} +\label{memory} + +For simplicity, let's treat addresses and values as individual field elements. The generalization to multi-element addresses and values is straightforward. + +Each row of the memory table corresponds to a single memory operation (a read or a write), and contains the following columns: + +\begin{enumerate} + \item $a$, the target address + \item $r$, an ``is read'' flag, which should be 1 for a read or 0 for a write + \item $v$, the value being read or written + \item $\tau$, the timestamp of the operation +\end{enumerate} +The memory table should be ordered by $(a, \tau)$. Note that the correctness memory could be checked as follows: +\begin{enumerate} + \item Verify the ordering by checking that $(a_i, \tau_i) < (a_{i+1}, \tau_{i+1})$ for each consecutive pair. + \item Enumerate the purportedly-ordered log while tracking a ``current'' value $c$, which is initially zero.\footnote{EVM memory is zero-initialized.} + \begin{enumerate} + \item Upon observing an address which doesn't match that of the previous row, set $c \leftarrow 0$. + \item Upon observing a write, set $c \leftarrow v$. + \item Upon observing a read, check that $v = c$. + \end{enumerate} +\end{enumerate} + +The ordering check is slightly involved since we are comparing multiple columns. To facilitate this, we add an additional column $e$, where the prover can indicate whether two consecutive addresses are equal. An honest prover will set +$$ +e_i \leftarrow \begin{cases} + 1 & \text{if } a_i = a_{i + 1}, \\ + 0 & \text{otherwise}. +\end{cases} +$$ +We then impose the following transition constraints: +\begin{enumerate} + \item $e_i (e_i - 1) = 0$, + \item $e_i (a_i - a_{i + 1}) = 0$, + \item $e_i (\tau_{i + 1} - \tau_i) + (1 - e_i) (a_{i + 1} - a_i - 1) < 2^{32}$. +\end{enumerate} +The last constraint emulates a comparison between two addresses or timestamps by bounding their difference; this assumes that all addresses and timestamps fit in 32 bits and that the field is larger than that. + +Finally, the iterative checks can be arithmetized by introducing a trace column for the current value $c$. We add a boundary constraint $c_0 = 0$, and the following transition constraints: +\todo{This is out of date, we don't actually need a $c$ column.} +\begin{enumerate} + \item $v_{\text{from},i} = c_i$, + \item $c_{i + 1} = e_i v_{\text{to},i}$. +\end{enumerate} + + +\subsubsection{Virtual memory} + +In the EVM, each contract call has its own address space. Within that address space, there are separate segments for code, main memory, stack memory, calldata, and returndata. Thus each address actually has three compoments: +\begin{enumerate} + \item an execution context, representing a contract call, + \item a segment ID, used to separate code, main memory, and so forth, and so on + \item a virtual address. +\end{enumerate} +The comparisons now involve several columns, which requires some minor adaptations to the technique described above; we will leave these as an exercise to the reader. + + +\subsubsection{Timestamps} + +TODO: Explain $\tau = \texttt{NUM\_CHANNELS} \times \texttt{cycle} + \texttt{channel}$. diff --git a/evm/spec/tries.tex b/evm/spec/tries.tex new file mode 100644 index 00000000..d8fc2674 --- /dev/null +++ b/evm/spec/tries.tex @@ -0,0 +1,4 @@ +\section{Merkle Patricia tries} +\label{tries} + +TODO diff --git a/evm/spec/zkevm.pdf b/evm/spec/zkevm.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8501cfbb9e6ce463f65f0f2369c78cd08d445096 GIT binary patch literal 146723 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�_ncVO0^~NVW#^zSaAhgef#+i7&cSSP^?7O@Fsyxw4)UIK;Z>ZLh2>PO&KJQ%Jk5E{%N^{9qUxe{wWZ zhk?|R$kj#fs;u=qZ4C)t^ipyuC#@K}Mvg}4pc(og=9xsv)_0<`1YENLcMB`Wc!)8P zL%wgcLzr^mgts?bOJqK??0Tl{kF0psf`<^1typvaVg+?bTb_9=9~k3A1fOjA?s7wb zLSJz2ujO=hb-SwK1`E{~klmP}KSv9se|c;u7i^STd#;y+z5IF$)LdIfl}St9 zQFHlf4~n;kD<4dkbu`8JrREUwpR zz6J}KCQs>!N$gcsSbc~Gb*37Lc&Jd<7=Gi_XIWG!Q$`SiLF(q?Yy;1boXfKrs{|Cg z&lb)N;+m%x{=0Jn60j{R?0ZyMVMUlHZileGUf=}D|K`X+161WLwBpkjjjVr^E=-3< zsRfY(`M~O#8Z#+AC4^+q9g~N3L)As}_P~l;i-;Z?KN8B48`{2VAbui5P1*0i-w6rM ze{YDMfnH6m9B)i8J=!Okct9f=X(sWYA$~DQsRY&^?zebFN{b z%z-R~tk#5ZOmo{Ark+j(Hn6 zhQXvzYdYZ?%C3s0a5ew~%YH)*GrG{(U#-Z*C$bDLEv)~n-{lsQisQIGBMigOoD7aH zX7c%ccKJfw5~7AHMy_xO+%$~ApS422rItRmY1=ifCY-o{`Jr`%g|!7u`G={yi{$|7 zy!Da^E3!gl`OkdgAAt;iZvv|9w6*pL?T-EX$Xf0j8+B%9bfjnx#z0eLuD1fX@?~FH zqt<+6C$u%S7va;9kAa5TGuUA#NO|o>84&L zV^j^R%L6SjMDRf{;ui`l0=7A-JtMA?`@a$N-hh|bwW@DJU4TQqBq<{TQ+d&rc6YCA z^uG2jCE4MM^gg{9idNX2e;>}I#V@$5AOTm~yc$DCXconzbH$tpC&iUErDj8J*(7@w z|0s(vU3dC@Uvp$QrRxw>NHY}WCsV4{tjFUrIrUV~I+-kfTX>2;k^Vu2!+)z>Lxq$# z-Xm}kXhWPq$*5N^q&<-BM|@N#+h#UxP#a(j1;c|WGC##yjVi(BE2TZ^0BmAxtL&IZ zg!SFy06@4oZ4U)ji@--8?C^FBnOzi#q>CFQnlb>>-v@m9w8^Jl_GHWwmU(sV$hI>` z%}un$f@R>6gTlwb3$ItFW8kS!F)3hN_3{F*Kpp8PL39IvwhSM`gs?+^r5zHciICvY zlX%wmhk>ESu>12TJ%LrwN{8}{H$al!wU2Nrj}ua%TeA{)1r6rMKgZe1p7sK*eqUl+ z@7;=kQ3M92*rraw2m5RLIa6h2fHF}q@PeYUKWK2RsH&-)7rq@YEG$Ht%1dX4H>QymUa0DwH(jAMz`a6j*12gX2j}F zaOytHnao8R`8kFT5CIfF?G8@ymnzV+a<;{1xxwkfcJ6(-ob7-y)!9T$g$!Y#DhZ0V zHMm1o5QUsX(@%K(prBO8nTWs#`s)f0;P$%6W+_5b6oHE=?KCo!<+nobWsvEf)SQ`a z3irvJ0$eSzwOgk75}858W#ZWG8zWzL%YQw-487OfxyZ5(^LVJ1kc6dPH@;1?Ogz|~ ztMdqMv8O(wG=EK%?hCu?HmY>CeLy%?joa=m?YRrE9jm=zo6MrDzCUVzlr%BM5kWC=~V zyWNk50hto9|7KmeJA=U*rK$gjhRT?|x!G4|yzBLVi0yw~+*7avpHSye)c+Q!L<&mW z*ZE@%rvKIjx_S2jrrw#*~?$i0Z^x^2_OvJ&;@&8(Hm>3!VihCqi3QBL9EqwJ@JX)o$fa;rvRv`^bP00CF2l02EA`jdK?Kg~mb68P#yO3~=$@d4 zW*8(O&xD%=7ui!N zKHYH*rDHXQVyK?Xfx;oj8kgfL!C<*dX%H6abE+N_^!kq_&G&{$kRBq7-3O!8GHjEi zNB2_z!%QmMqqsiVM-7RvoQ+rj^Qx0l!XXtO2_cr2klr*LU`McmvS5K5KwKwN{x+;L z>=)>UKckvwGC?xRG0&$-HqfZcMptiY=NTs8zPI39@ud(2DU%1NuWn<5Y<5!4D4|QmgW|#AI-$5$-#h!FI z@$~elul@^Q^ZsG3p4;sevwvjDfV0>^MM^zCc;rrelyrVVN|W@C3Q>ba}hHfcq5CAlPd(g2>!} zd$?#Eh6g4dVSdoVxqf$DZ^clWy~h!A`S{k$qR?vq_qNsicmjt5)^oq!Q-O>6E!!GOjY`CYg2XDoqo!<=DdK zMq@iMK^;|IG!@0C>ezD5VHoWvF5Fkg!)n=#^%Yx|9+i#l1tp*;LoG%pY4Kabg1N@J zyC%@FC*%`ya)mpPzp^QJnb^GvQ|%IEiw%%>5y4#DuB)OCSQ}j4O|zo);omi}K!101 z^Sz3BZANLxNf=8(c;t!>TQ;wk>l0LIJ+cY1io3+~trK(m?Nce%;$x%SVWg^N^4c+9 zz2hOYEkwcLD(=L&?l_SgxAT3!r?f8I!8{7hk*D||w04=7+%$3RtUhxjD%<-g?ZJuD zo$IZEH>kVQNa_;+>c}u6WoW{*o%o(P<@fc${|+zL4~*YadVl@=JR;XA!MA}UTjR_O zyKy++tt&THj`u(|bL&?2IM6*6_6Pd;k2r(5qPe>8PnsI`_deoDSdZfZcNgMYK8!6K zxvy`HcN1SVYc@|g-EW(Axt*b{F}E+Z!+T@8mgsIcRGlN8wZhj)KAMmAtHnaImMf3- zOB1`)uS7!n_xpbiZ#Lo8Hxvll#Hl+FVT;vilNGZ2BcG~BpCWtE)2<#9?LV($^si=dJf`F z{Wz|a4k)%hMB3_8Q`XkWx}b{#Ln;uSLS9E^0oJ9Rs|^(okhb=GSZlV83=&I^>vJ&b z>&o;Y(2;fJ@ejS1Qi%t#t&+h9-~4onyYPk(!~hPUwue9h3pCj(>8T_j@VL7ThF=`$ zR3c}g$}7D(X=MVDOo6TN zGYpQp3c;c|&H1s${tXpDC9f%d*Bjl4KcaosKEHQ|+xya0_7^>|SH5Nuz!L*GQ z)+%!q3R~o58}tpDZaEy_e{a-#Dt5Mrl+T=j;JFUChN@7-nl}t71Vt*ANw&}n) zBkKs`{vBj5a!|n^c&*Uh_pPeQIZUiz>Ny0Ls`^p0y-dHP<-YV`-@nKIoIaMA%#h%o zT@pOjkd^}321#1VD9if_9jf)itb?R0+7pEAC;RW|Q$@cyT&Px>?ks$~Ud)0q@Q9>z zkESdN->H+G52Hqwnd|#2vROcIU+!baZ5%%AetH5+WV=hW6UxF`5+jAO z$Qc#|XkGWDK=G1Vkv~Pn?uBSY6>{7c0+awhuSZanOVw%nh2>|fXGtdVqe@vqd`8e( zo)vZCTgh)~Wvz=wTLLlY$7{u_s^34Ytd80%vTVcF_@ zHt4L8;IR!F&Tci3_+YRO6Zj{kw_Pi>TPQiMppb%d^%q#ruMUn5XfyG5>tK}X%xSkV zq(~*wH4IF87Zk3Pa!~BY?muJUm~dQwAbS^`eAY2*Wiz1Lvd2shg^OsrBd6ulGTQif zi1u*e3H3^(lkd1qYI+hUR3KA#|CB-4{1kTL37+pKsiH=yjf~?b$jOW2m!Id?-$Dt; zk~I9rB0u&jVvt^UGC|ZRcs2PE%x!?%s+Tv9Cfz#UtgKQJ<-LvP+ZoPRzuO?e6(CIgS8DpT3R5WLjn$%4Sy>-&q_d7=4=EGACq%ji#`3cQr zhn!!Xy)D9q0IA>!1p}ms{pnIWp}Bd#%)04)!$tW#Us=AlS73nVex*kQL1dlbZl6&t z(zngaz3Mhr`IOtqOi@OCx^=>oR)W8o&Y7aTm%RvNf-<{sMV;JS6+8i4e-T;%8A*$a zc*86)yKzk{KlgQ}z5fgWHAO^YjP<{KrQQL8e{N+n_2fkIGMV_-W7*09LGc6&H6UYNs|o&lhbgN2=NZ8BeNb*SWs*JIc>Uc>HJ_IU%`kn z5AUh&x?Snx<(^KR$QssjfoT;891kPP(GSPXn}*X2T;enZ&F1A;AH_c^e%Jq#Lm`@k+Mf715jge@=r4?yLsWtHoxbKcE)E+=mb z91G5d^)FQ<*m#~{=+V)UnV)d+ib-sH2VnM&5A{xtjz$a&XZ{8V`F>YeaGpfv)K~|k zpM-GOlu%G!jT0aS@*=F^r~>QL!2Khjdk2^&C`czqVD?UqFkiyK0wKVS0&ex_0x|gc za1J0Hh798Z***FCxLRwAd;l6?nbavDI$~nr75;sopW;Yp*3ix%<-i85L}O+6v;hb} zHsNGgV2^+5&}0f*8Pb~=Aj87KAi3w}Q}97rkj+fN-Te((foS8Yp)L>_K|XD<^AViE zzP53=AwUJjFs}Zj3F8{VKLQB}`cnjfuAv+q{Xsi8F$NC=$#sP^r>+3e09Q(Wy(oGjU8U} zV;uS<^1h2tg8|L>-T6ir&rh$m2X%O8;*`)B-nFsudpk5Gi#e^ zR=^Dj2*L&Q{r$7-un!pm6SE)28X8pH-@0E83o!F-450xC$-5zhc>%+6;G2kp?SKDx ztV@|l8muOgL(m130y+#4WQEabHCodA+CJ&anpuWX_l8Et5cLmF4#Dgl{%WcO%f;{o z@e)n>17=DPJbuwZ^*`Aa;5t1xX!7|u zeDP0y$&Ul_e*2ld_rZMmQ75`N*}ol{{fW8*+=Jo|An5ur=O$l5cx8gHjxW-1e9@~A zKCPZf3FB3Nua<#<@=XUOC&di)#Ufu(A-;fVSoj;Hw)#yR(|6t63OWIC2e1&;?(NFM z`o}~iJ^FdeFPc~Yp1j-nEFR)upICSRJtf&mWWyJ)$qxM63`j8Go(4QR52y#AZ}z+~ zMd;&?ago4yS8_;eN>F<+{6XLBNTK%X-`G(=?A5-=j13l{_+QSZY1P7dKNfuu-w3nwjOc>(SGd z~pwpOh>&+gu*skqM~!B;*(+S57x&?i{hTXwM-H@^AZsm ztqh%R!AK4vF@-&oOqHL;&A(VB3W!Ur4K)HPbHJW-ItWrV6d-vg9bAA8VK2B?@)SZ* zz%P%eItx&m@TBbfr$O;kToZx2mnY@7XS-tMow+r% z^X-#CDbSS@lxp@>&oP$ukmX}6Pd_Fs;5$ZFFyFDBzkZtm>_<$)%rS31fKSZ3skWld ze@YZGx|$Uz_T_p1x!(6N3%rh36o%giSG)tu)d*752j!v@~5p@}~!1)zAKZ&lg zbSVqcSU3d;exymH+&lFwCEHQ(TBj6h->)_+}fd) zTS^GXg*=fFdo$cJvuqlNVh<@<#k#`75H7Jkcjfv8?5MEEIyo^oS5NCJd*aQDqHw*b zE2os+?StG9h<)lf_j8>T;M6nY5;IF~8eu*Kvq`bm6$>@I>wc)iSx z2*;g)tKsiSBk4=KUB?hh{xXj%Vafwj3yxBOcP>jg|O!( zGoKiATLtUy%ha|w9pdGn{02j>3AMvatv_?79Xp3*AwFr|)7&ys zM9-0iR@@03)X<$^kQ-^AW5>$8Y*1bIeC(KSH0)Iry9?vwjL_;WtZfeM{LScFDh$1p z4wa2~<=XFG5pO?@pqiN4<1vYnkyiW&^#0sB@UO_K}slFxA> z=5AoAa60YZGQliSSif(aYB}0{&q0Ognh3g3FZr}QJ`H9oN43Wi@0W#`=IXFn*ZH{n z*1eD?uu=*})u|QYb^`$zrtPj_#&9JtcgNBy)V3!@HC{stiZPp#F5T{wnyAdFRJfQ* zV;iM^7B_B8qz>-w$I52t@6yCOggKjYvw788ox_iJe2q0$%>0OP!en-xuzZ!WR;JoIA9dox22Fqn-6vv>ob?a74(*U|rkDe=Ug#Hwz$(8()7t7A~x{|7B-6-@~j-xdgm;C4o zwz4W}6J2l3WDw7OrvHfU@RsD0IAL|7L3%a_-nbXFsCwVx0z9(S&F1vB!?E<@kJ%G5 z^`;QLGCf++-l-i@ux=Ur3LYN%t-<7Ffi>TGC9&o>&t!wk(=qPb)Jjy=)-#gj2o{0P z!Y;`4Dwg}BC|5;}mx<09-kEDp)wSE{uH_qV%F3#fL{LD%Z$x8a1wc;RQ4i^k zO`{DLi3yAkaQ6HixUepdUQsJjd#+0}9JROo^4AuA^P3S0bB}afhAhje-PC}o{tn?e z?d-gqE@^sZ&2Adgk6r6QAVg1phuR&R^U z6-tZ^_f}`%0(P4$dx<7v^N)eh&MJcM!c*;j?V%?pjJHkBhup9O{Sm2>pjYc~Eeo0Q z&x|RvCsFKq0gTy)D~m-LkojdL>K@;l)Y|;aj9w=P%!;C-Al2$svSshPx9X?$md>1U zDQ~$d>upxhtuQRv{FsnNV)-I0zF@#I+`<=)PD^Dllw_Nh)_K>b>1w!=lVNVvjg?H^ zE=wg-w!0MNX%OJ8O>?UX+=4vp?#e*!n7sVYXWY>+(Va#FLW?ynqT}v_MMhS;2^NKG zEuS)-@D~CGC}ux*X%MTYc4;rv=ydc z*2?u}-<~3u%t=Trrl!B`e}GkTuir!JIqm?k(g^~_MzgP95f`LIWdn|b8_%@hOe`e zr)`SCc3`3PZO~cWWZ#q#(9L+M*^4XKnazUexM+r(vRga)`zAlzfyuc8rR#=@c|2(`=f$mj=kszOX+!JVe=pr;Y>)%6lIkmWksKR&LKNWXQ$ zD!r7axtQYw@mBtj3(1ZJUquf0JLqItW$KHyVq4$wVrTn7$B(g{g>jr)jLl~pmh}-X z9~un51o zj3y)PX^(ZGa+}bfxqx&PN9XjyH{qvL#Xr%VlX5T+%l39Ws2>vPL(@u5C~5ps|I0pO zJI$-3{uYC4ilTkF-J`j|z~um*0FwH~%>}pjiS(*FY=S0nu@*%dYwt4L!K~dgs#n&C z(z6;VR{8zZMR*?fHO*v*a=DUe)270RUb;LL4EX?h_t%cQXp`4(8QP~)rrz@r3X>0bAwE$>YZQ8 z2SHTEx@7Hg!MKDy>s^!pnN-E+>F~PssUIH-qPmIPclJ776tW@$X$Hf(U=GrM{WP-i z31+Hy6h^n&6tszCj#p3~vdu3=o#*g24OBdLkMd0e6bk`i=m&?kYk-jY?oN66ZU$0Ny9+{^|F+_e5K{X9E^9>1 zhODcfz$mK)&}9%0?5OTBNhSna-Bs>R1v;vD7!-})VpGfPrxf)G*QZcrzCR zym0Lm%CICU*Y!s_)^98ZZ&<%hzl5uW2eSzH5nE(Pyb(AEpI#5q146Q|FiI@6TvjyO!0e}0G|0Z;50cp+FWMW=$EGk*f_rFLy@sml*M;V)@mK9N$ixVF8o93z%q(<-d^g6;-PV;JfJ9Qvv> z>G#32uh^Y}8yu ziDu~c38R9EZsLxcplTn}56?f@!oocWBhDl#VIfM#hTLcQuLd1`5$kq14N~s{)1j`B zxwoo!T^eDs9nsWnGC@F9QgU=RQ{2$a4Xq6hC*9jiP&+EsTG=LaTyF#tR_~js!buQgrUod$OqiU(n#%j0t9y*?!?`PcRaJIT zIv*l=Dlf@lxy6D&flkvEM=M-zGfdXW#LV_hLpGc$Ydl)$Iws`~$>B9M#p>sI&Tftk8hht#HBap^7o?a=* zQxY`mKT@KI!}W)xQjO_75m`cMQ21^&(*sVkkHZduY<#cYbbGq=Oh~$T$A64Nh*kQe+ZvTJ0KT+fteLbg z${7=FMottUIMI#fk#Tnk@G%MCvMjE{)dnfyZlZeLp>ZTBwqhSDDt=ET6V^nD zh8rf;-6lmgf%s!#=?Tl~%T!XtzEq^=XwCI1U4yLw8FOkT?^hdzGG=LTKbg(t;O-V+&U_6b%Hr| z^V+R=c)acS+Y{JGOeiPzRuqXOwg8$#0W4E}?*6PnqNnV9N%F4I)BcadsGw+Jj{kR< zaA$RA1!=OyQgX+BXF-6f1uuB1zsnt4#C+?ohTi0Ikci4fo~@#_!`){So9Rs(6jo(d{Hx>g^cVQI)YLN-3lGHZ z7!M!sP_WW+_lC!b({c_1E@(-;lD6gxZc_oe^s!WlTc7`-;{PG+oO*=Of;QW>ZQHhO z+qP}nwr$(CyWh5Ln{&R4NhX;|rmpr*{e!CYtQy88y4S7T(E>zG<;wSWAtho!RXLFA ze==%3kl?(b)kLY_SOGJL41$ck22-Z-#(&;X-7QXrrBn67!gyXLq!1JWEn+)^3I;o|pt>WQY!bZDdo9}3Ql&H@l{ybZc(kN8TCO}kOu}F^npI7> z&C`B0JV_+{LqMEuvML-C{?zGi+n{f~KinrREhtG(FMCbgV)(V3>Ii&0;vNqM#hdSZ zl4yXEwJELEeilhmb=e8RNKrX04jhpWf8Ad}>;>?pu1_W9Gu;m9n)LlTA)YIqw3fM9 zWz)VvO-;MeI5i_W9ykkhZZ|zntb~P|MZ>N$M`4ZUyB8ml;(Ixb0kAzA&#c#Iy$8LB zRIOLHjY1p^m8vHb1N4|)P_)i6I2ht2igzW)xfDg*PE#2OELo|A@+u3F)qzj)Dw19| z#6qIisU^U3w(ptyQher{iKb!kLc6o!m&f( zl>xT6$|8yp9T(1-f4KD5Tz-_45-mo*_G@<_7r?tVBo?dn(gsGclaSoG&z z?paOXHuEU%A8paZqPWpG+==GPW$x?v>Kob{Ahq2Nn%)+ss$WFZJLsBrec`(tWR znXVZ4-7K^3;dn(SRJ&2G(nB0`&nF>t2fNLaLmxX_E#{ z&?N=VZ|z5t=20M*Vgs1q(C5%4Kibh-OioOLqf*`Je!@9%Vx_JnFlIW%+hy9mWiZLt8&gQ=(Ap@0 z-~vfJ8(Hc8Z^wdXaP(Ni-QSBolJ06KdwXZlqc_NUs5s2E=OB6!I_rcGlkUoXWe|Ct zOR~dyI2D0VnlUlu=W?VYC_p=sMY+_&j`RfQ^B+)?IA26me3fL951x)RjKT*(`orO| zEs(LfpD|ZQiqOxT6C6&0M|i7H8ys{!fxmib6B7P~t7wvYAH1jVUu;y)U8W;9CH!y3 z!rod7Vk}3v>8FyV5TG8)siX2I^-H82*&bhD%ti{bc{{fy>G8=V`O zp{UK2Fl-g4($zHVwVGX*wWnu7STAzX8F=OZ`~$E1ZrAt3(Wz|NUaB_nVbRb&u+}{T zS}4BZ%-#dh`Kv)*sg+du+gd5N(RC_W`Fn}bujVQ8}MI|fWDszvNVd#0u7kOku zHkL7w)q7L2%?6>{p5b_;CX*PbYPaE^D&|!H;ySxc;tvpC_0`ExnDqJ@?tOMHPm`yq zR>f1vc`L3nN~opYkre|2;k_c(^uIRyc<$So51wnxFRzrMW*h_x^qYhn`?&`%Vo9pu zK;wJ`h|%e4_^24Q)%654Xc4l3bsyPw_v6JEhd~qM_YGYdb_=n$8+3bD;$}(>bNkH} zC?Bbc(EYGkX8Mid0n^^Cc0&ccDBCfOGXGqcj15!L6s-%+{R@wN+Rgzhfr3z+%h)8kc~q4 zk2e$3JJnRWd8`v#duj_P|I52p*TaS#=A#oj$k^_70##Bq6&-p)8SYSHoMP$9tJd!tL-8A(SwxJ@@&K zeeLNs2DvN|yPm8XL{`VFZz}%uLo&VlogiFj)J7rSZ_Yf&(WkJ+h`O&HIZ-fn&;| ztjE5bInhJVFMHA@;n|ADQ|@*TIiWz4mCmn4cZ);hj0$$p3_3^})E^DGwpUbNd$7*E zqC-$Hrl|IYQyn_I-n$JU#HKcOWbg3RlzP_v%58W@tc6c0BKy3d3~67c z;fua}F%{I&pii2SQ^pS5Ooj#8-?oxPXW0IuV6mY$m0nzE?7n0d*7l7$2t3ARygvI} zc=E$XYOwl*q?=ZLcqO`PP8;?ocCDm&O$c*5Im_@=Y3+jEaV`81TjHB{#$DD)y=m!73}ci{h%qKCC^ezEle%jbL<^a%*!-@5wu_fV{#V~T!gc!t7mJU$pPmab>xcESrna5_ zUZ-LObxh}S(7H49zYb7WV^7@l z(soH;PmtifVop7DNjW@4_^;cJ)@k|vg=ZAUX?>?XR2Tc6X&=m;WS#Gi!P_<*%|DV4 zE`$A_qDawNSAU4Rf9CC%PlnL!sGiH_LC<9}LJH2VjK$S$Ro8l-vhec~wxGks3xv~S zV7Okis+JM>58$JjIEZ@KxAohOGk!33--k73pz2d5K9ernSSl};U9wL|>9RDQ5Ql*W zE)`dc)mKOe>RZm6p#4{h?YJWx>rrU5Z=rFe;|iAGaHG#bWmd-)2LKyH+NZb=K9dAHWWy3pc zDH&I$#vIYb41eUwIdHby)d`aVM$hLpJ|u>0DS&XTdHA!jOvYo^l4qsB8oz8*>@GhN z9HfDC=#0)oaTz(0yN^zK`2IaYUDjFv7$j=Lqv0nx#olLhTs=r#`@+3=!`4XnyEi*z zW)?W;c`R)J{C6_@3L; zs?w5S7)4CEao-7;me^&cN0_2d!OgcIS4d_x`E`&|gMbw4IO8iUiWDAa>&V+FjN?;* z@a)~f)$Zkbn3WgY4WzkE7`b*_V%2g!g~4St2~xF6&Yl_sq#jjS^wRP z1DY3*kX@+sN5U73{Ch?EneLr$#muwf;QlRrHtbctlTRuZqW9hLv;AA-%Zx7C+IKWO zz=m|0?2~x^l85%W&CGuA#5RUmEI@^`Ds@Q{Iw`hOE+1s;a#Aq@Iu!*ecGYY0%aLEt zlJ~zr>u+Ha8e>3Q>@;4@$EVXWf5oePS*7uU-FSk66dOP0;-T91_a-O1BUl?n%aM^0 zW!%Q1^;fV|voE5^Pf1E?Rz-3x_Jb+h{Sh?prd#%!{xrpS9Tg=&i;Sm%u#zq6YE^S0e|}Bo5uUHF(87?8Hw{g?%G7 zhY6}0X5pUO&jFvr=1JUmWJeDlsX(nh{|pI88J%alCEo)-#H=&&`E!<%WthyP1V;jGP{$;1{D4Yp){wYGXG> zDBsdgp#8qI%6jJ{Q7y2I6U^GSrmr^aSgHe}<4z3Q{eVB%7HlnIdJH{l#cd~9tG{u!2 zyEK<8C=&!8mnwJZC#%*h?68W0^-F9lHd3sx2fuMv0jAo`?55#h^ScI@AgL*#&zK>aE7#g1pSb zr;V4-j>fnIj*kp^h`^#$932BM`Tbt{^HJH}9@aeq!c>1j0)2Rz;Qr7tb8JZT50bLs z5E~oFofe3(YCPio4=_#92=V_x&~pCY1T7;069WU&|0b`P2pE}JIoSXA_y1(F7+E;} z6R7@A{r|;iu`i(iSy5I&p@;>8x}}e9ZwGgFEDHk1c6LYygOIm({wqki1P};3g7u>u zYRB$89*gD*Snb-8Ji!0M^Hyy*4F`RYH((1YOWzv zRIm)xwHN&Vdub7vqc;Te{_zuy&1H9y=pHvz3{ zYJU78pYPAWCjfK>&jLox0C0kPCqM(Gb_5rvGp{dAz0}V8{{mUeo&(tU`0&K|mjjF3 z2+SEU7w`%oo?C%7d0$z8wSZP|X9Nn?>HR4OsVz+X`IyyWYjA?N;biHpwDKidn z574azxFSGDVD6s4xnG|MC?&wX&@cYqLJG)2BT)DEM8Unm;RS#z7{DG(y8&}z_k?zI zb{FRu#^VZ3Azc-Kf;;H?54GmL9zblMA0B*QZ02>(=|5(v8e#qS&ZHnSGdP!cVHeuQ z9RCM>(T2xVLsK|GpoeF*>9zCMXPUxL^FY zURh>l@INqXWE9ZA=*$SXq0#Xk(4#{G(C<%M92DZ09e$O+v*{HL4xrC}(U-gYPr>c4 z9n9jdm%t_9ueS8~zDx%p;5B{$R-;p6cDHZ+_dhi2F6Gak>aTk0uX^E+o|uwdd#nGa zD&O^B_`)Uh~f~)5y=;sgiAJ~tz>nM)R>|ee7D(ZV1bkS>C+aIr3 zLo~`dvPm6~8*B4ty3ViZ+PC$h&EP8mDmePRzB+V3WNhlo-}`Rc^vvnar-SG6@gCj6 z*5kLmw4^sP_)~vdOl~wbfQuWL7hyx6WMpnM^#1T`TihwZe7`D;fnl4wgReAT59}Fu zgM)+E`&FcqGoXgZAJY%?1AxZJU$ln?AdQkg0(gMvE4(yjOMQFSsL+2Fq_jJ6zU(eel{pqP}*x0saW|0i#dw2SAOP-{@a1Wwa-M z0X6{WbNu_*xKaOut+)n_fSteh&R-|)4$eQ~1KN?t|5{D|{0YynO{wX{tJ|fYdkN>zC#MytH+{Yhm z?5_VBqqVVH(fOPCI(PNxa`|uDqw4&1>}7Rp{YU*da@b$%@Z$U?-m_(I@$YZh-2MgF z`|$n>ytaGk`49gsHfLrxKW)|izQsO~z5ea@>k|?rkWauE+HMTW5Uh0ZwLJByr7Q$r z7-(4pUJ7u>#!Z^fc23{0L+LQ-&Zh6%p6M4&8vj^eeO=8{?<-uv4?H>v}y&;sQXAo9~}=FN6|*a zrfG+CD+J2rxSH@<2b~}@cO!f3#RiW|E54l#9nGk`Rg8@FZB?DQ92p+7-KWg(H)Em` zbX2(E^B#GOtI+y6!XaX|Ir1tS#!KK!w2(sfmZApU@WpY+X&&f}&g0`9KINBN%J{&( zCp1jiWt~;+odG|K@Lh!+cJ@DKXC}6V&#(3`F7P6+W`q)86$8XIYbKsR3}oe)TF1kw z8~1EN2qUHWW(6m;=hbYbT|J0-;wnt>F+wA1vLo^Mya#}^HXoKa?xS^C>D9q!Mb9>z zre8JtvlISd$PU)!=~w5wFr|>1MG-P-RzhH2BWTSlD?@>xm5H1zBeaNV?51p2B0R~n z&!x_a6Zx^%BQncB=O)wU|6r0CA0t*qXz4Nn7bPTcm`_*jFZ}8(V2|=}dhZaA#|-x> z^-9oWlm*svxJm7j;9SOtN9$Lj@C4=N2DP9IB~oq|neV(!Q~B3K63RY}7N5R2;U82c zGW~81T;~newIMZoAl9z-40bLZ5GvZK`%{B+LCq9ZOr+K_W=ZR38g)ob>v9^o*QAGb zsbAbW#b3aEt^`2lV>cRK-_xh|F1~A#vY(l9ffBM;H?5cMe<(&?wD!F6rH*10Bk}j7 zsVQab?uv%k%aktfW<4wGOL9=ffBWA|qj>IzdEi$(3e(08x+WCS+{rPL&;I9qYfC>u z={PD)LtZcgF^0H=6mzaObC~qkov{eeF}U{=O23^l4lTKYby%(a#+mI`3T8D+gO4N* z(@7gAu5LwnhH3UmsQ%eZt2B$)eh#%q5R<1*UY}coStDdAqT{om(O4az(}5(~-s9uF z7$3x3s@L&R1O7IL1}%{}bFZGhOX3br0sqfBu*P0KXT)6XrASx^N+4`GZn+9C8Lae0 zb19UNuMsuVqAX0ZB$oFe?MWy+=3foFj8%{p)+TR!+K;W1eK+Y*S{GTF?dRR^9^Q-*?L;dnEfil#R$9;H ztLUxEH`-VUc46*k?VQ&iwxzo`+Vm(1jO`{wx*?2^$j^|R72eEn3~_q&7WAt!$jZtg!`mO|0ns{%&h9CaCN%O<%f;csz>t8{LES z<1$r=uHY16J=aQ;(O){O3Aa2LDv9PESVEi!sZA|O^hVuQ)D1do;JTje4Yvh0-C1P_7

Di+kke_@}@cZH~1`L5f9_1atNFqGWFcTz_Vf2KiD+@2Nf zCj5><=8ii^DsZfcAzU{9riL#)n5NExOB*=;IhRRWYCp3%ZB%n7g5~KhLcY6V8orMy zYOeRqfXIB`reK>2DbJo-2DzvL%r~KYE$lKSrpnRRoM((=EtMt6LW)NhLD8Ys9M4_R z3^fr+A0L^W;=0mBEv^5g1OE(2H|HN&EkZ|`jtQwxZu9>Ah1mf~J4ph|BMvmD}5Nj)-a? z0{v!KetG<>E26F>Z)*a+Sx2Q2F?7|Z%A@AaZ=r+5UjCtS03(-g2ce@|GT?g<%$J}Y z)VJxjCDE)L9Kjwy!}(O~bCJ4#fXKraDrnm3;VVF!z~QaZ=D+Z6=pxUnN+c{w8r(*z z9|h;BchT3>Jo6b@!A@tb2^NE?zq90rvh6SP2Lnw+!MbT83=voo`}CwSW`1_iWR%f= z1CMr2c{U%npd#n_?&7%WdqeD=y(xfGe`#_A~>Gb;ggRklkXa! z<9Ey4fH63LFPM~1*>u2~NFBJ`&{p+|`O%-O3E9BpvzPl+>+;A#6+wADBrqu0i~l}M z)aX_CUFl3YwYMeGF?cZ@97Rc#uj@OFufi|pSe6X&4<`JT>6wVEp4;uE>y=0rJWY;E z>GlDM;IozcO6W}iWxVGdIV^wpi(s;0gcY9VC>LoB6TvY_2L5CZ&a zu1vY=NRb1CGX*N}Rgh5iWRHBP{S`Te5}e;A{Dl+=R2MVXU)e+{=N6&av-_XCVY!Nn zhg3CE+ZD{jcWU4^w*VKYXxXgB))-iW^ty9^o%r-R2`g1wJbhOh8b=ZB$~P+0Ikg2g z53`}oq>8-HJzc7W&s~iNnrvD6f5tYRd;fLM$PAJmGSBsf;neW)%anex3@Wc5h$G%m zlzw4|+I+elYms8yf5?MITSHfhrXeAY07XoafVW&o8tssW8dscHPfa~FI>Sm- zO!Y80$GB`6GRGpb7$OY8WVU&J%!uWNv+O5=mG~ykih1eb_|l6~BsQ$*_}Dhh(kYu+ zbq~}I#8!IS!Hb&0dSFGObFxIP3b~2x{}S@q-{l8$wX|_<-)>O1J|I`2?zEX7=hG_$ zb5V{At4k=f8P*Uj7Dy+Xs zT=Fiyr5$V_BD?(NCjWLK5N|!0JY`(@3gGRVfr^OlM9vQCa{qiSuAEh6C)QSIB)`{y z=HBU^ChXy-@PAWC2;MgjWk|D$K#ZiPZKDx-Qaa>2l_5|wP61^ns`NmRVtYv4o>QQ5 z4XJ}Zj7cxRQ z7&zZ~JC^J`E+e@11WnQvhZen6jxplnBaS1D??gQq%Q$Us^4U$hwOlU%n5fvsOu@{^ zxV@!U{B=UREXziUG^Zr%IEqG{aT!2a7Wr~)8dILlnO1F!CtvNUCxe!-r#J*oI?`| z6MeABdw}}K?}!&CcEZ$mlWz$C>lM+Z&rlM}t1rIK-vQLrjvh5*XlL^ta)Ng)eMVzG z>|b$)Ki6FQ&3g>QbIhxEm1dW}7S(p%ktlpOvvtl#nUxZX!eaS_IZg8Tw5kwr@d*O1 zwke;R1acnj(tZ`tvSf24=6stN`KptAi3&paLQLy2?O2eQ3sQaaRFF^eqefhp@gCEV zE)mxBfI%WdZca6Z{HN!XTY0Qlmu5(z{nWEM8cb;n;Th5sj8ffc$&*mNJ4-ClHdto}A07(~qZg0;DkB5&TkN)gl9#!1?-z=EYZF^Sv=12r@U>iST1&`q~EjIe%+)z#DPc*{_B6=hH z1Tu20jm_xQe6PY5dqsqv#D!Vqb#`ldNgqYCfB|JuskbU{aR5%R&biC5Jox~t#LPA}RS0<$vLUfoLxT2sIGy>$+RD6CgP_;ex z1r6Zi1IpkWUY~pUX-5-({ckuzM4h#a!H3PUrhpTs#^TC>*VMAa4{jj2KIdB?W(H ztY)9pmqx8zY;Js`$gJsiMhl;n!dtdKXL9ttSdKGfyq?uBT5q>a>{p*l_23@2^5D4S z3V@L9Zd4Lo*q*keuAJd=^u#8GH-jZfVh1FXUq(cTou2WkznUJr7RpyU6t#>FLro4; z9Di*C{~q3pz+#&BUfv=#rw@b-yRLc&+!|tG0&2^W4}$`vZEIi5B{KHRZWA}nR#<8G zgdzdCJFtAhOg%^ra#Q*hS!wne-_!yMNV7Wkchuwh z?+nM%n)`44n5F>D1^N3gVhZhp)AHgf-R7U5dp!&=H0{Q1PJbGUy9VyEVVw~>(hT~| z^^8q3+&RbQjJ@F^2mUCKXKThRa3L%OC=~TBB+H5yB}2-`SIHc(mN}~9j`L3uNfsps zqTO(bcYtY_8c;2Yo9Km^ac7INvSZRjB?}yWd;&mp2|rtm=;9-xCfe-c0-jiYKvGgg zHjYW}D9{)G7}d|gdyq$k+O~3h(9MR1X|^qqMU_+!fov$bC{p9^M_j~(9ch7@+s2#J zM@?Guk>8h-h6s4T24x24@OTM4z>@KOIlyA*O`GuIzEbxo9bGe^hn7LVCv7;I~9 z^Zded#{%Ma&_#Ec&NIIH7o%z5_}n7noIQ7=wq$Q=`%vZ6!ZwS=v5zHvHWVSIwuxJ_ z2d3d(s;aS+Mt%O_W*!a;cNAInYhmflIOaqkJ8Ak^d!;x?zhojsyax_{T(G0z7CTm` z3;QCi;JUCyB<<6Qomi8*bWPy5^Wg0^!#=OQ48P%8Sh;Fao=S(+TyE+*^{Bd8%d)P# z;r5X54y^yK+!eNEMIFcKtKRZ;{q{Cj@XqR)x5T;Q!$%&{>N>xI08pRI$*c45duzI2 zR`(~>L-I3eXMGbP9HsFIdEoIPN|SkFv95h!?uEEwZvZNWrh-cOdK)qGCm+Y^Hb z#D2_9QC*4X&MvigS9=-0FC@S-!@xYffCR;eCt`?w{tfODo`6x zL0N3L-zHRYJ4$NCYxyrxb5_up`BNX9=N@yxT!UFYC-*>?Q@HMfQc-k@&4*} zzVG->KtjU#X(azss`AwSX1v6AfPA&pzhOz4L%KsGT9lrJz%tb?`L5 z!KYAuj*Slfs@~M+87gLZ`9hhEpOaJRUe<4;!7AQ2%jS7!XQw1nsDO<}-2!F0(C$FC z%YThQ`8w;xE1li4LyeH?n7pi%IBv&rN5Wu!y}|u*kU;rvCB^X39h+?}?X14)UEi0X zZrFF>Fgz}ezN7^6f1JT}q+#SdDkvm%NZKNnk1m_gtv5s%BD+g6f@b&<9+RA)7nHN% z0J%b)heGkl@X~Y7+Yd?L4Slt4UMGEve}h3Tle%?j>dZSbh&<}TCUO{|pwnf*aNMh% z+txINcTHxk0-w00uxLR$0{b(2gEDIh8V{k?uQ9aqK)1Bey1AuUXq`rNRc>Fq#YLB2 z|JD82N-tyMZkgLaj>93>0y8yo8(GSZFT9{;cOlOV(}-83o1kRWvAiQtyg-q$4Q4`# zeYS1X1};Y$1`F78=-NvhLWB8i#PRz#7>fPm(XeAqh#p<_8Z1~MYo+Gy&MVW0#k7Rf zO3e?3r=XgTz1qUah)ES1YebvRK2B`?#Hj^`u}OKX#IMelCx|i&`pZDcFm0VxH5~uk2Blocah7-h$W;< z&ZZvN4>=B*-Ej1Zd0#U1xwtK8>~$>8^-^^p2j7$PdoF!b_UpJGo``1!MX7iH>rLD@ zV>URCPbkcLx-S6ZDMQ*ut83(Ws?W5;I3L;{nL0Sy%zc}5o_utubEH`h5N^G6xn?dv z$3{q>Q`3;4N;Fu(u1ZEt4YvV`;S32SCS6eB^6VgTlCZ&QFdK1#n6k~6bJE0`3m<{bHY9CuAcxTuu)*nOnyn2!8nRWxeq?7t3PfImErDS-G-GdarkE?Y2Qa-!zi!$s)dA9z$3~1#E<{)EV+qLg>|WOgJ@AdV5`_$D0jAaOIA*I%PKBXc z33$-4UV!zE&)sl0^b=(aoIV7*ubd(23w4~7X2kp(s{G@r<}6KJAE65^>TdQYtAuUu zJ5?)4F16Nr1j<<S_Gv*R54qT@SIR(a7sD-3A5d4-HYYG z^%AFQ)DpPn-Sw zX)UEMGfgtA*!KmdAJHmGIdb_e37miZQ@os8tkAW`5WMVz39}>kZoSktF}K0!4!r?C z&WuN_lI&4>T))tk3CY=&88v?NzTo*%TD^Ha+k|IU4!cHUrEsRTjiO>Y4vgO%vkT%E z;x}>O#46qcE!bT)-@+TI<%8C_`>^RxDJhwY`o(Dl9OM$lpk1)_w+AWu`cL7K2 zz59GLMV+a`q$cYy|IKc(brTCRfgw#cR?7R6>nKwBe<F$gKEKb8SmoACE}UBY4J&8H&StG0rckF@Ktw`gi|-7h58Umv8d zncNjX>__HJy)6)Gi(Xcc zAzLyT!jT=(xAb|vrYgY5)FH7csiI2SPAJ@ciQ-(s86!8E`kf%YRmlrqC$zibE}EDI zJH-xMr|%h^!A<3!AL(Fq-5#-ph#Sv4Y@o9SI1DTYM`7Iv>8P{LbGFOU;dAYZE!QUZ zF6_*E$A(I(GVNPWU3txm+?S7q3{MZn zoMWubS;JnY@)~pgnQp=wG^oB{nnbl)0(4`}Yo>bCN2VnqcgIMy>Yv#V?s{i7ufCL1 zwQ-}T!oyU!Ds_DQ2D^CufQuPE9(tNZ23~Ted}(f@OxvD##>ukI^c+k!>P_^{3gkui zCz~4^ZQnLJ2EKu`H1gF>zaBH9Tsm}zTrl%bAX!)Twq~8EyUN1vVw9>7w=!?O0J&zG z)MlSe#e1yzNyA@rE)j=Ng@yt3Au?3o-CDU$Ox-~xm4lt-l>dfNQp_6^XA;`L61$=R zxq#sIvo24nhJWcD5v2$<>*!W&XA9S5g#q11qcCJ#VX%I-NPKK@LS3_hb-$*lbApQ^ z?~U3niiDi`eO%@?0C!l=rgW&;SAU|;vdG{0{ zpo9!V`_Mga^9J$KMnf1XRNiM+Z~$-YNQA9as$ldfnB9h3HR$tA1hg=BZ_IJRYQg7T z4E*PCxp5HwYn%f__}!;fVctZEsiBq4!8g1IHgLllM=$O8Df5Q{ zu`NtS7Ox}hZv~JM_W~>F@aDqF39urpMFl*n5A;OVfp# zJz+D=75c6BJ%wSd++S9Ru9^Tn=0%K~#C%h1Z}u?|=}$8I89t-F^SB7>Jz0ve;HlOn z)|7{^u<8f*J8MuP*WZ-d!NW-* zo&P|x`V4`sH^fRYoi57h#33K%2MJfh%fia8DC!Cx3Bu}4TM7JzHmF$`MPqVLA~%fx z6>x5iR@h9pLE)D##TdIk5B|*XAZS|^mEhHlg*It_bg+yli$$3N-@Ld8uGG~IOEJ{q z<-tt5yIE$|{nRyUDu|fwt#|gWWM-EJCCc5-p~N$$}-nL~FJ zL!E`Uq*D?rzoNnqm>iZ|(gyT&akG^aX2Sm7O7j>GrA)1~a%*jIp%z`o@HbQdzu@!)WJ@h`1 zv%S&IU_>D!fk%8td`=i-L70?&F@fYBXvF{S#ts498vmgPW#n&I0J3-MBU13V)8Hgn z!GJGF%V=v46^-;fj;9HQ%Ei#nd;%Q-ir+$&d)$0{?98(VrRW4})G#V5!G=1#c|sigJVu$OtBu9?pzID{D+t|iD%|FRn=Z7an_=U)m*HK90irCs-|D_uPDWoJ`TE`Hpi>9{;jux@~((@EdY6WZwJ#bT>j^VE72eh5rZerQE?2~ z@P4s!m)q8PIhoQsuuD%{i_VLbgS-yk{SRtbY^3o#vpl}IwgfEfny4nZI5?H@uqW#% z``3pTxaO=AO1;Qk=&V?KD31(XNG(VXcdw-&)HK*^nMDr~DFqK?x02IhU0b#LlP0K5 zy@k@l2b*}Xdqh(x>jbZ%=i7SIFh8;e%Cxw=R(d0E*|7FdTi-BGNxMw@T57t3sF8ocoyHHPz%*gMo!C$f)nu2yiPt&3u6Iq6J2~OP3mw=b4XEua z`+u=#+z|k{*lo-W`>I@%wz|@(=)SOvXo#nA`)q-*rHl@B3fa1$GBTQ=YrLy3@n#!G zrNyhZb5xw6eAq*h5<@8S7K_~Wcq+Bxz|*R;i*T(f~%omB02nfZ4^Pf=V4;@Rh$kVk#@+hDlmC#pYWCQ z-6EmmvVXeDo$lK)sz>Dc%vDSsoaZ(jG0SM8ARDA?QRBtaC{|TDh{Z`+zGHMEVf7zj za3X<6@{uIfU@utT$-wF*aLAkW_kaEF0b0l=`>QhY--qw4K-9`skddXJZvJd$9s=4S zp0B7466+I#p<;F@n=&7KuB^#(xaP5+dasK}S``chBU^^2qWrZgTP{3D&8zff(gsGX zw!ilEO|2b`Uj5eLhavq6*U9<#+0*@Y(#n~-DKn;q*#}5&;*+$_)Q2*ReNN@=F!>N6 z0G?r_(eWgmqJ8mr&zHAae}d?6k(PNE(M*xbzzHw!zeLg_NktB^PQ3HSaWH|HFddSW z@VetmVZ#B2jZO~wsF5q@s65fyO641*I&pSCa2a1o=B=G)08R0@JF$Qx-uSTixUoOJ zLYoC0nX(1bbz901Q?%?<^)Ft@%@w@Yd^^w?L;E(_fzKNoLn-mL_^rL=gjldiBNGd%xiAh{%Gy zeJIsNk6rg-NHF&MH1#=WEtLhV6GoTLfgx*`%elpHlRBjj40X-pe1AkTa8dT#saI=YYl=4 zni?X_17{A^3rl7jv=sz`q%3-x;T7NG{N&MnRKwa~2QwVV19Pq5h=9*gUU~U z)bw$1%Lsi832mN~0t7)_nlQD}xcw*TUeM~C1QNzCo@rrvGvAf)Sl!vFtpb)R)JaMy7o}fjyCp|Ss2>>_>61ZD z>s(}9SB*ALJxuhecW-6u_ktS>6N$$S4YxNMhke)D$N$c#ELhr)ILUE(pg|4IVUhqd zmw2w#Ji7J?C9}7}Ua&TFzVE8xGDRz{N4}jFR$FUo!*zw~+_HCj7J+AcWY|mSA>|lR zhZ?%8!Sm0Zr=>xUjiq{bS?dXv58D$7VL13+>pVp9=p zDqc4f^Zqq63n<$g@*LEisU)lgiI7^0fO>vP8EfmoSVb#2o9?x{tl{U4&(lT#?knm> z)GjZD)KyZF(4o^+vR3RDi%VsCEzcEOVXlFajR9*mCbRNq7fO z*?Z?G9_=hZ;sxi0B>n1(+NS<@FPF0v1&t$Xx4X8^<0(q^pX%7pVKVi#V;3}~PGo~W zdB3z{AU1_bZ-hz@!ED(VqX#dNV31zmht+NK(3z&QbiM-l^I2L3cjS!^s*+Lkzekt0 zrDyfPW>V!6yi$5zs^*Xqw?oz^r99DOF*t_}#J=bsVn~+GvZm!RASMFc+(P@){Hb8& zEuR7~wp6&A%E&Vj=XOyAtmP;#aOOHm5(FBLr{!Qjt?L7E5M~?dtXz(b_i-={rYBE%bmxtuHJzglE7&&n`+u zDy>d8j4}f;;=DL0*m%cJHBr1vpBnesR(g0I+ON6&QNEFWpCQ1!QpcNVbeO*N$iyN9 zSk7DdRY_Q}e7O+Lc8|q0K7X0G3X*w~L=m-l`Eq;JB5w`kj{JO7i#lwjP`~YG9BN4M zv56QbaR-UJ$Ka=mL(6B(S)hLTs4b6*Xyr`3#sDKG!fx6~mQq;-$%}UZF{qSXp1gL& zR*J=5s9@jshZ$swtN4-$G+BSZ(Y(qM{fAr<8Sp_ZVYsB)urjuU7G_Y;=6B>(Lo@6B z4~MBJeYMN=0Kb2*qXX|^+k|2kTdOGXxAFPLpCON>E|u6u)e+a2M{y3ggSZEUdrk`a zs`+9e3k{NPJ^UCjb6B|CQir&yG{*awP@Oyz%ptFf3zV9rbUQ}95s!3djeYV8SfuYJ zI5b`j&XMR;m)i%tqO_sD;WE!iSUFHT!$6YOAx8BnXLS?SMJ>S7!)k{*$c6Am>lH*r z9*i?*-HrqmiWhL2N#@&Ye8O2XnYbBOWxic%!TT%WUwT_xCe6wKkAzM2o+I~alg*~1 z_kV>iT>p!)bBN9aXt#B2+qP}n?AS@i<`>(xZKq?~cE`4D-9CeJ*FX3NXHbKh)Ua07 z+VAshi!_!5BwW#oNuo@e0{u|dzRWSw!r*6!1=Ub zUKg(G^Ld;gJ0NEpe}p3}AUQXDjH?O=0{c{mf}_UFi7L{Q%&pl%g|NI=*NTD0Vh||;rUaQ;oztq>s2E`?A`-MyKZ57{`!rh z*_P@h>&B0K$p>p7$LsX@>G}2rq>Kp5yJ*(gL<}%m+UmSCa)alNcI&Y*N0>TQ^&`bdaJ-UY;aI(g0r!)-d^ZiK_@b~e4#JycZ2rM=>z(7Y>H$4PfA12 z!at0y$0HO=s1tEjR`0Ax7^X++zHMKkpct5MBXr%7rw9Gf;^=0Ht})M+u6atAHm-I~ z=;*YwXL9cFy|emNusQjp4Ijyt`MJmmzlVLwBtP&vOpXWAI~bd6jcXOH#imF%GvyQ} zQGdq1btwJT0vY^=P(ta~0wi(1m)#yciLx=j7xWfeYl;aR5xe7`jd3^f)}cV7S+Rq% zVAyGn!-nf*IR#0kmt9l}y>R<3uu{PgA>(C=xa5@*?!552!e$R8&#}bVZ7J{l$FCRTz=EE=6BPEOzI559!3(~JR&h*^;oqs&v z^`CnNfcWV;Uom|atwn6q&HL)v!<;y~T*kbXnxf~o5N5->qky4Z1F+(hkvVZ))pYVJtI<8zCrhLdF{%K-85J@jBhJ?2 zyt&)sUVjg!T+|rQ9HH=XW%eA|Gv0HE(N^ER&O8&`Ao=Z(`tyh1dK%wI-6V)?aBGLQ zng)vCd)G1sIrKOLB&w8c!*w5a(PIZ?xO^9r%cg?Wka=7dnd-d+TsNpS3Jm!7)){K* z+$Ts@QL9hOHVGeuI2*!LNB{+4KTu~`fAr7~T5ldfH1f{&LvySNnRWeYT*=?xAnU7U z1NiKvT>G9XPf6X>TGsjz9!Hgo^(fX32tW3=lzmgX0;{-VB?~R@ms@WCVpho&6*t6Vm`vDn%*)wm+YE7U^9oaFHizVS0*#8a!=-#HUKc)e; zV~Q?STT|L!yn76OH;ia}lH*J$sJvbMhS9GhvBSlQx}=jb)R*Vxd%^x8e=dxf+2I{A7pX)q3cd{2bdJ5%d{)tp{HB) zoZ&|LY5_966{1Hkz0!o;OEHEDPerqfcOdrAwPCICyOe2tVzNC_{-l)I{lW|dO#U5^-UOm#Eewn#^8lC1n8v5Sq5%&gF#C! zzzml7hE@oI^n0SB%zHcQliu@lzd?(Jiyp2rpN zE=ZdGF2hKfLrTfn7+faq#V7BBCoje>Gsq6Uebb%YYugeeyH@SgO+>t?I$fG&Gv_$N#yu3zZ)wJ!vm5OE*-Qq}FP{wv{0(elT4Af=9qp@(`R0$t8WqbDkidQ~%XN}_bJ z1Itukq6LNJ<&QsX#7xF$@iUvr+sg$bz|r||=qX3lkbAqY(;viR^~9j~<4U|J10IQc zwPEv8c`*afDMJVTRfWp8q$8DwBj+fMkmL(LKSeRw?$?gp=1s2^r9f|ur;IljT|$dI zc#r3H0l8DPg|AK8z%-qeNA%NF?K?T9JnrYDkSB_c_qe&(5_sK4PD6aBFu>}8{Tsim1qobgQVtZwQD45@GuD@yi|A}hKTDhyy;jv&2ebDS&3#2 z`(I3ofUsSjIwEjSuO*|9LfrskFL=;nsHSQx6%NmaEh_U~0ns^J*hplyJ`|~Yr1{Tb zAe?ueDb%w&)j@kdANJ=OT0XxFu_Z4o5`*ZxB`BdC8*imE6ek;DZi$txHJHxEaQlEO z3J1EF&e|sh*=FU4r?Jp7884n37^wj9Own9BCIDS&E+(4V0ANSRnyW}y@-&A$(mWr| zU`E9UWnDk^$B%(&vE5T>!+OX@+n#>s?aTpVE*^M!8{GEz3>wBr9v$`FHyQbwlLHyD z27f-Gt<%W)R#b?Z#aS6SE+~Af(bZ!*c3rRO;4&l#qW!$aj(HFo0dvkB8f&~&$yWjH zMq5PQ3b_Hf_7EuJiW8kG0QEW^=B`^Y%*ZM^9~7 ziu8)V=Q_f-P+Wdu*KCXaMo1g94#pVjg|xAjAU#i1H1Q;PSymc!YHAcmsuJfIzvh zV1(a4i@-XQK~c_V`bSTWj)q~K{)vG|FHO1Hfc2G%L4^qS5ro}EY=C>!AuNJ)iTu%TYnvdxaR?ygV;8_et+?N7v-r{e9VA;GTLm+{ZigRg$It00MlF#Jn3zy-X9~C0 z0_0R6AU*s5!XCl23$P%sekDI0H>LENtvYrae%-fdLnThc@Qww-shX(W9RV*W6rw3c z1|k{~5dSt{qHROS9s_tdA6??!pgx1^4g62#S_cjPtt%WPzjFM;=QyAvg!^Eicqqhh z!E-^-pC%}OA%A+bXs}hG%L1lAKaE~j(aY}!`x`>wCkV~kodraw-JhqAm8@m@L7P1$ z^E|&nKLh5x(#)R3TE3OMlppk8O8#D;N_-$9Km@>!$i6^IQkqaiG&DfBKjy@GNRP8b zKMBx3k*ow>sw{K$kye9pt@Cg0IiOpMnoxfQg^D zlh+ll!Iew#SKOmoij`6TdYbS{NsT~AkI@lKd_QOCQ_T|urj8|5|HU$I> zKlo~rfp$kkh>d{n6=9Jrwft=)9X%?P%Uev7?|a;@g-lj7i20CCQ9mC&AU+}A@1Mvm z7`RK94{*oclA{}>Lv_I)n+iD4L0dm)c_B?OAQ2)V0kGB;Z)<2!r9K_;b&)PVRP4S{ z*dS7SRiM_3H_*#ns6Yb%%@E>lqp*J;l6g433b2*^53Ss79EKqx*uJ`TucgDrkNrpM zA7UhXsB}X&9E4Oj=W1B%4`oun*(h_LU+<(B9plaj1WR;Uxe%&C6&D&T-1QEnk;=0!T=60K2Gm$9dT!yb(fPL;b*ShF;>X%1CtHVx$Z?=le>x1fBCYi zskP1U$PoDnDBlWRa>kPu;|{qA?XU>>k4SEJVle4MG8m!BvK8NEG_YXW0Q3C$tYsym ze8hf&-$*5YGM>YaQ7fA)3Jepe`)8gc)c)=-F=3q@CX7?a)rc0u0{PJ z^M5FdM(X7ZJ;1M&fxz^Bm{@BEs1%f&Rt0zwvhEj@WDrLVHwLbnBF%*s(b26J;`~XM z96~3tnAbr2R$){4!x*K#Ul8V&n=*j&O$vo=w0GKkB96%WCW97wO3LQ?PYoepL+Okl z9&KEn$IsWT-$Nctn-TMB^y9$OGH~jYmnd7YAl6fEZ|T^OYZT{eYbe7x zsng)U43i*Eq0tr`>y;EQnpzf_rQM9y)JkCZ@{pjk8oN5hJ;5q2e!=x4xf_?VGKCF{ zZcv}p+O6-YUJWNFp|1*z3#i?>E-xxTZs%ih?TESwP7&g6UZ#XAR+)&6)6TzY#o`F) ze%G z&m8SVbOWEBqGIWs;v#0SIT7D>C{n@h4^@)+iVam~+0BrWQa#bZ4Xm)_-04VJqj z$XCjQpO`D$;C`9#X4Mea*d;lQxc*mvhQPmeMt)UTKedAgBK{v>kgW2(Z8}DSn%0Bx zbVCU0>+!TN(+nkpj85Duz|d^dC@a5*fZqe1=6)L+8=caAvAx>Lm2ek@*VceXB!FEXPL`K7%4S4K~H6~sg$!bumM9ub!8IQ61(klZB@3& z5~F(|+jPGj5b-{^=+?2lf#>6({WpCSZy9|241HdTCVThGRBfhP zKfB-t;{-n0M7dd4yd5XA8&l_l(hrW>I2qEbX{~LNtt2k7h@fVc1!~^KuUE3<=ZC60 zwXT{>>vO!!S`C0?Dqywau<+FX>(^vp3{~Pk)b_17pRx>7(QKzO@ED%=h}m{7%vj=e zS{7Z&I>ckBW%1&Q3nWfqvGVV42B5-s!n;CAb{esR)cRaM8My)6yn-_hvJ`KP#&(@? zQ{v>}+Z@y@Un|PoEG7S93%hOr z!odqXum{X0wvewgt><9WE44VVOn7nArsO>M>Z}zqh+s!{5_KrIooU1F=13l~u4qm5 z@=jhn6;fPFc!M*Ngl@2xs%3bWRJ+T?*>WdOu|sZJe#HPIL6h4?)+4w7vA18L>RbSM zhj@EoN&gbH?%;Kgyfr0z@l@PO<#DrN52?wH(!HA)d|9|Q;#fI8?MxE`wFK3R$6bin zTv%k-24BRbOu^@89ilAaRW{n0BQ<@XbLGNu*V+E@T5&gNhdjt~Pd7<01X5d4@=(00 zo|?OO?&v7y(gj)zB(}YVc!($~1);bF!@Te3cxJXtSmu3(Uu;331Yn#_Scx69DZTTW zf2_kq7%s~VG3+;^&K*_FL#dBfF0_PzcVg-43y~MQ?3Rq>v4@`rwvMmy>f(HmD*3V$ zXm+b7B1NF?{7s5{i0gi&)Ry;`GOEAVSas8~rC6+LMt@XX(8QNf&8Hk1MKAm6u%_8Dnrx7a33l>Ms6^+XI&Fgqm3mgm1^O z^mz6-d+2zRQE#Td^o^Vh?f7{U_qyB^3d+lZa~ihbA^ZhqjRwEw{W!@Y@_KpSEufmd zLjA5u2GjL#A}-50uk8~q_}9g=yj+GyB*ffw#`cLrXFaf2CYHH2B6OQ!ts+kM{GRuB znW(H10*7lW*)zjNV~ItlQ~OagW-co&I`hi0#&T@%I*hA&fp~IQmw<@ z8EeX#qbv#bo7(VL)N9)e@+;neWFFbX7RVDIBP(*oRn+6q*w{wFJXN)rVZ9T{O5;a8 z@~Ny%y<}pGLpeOg&9h3d%01NFRU4nX|AjGmPH>%k1M7)|M~2jF{u8~G?}y_D_{QJj zB?1X?>kAgt-Q}e+jdcT2+!bGkqgrj=Vo8iiWIN- z7h_4lEurBrWp%y;phyx)o>vOcW3${InM%z`xfH%@zjEDGl_>slbi7dKdd#!5?r3LL z>&cT|DnFVgQ{>G;t}3p;2D?`Mr{VJQVU28az_VnCK><*F*SFy~l`nQYtaB zqf?|DYm**bg{GKUSi5{+Aa6+Ya&kOru28|Af$Q92NlG(C+`3qXI*Ef)#2TAa!m`wm z%;ve!U7JC$+A4c$0iFlnS~lw!)wlM;)US2=Qq(w&{8Qosqx|PLLUPpBs9l8dPQNa6 z4Aj*eM;mud$WOe7eNoC-5z%DoM@8rLi3%^mhfn($5}X=7{7%xZ!2ITzS~KsU&(Sby zO4RYq=yz2&=}!5T3I0@UHBJwuM;xrRcV?iQV0QJ!x45uC!UR)NLu7?npd$O*7tcvA`qfDzMidC^NNJM!m_M$X>$1=!0^ zvA65%Dbtm#c_KBqRIigf3cG^EU4rsQ*!AN!RLCQ>2i<90-?n33KI%f z^JzBCL)aYx1J_P1=YN55c&;f8e=`iP(!qqL*i$#Tn`{NdqvT8gKjFLJ)%yjj9)#rS z5={isXq(&uGnn zdFOMsfLUU$mi%L_O&qCmX}#Z=WhPL6C~JU1P$PV2WX5VZ&D!knI)4Gqwt#UsFf@U7 zj#|YpTRoPN7`*c@!va21o;qQ)yZjtEysnF1jroF#WI6WV*5)$ygdwro=wiO@Oeog7 z?#Us)1|0oz@`aQCl*<<1&GO| zsd7dEqvHxTus2}U^T&@va4ObJi>Cd0s=ib zG0xF5#wT*W({o16^rvYrH@P})juj=15v;}SLCJ$%+tQ=w&tx3*7+W0R@68DEyz&ip zx<2Cnqbw|=mC@}S&qepNK9V)~C-Y-yErqg^Cmff`g%?w4EAKLLBxN=n<~M*aySub` zL-hg6q&3P}%(1pUtN}-@ENTKEE%OVS_9BvJ+A-I~1V%@~&u(T6z3fF1;$x07juRZU zwoEx>6nEF}hni7)qWxbjJ9qM?inv;{8%J-v$wuZGGho`_H>I<5OsUgqJ;eze^5SIhR5DkncbQ5e zp-IzXP4aJPZ^dv{_tcSOr`26oxfj>JkE_OER_v0BN4coonR})z_XHmp4v>X6RJLUN z+Qo-0%2mo;>7Ps+qTUxE)@n=Wm~E(7iVZ!-CR(yAoM#&%)Tyd)xN2< zQK~8%KIkdtiRC|dEb7kMdlbbG?l2!_QB?1`XB1I+6bK>n1_j;{6Pe&2h#6w`BbiGh zUqZOv@m%yNt@o_fdhm&Dx1|ueaw*nAY#Oeo2Hq$gX7;xQ}+UWfR z!%WPZdh6UeLmHWpR&2C+&luU>XB`&Haaa3vH+6$d(rYY^K%%1Kx;Vu?^SVT0b@a26 z^}cYGQJa!@=byKPc|a9!RKz(-M3H!Dif&tg$IzHXT+pOVK`j%KT)KWg1A~BFb!C%ARU8zBC|0Un_&i91Xupo9$v>>P8o$)`AOQ!HEDkU3=~ICy>Q}kh z_AHO#lW~Z_8lkr%ZO^l%Z26s^i2C`dg&C(`&>+2$p6HZw)Aq-U1GbJh7A5{yo}i6D zX`%Z5et(ys234SQ)dCqg6Yl{UuSLPcSSjn`f=v6;BK}I9hWaA3zD9b!sv4IEJibyy zTRRaYH`>F%@X2(Cmj&ujw$@-}zzY@)-Zt%a%_+c4{O9xn2O>r( zmv*6^vGk?Fc;W$&K;2(Y3T97xqfp5r!cnyHmJST3*-y7ab%%MBF7!(~8ku7!+4?Px zrjwP4!Xf5&6+Q4Ihgk^W27qLCNfcHubV1R59J0FmtjBR$DVPt{fi%^52y78kJYcL9!N? z7qnb-OvJkLa+=H2kefwNtVSJ^DMcDcftadD69>a9|L+inxv0LUG*SG6u8zO16rZFA zHXu8subibPa}xZFUD+NrMD;U_BO)L#r?V$#AL*4-3yVIir5Ry$B5D{pkCOUq@-el} z8a+*J+Ga66wbw?V9l=`q7k;si`OPIdeG~)Kup^9@Pa(K5a&e_B&OMiiH@Fh-M z+EeZ@4F{KguiBLg39O}~e3VCsGZK=kr1XKs4{S?yU1Vhy-GNql^7uApRm;s~Q>W@5 za|t(+xy>Jhlz5;)LICsZ|^Z_mzF-6X}|BYON@XHYPo|CX=vpu5L2lrJChzp(jUI@xrwm zh&;U_!bI?4-6PtfD8XFob?K}+!%`%4^HVifgA)b-Ewog8B~Bo2Id?6d)Jo&abDowF zzUFpcc`yhe`V4^n;#0ZYzEV?;eZspXw7rA`7l_&Lqi3;@6X@`XRP0_OI=2s`Gw5ZA zz;xvn$a!K_iPUmO*5yM6f_GpP+_s+z#{ku;(T40wb?|gZM_)TnS{ez7Zh|X-unaOx zs(wvei}c-B#kES!jLbDy=0wdVc^38P=5;gKRc}I+YBJ)ETx9UI$+R(G=iW44oBPYW z(aV4rG~RG>4Rg^vV_Y;p$H7xL;hp^Erg=C5dh9&5dx(sQvxJ58=G9lSdDAq_NP=tZ zTo|vN&w64c!lrr)Kigx&nLZPd{aZ`pP+TWJo&8`Mk~o|I$NIUG^TfmYFRMunsLx14 z7ldc0WSD$(0-8k$E;T#NZkoz7@#I!%lvvLMPRWi{T603#y_=9Vk??CxK34S*O7 z1{mtR$KT2I2)`xP`_@@FafWc1W=nl*{9?v=U~W^cpp;*@Awf%0@{|P6oxL717f-Pd zr;?ufPP4a}huv4~faeK!484#w1bsR(oX}4Nqq+=Z+cgM5+=H0QgHum^Yf^Jg8jA_k;@!y(u6&>Kb!t zlny^oMwa9)uGX#WPr+eSb5Oe#GSVno*6z35^6+SF80A=#XyfefM-StKBMfLRqxi67 z^5_zbBc)fZwe@h~6%uq|O4y-*%wx{S>ZY?8)b-2HUGQW0UN}c+EE6b1o$r&eqf)2| z3qzr$RU2PZcyz(D8H-_j7Dqiu^!V$)3G!x)KV&cxnvZ`9X{vsAn64Ehx|eMB&i%5+z=B0!iApOHFBcX^;O72l<62tAV((Y>!J!GD_2j8 z*`9kvr0mh$IeYA<5_8V{%kBV>^EIq>RO!&bC?}V=&>u^q{?3NFOmmeE6-d+vYsT}* zZRvKu80vUniH^4g_!0JZx{4VOaPxBlw7+6I_LU05wAc)ZncCdE&Scr_rQ~Jf=dlmV)Gpv?NIaq}}bSG=>#+W49&I)E4O?~`Mqu~@E2lGv81 z?&5&5E=nVJ&aPN$;GD99shzIuoNq{RW&qlM3+mYZTTsW&{vY;NE)J&uU(fxYK^->- z+y6bNYXw(A)5qjMSda=@XiHw$B3cM{flg%Tgryr`_#+$S0^pPaz=EhO5KBpwxo{>9 zg!Ovwy!_<7^{i!VePlmmJI}Via0ART&e)r{OZpvH!$I&u4-b#_kwM2QD{1cTf!*C- z+y5Z@iGG~ENyJ@rlmm2( zgFWEeyW8+@`QVKx;6wjdB_^R1*jpHcAR>nh)4djkXC+wK!!H3pMqoN3b)dTPa;8zj zJs?ZmaDFtfVBlW?QnKOGU$I2_>_WH*fgqKq4&lri- zctDg?3n=;E!a|q$#D;x0slZl3xMK!2j(aSiE_4Oqg81tW&c=R!mknxeu;So;D) zz!0IUpx1!^Ddh%QvL*f_I8Nh-!Ug1qkKmsc^^)h}C;p=V7V?D*2Ntl)(IS)%i)IGQ z6s9c-bPhwKD`idw2?PcDLjh8QeEZ>^Pl{v(&(=x#smz0vPgDa9v<-OG@PmryU&4sk z6%nfOdl~!Uo&I8;9PDdwz{Q1vi|G5(%fnkD0O=3}U|#n!yx_xKgE@cTW{4UYKJ3`~ zgxkx!H9rN5n(7{UH7itOd~xA z-hF*-h~@x?^1a+2fZu+8e>Sr8XkcL>G`hW|eUG9$_s**ar`{Pmy zGWsVe#zXc^4pM?4^%%H+0tn!V9_x7Azhl$_GX`%+zpG!q^S^7??ii8(e6kZ)`~N7E zLiDCFfc4$iEyTEkc?i82bp6bF{*WI2C?5AVej22F_o7pCY3ls!nZG6dPb0T&{$B<^ z=IfFR|1LsMwbw7EzVEb3$Tw#*6;IFTtP0734&$t`k3895VAR3%?i8 z)ZTMjFQfJ^tUmZp+jrCmkT&5TqW+yz-}mpQhi&U5+{vHtDvZ_V%ci?4*wm>+k4 zA|!JtD1avj0?JEc<+F81d2*@SzAkQJAD2|e+{ppn1be@P+jv~&clf-5VwN(4diwRD z8dJTMT2g)Im}WYkqfU*-{k-@t2J~RGxD*yh<{Fo z0*kp&l;*uIARQM=R@4r`s_XPfS5F!Ba-z?PUg?^Wng%BMBj%XXi!D?7-$7b+Gnu>8 z!;Mp>SyDSGOhvn5&1ev&5vj79SYrNV=?O0&(04h?OQ)tqrgw|DN^VZ6{&l6^&?08C8TTI4hIwjybBm37%NF3&6HiudR z-6)K2;2S>TD6GA#VA{cL0p!Q%Oy&ZU zJ1{!)>Fv5cSUMfY5!aqZv!^urEQB+tS7yV;NC8AV;un%Krfi?@ZQHn^#^UE$kQjkj z91909QtHHkW{#j;0ipF}{DZUvY}Pjv$F!5qt@Meu`0-Tak*z_l7^Ns=PSN(Wn;{b` zhm0PbWzNaPOY@Zl)iQ6>3nGpEHwM)n@0&9!;n5U}Q9 zCT0+WPWekjdNWAJMzfB{0AEDUgBO*%{aNP67rZ0-}yE)vbQ#MYU$$vcB@PObeYd23Xt#E#I|rVa^x$c*txYQ zM2K*VeKIGH9Qfs`S-rX?Ws;;;{2)$5b`G<{@&ljFWGRaWlrThP*OM}FUjZ_Y0sX~e zE1a3l*+JX2UWr9te)AG`p%Ek*G(^Xfnq?Kt1yhe0_SzJ-C#U2acV5ZZmVLVsnlN@W zpX|$y)=iqcAeup)->x-2RUbI%gCs`@sK9Q`A%J}<2sM032yZ6r06c5^19?EAt3Y00cVw(&F zjJS>bCth&*^KxU&8jIG-U=(x*s6%nqN%mk5)+wtH=^3wrjq)BQFsT-`& zV$3PFU6791Xc|-i*B%p2jM6!Ri5psDl^4~N`dMl-yn|c1tGw*p8D9QBdBFV047Co7 zl``jHmx5;(6#Yn4DS9OV?NzK`f=zGFo{fUd+6eX%?`diyYCjkp_}|JiT2|lL7K^{C zUgWkri)QT+q$gJA|1evVT_Nw~&>cTGJ1<`szWUS1GT}DHKiULYI^bq!G*1aRCiA&c z7prizIFFw1A)&{PszC?RESq`#&HD1pHU%dD6(*a&ie6nAy8ZaYA2H~|1u;vMG8CKD z@+x6;7yE(Vq8+s>3qPoqE6l$^MkY_4x{~=fd=bAhUh7MkS6<^N;l$8jB;SbIoX|Pv z&Utb#@YsIhG3BjhcVO`J`5AQQKf_ssvC_`6S2#i^bD64;`@{3vEqF;Eo#hE!N5 z8Ho9xLUT9ONOaj87~ya%+Im@M+CecB*C>Nh<6y&Rdb&BR6~~TYc}X|&sW}c^etg`e zum|HJhl`iB!z(7iMXOm(S_IGC(fM2zKmsoG)X|<&L77^)WpkUmxaLyR^sL7&N?lLa z1MdMtGYbv@*pnB8`%LvYV3;)V@$#Lf`d8!BDPopk%}pHj$o&P#Q*EJpSjBP8KNnV4 zJ#d~Uvb)67ffr7hIA)|^JZVW1vN@9m54ngwxLyTYA#+oM}E&mG$= z*X-}pR<$)vU{!3I`{GpwafJ2cCpkpx^7(bu#y4?1Uj18Jd?R}D$b1}`58sPT#CJf_ zo;5wXgX*4y^1ZW4!P?*o<<*Pt_NtYrNoOR{bIUTd2d@y!{ODD=qA79DE#yHWQt@88 zqBUF9kmd3qDWLM=lywdldGTakW_I-$lg`-9Z-x?w4U7Gi-ACN+X`aeu5%5$;W>}b{ zI->yFjY9p1SHWH4f)m$T@cS{>C&YV?=bDA>5;pqf5sBoASACg!I=JJp5xr{JXn@5DnAIq(a@%`y&X_un+HfsL!pHBGPKqDc!+2dz-CU>2B+Wx#f-upQ zFb_2+f}fYyUi$3#L#=PFl4MG55w?miT>T0qpyeTLmM97&F$WN zc?uk5CNSxO&u5rlg)u~CRq-9Tm^htkOS|1{*_z#pJJL#~T#_qjYm#tRz91nmLKFwxSV=2v)|;{gL?YI2ub#3}urYx8#L-!SSX=z&$JV`yCBe%4>0%K9^i+ z1+!nx;%Oa|N!oJVJMcU8=YjN^IES|~bPrR$9=meT>>Az9o8NLGm!^MAq&GGih8dV0 zJn3J{m}>D3a*>jeOww|=sP8W58gM+9$UBWh1dkVmOi3g|KcY zbX)_T(b3%%nvfNcY{OogxiQgVpq-QdJM9GPCp6EeZ`hW>%$TbN3Rv-30^r}xrwy*{ zIxxucyRwT+)5g+{n4%*Kep2^Bv}=ggir6@0|NeZvsH#f3@Q*MJAH}EvNT9do1VP@* z+U)-4-)vaHrG}STDMh3b#*;!rg&c*GQLTciuLkm4Wy7U&i`UAjMa6dLiP7Y3K|P;0 zEUp6>aRM?c|HtGp9Ez+^V`X~NPzXA&b)N}og5EBN3-mfdqFEIs_`5&{EG=kNs%a2cp0GX*l z>nfY#=j@-orL;1%DWg9!^V=PvVj>>7NrOX=VVts9Q_(4`HZ%|tX=Z_l5{I>=E0x*K zhC8JkFSapOKJJo8f7)WzW%b{&B+n7sVRZuAb>~f$ZLI6LB_;U1ux3oUES)8;z-oEh z6%YMT!aW@b@xb2pUP9NstcAZ0Xy);GT6s;;n&0#I?C>+Die9B^8+EL1T&>xi9R5CC zO&4#}_RE1yl+v0Fq1xA*JEvE?otQ3?tG6s&gJJHiEvc||M2i*L^LR$;|Ea(i;TJ&n zx@8sZ*-n&4^h^Q9&eK;JV?FLgulHQ&d|_3yA1GUKzJN_vR~Y==jNvDbnMU#lc|=Ar>BFG@W*gh9 z*Jigv{sNoU_~50&l7leQN49&hg-A399j)Zg=q0x(Piz&N$MXg1pRBYeaJz!8jq|@t zAf<26QS3RgR6A$1?L#J6vqh|YAFRe0eBFORvTQ4#D;J=`%3D#~DUODlj*phV=8N|l zwHYmsiv!`h&!b}md3wzr;Hf?%cVc@+k&ps!cp2S_sSah!xZ7MVmN;rhIVCJ^@meAF zQ((L?6nFm^cU&DaEvA#CrF~RdH1mtIppeLnmw^_b+xl zaKmcizkK&Lt!O-_596g`KHcG!M9h7UnvA)TN7G{a$?}2M>$=pP^c4oZZ9GuFBjH1% z>gj82@$Ao5_&WORwY3_O)#sY=afwe|q)`|z);X2W+ZmpRuGNjq4*#{)E++jkaK-?r zyMB8x9m$ZNzv`KE<47UCX_*CZlEQxp*^=;zURI_xmz6lYc>?t>2jq_n_z;|TRbN|x zDpXKQ@QT7cp)CY#jN8a-l0S_GE^`WV+z6L_pJ}J!X;^mjzfuGdzf~_XoG=hAH>&Zp zA543q&siA-wdP(GN-`%%SGgP60a; zU91GrcQ;d@3^Uq}#Ty!-uUMUlbKA~yC#83?cpqoO!=VKryQAD3Pk+Edr)he9q>kbl z8|gPVh_w5qqXbJVzHr(FD)t`Zt3Y3C5M@^)S}L@zoes(HkGAr-UH$~ z#XxgWFd4xyrC_L+{kY7#Hx~YMWKOSg_btJsS{gnj7-bh`V1J7MvT5M?!udw!igpX;tGsNHmDS2BBHK8n4!j_$(i7@A8* zLVJ@-!9L-bZK5wSz2M#LQ-Gi(ec76EOV(M#U0Nu0ocLF@@HStk(83!h$`*MpT20Vi zTTQX>jA5j$I$T+#gh8mDsJ^get2>cVPo$sWH{1Z@a|otObVKM`zPWC+xr$p`$)Vbx z0PVJqW~=MF@l@Vta&{zP_18JGNB+WYIg2<9@hz47?i{+o_u>?QOna$aLE4XDdplOI ztve!TS*cWm9ti22hq-o~GmAr5LVLq658B@7hC;A1AlUb*J0Zy`jgjdqSeby(E5u7_ zHR>M#{;Ng@y(CIl?3y6roNn!Nf)O%yXDzC@rmiCQ*=mwm26QhijIfsA#(x?}{JTmpOX|9`6O(k8vO_|d@n=q)XwNW>7a*%@m5=_O!_k>MChlixQ z`?#@gf1tG2^5#0RGJ;9l>jAL_OTRm;%tW%r+zC=VBn@`tV*4uex~ER_zN00@E0QViyQWs$g-&GskbCg5z)RCl+B!>yqP^%z zA-YsGon8^Ya$5GL;8IQySLOn2bmbcVOZW}!R`ZDQ9YQ(p*xoZ)3y}0z zD|KhFgDJY^NM5-FzXL$3z3zM;$^9tMQhh#bgcawL*N9iV6a}hrFq_fOa zLGuWoGtX(9=J5=CyfnQ_Ox}OSE)V>4xXPeUD+xe&rOI&_+W|F8$yJ@QoCLXwW%s(Q zyJ)U-nrZ1-mvO9OO8w5n-A&HmC)ra=Y@dO4ElYh zV9&YjplZ34#Vo~RFAH(d1JIlDm`U4v@7AWo-3!UhQbz9bzex}N%ZUM~sO%%@9;6+P zeb?u%V3O(P4aAFpk&ET#=cpuq^~`!L_BK6?Cj!0-@IgQS2(Du}extUZ2`0VkxnSwr z>(l~b)+3C$P8jXPPaBdMNnw<_3eK3p-tPdMRUO>)WEf35ehq203`nLDKO>*E`OdZxZ;ts(vIJK4@@7S;DwO9xq z^UMjS-|Lk6RMD<9Hx63eb{k9ySAFr8KXz1L_G8=z~B5ka1-9reAK{n|a@) z3EJ=wvET}*;0+6mh03ZI4vSd_lX47OSa&YYjn~vSHNhwPJ67IJ?ZLqeRS2|Hw-|`d}D=} z(*F{7ob?N+j(|Ba4_Rh8_d2_+ICy$*QQ=mbZ!ieR7-R z=`cJZPZVJCa1^I=^$jk=nVF=euu5j_E-UKxX(iI?Wj!!;w*f`iY`Wn6eCi(hN-@f~ ze*=FElWVnGoH8+Qz4$6YR`n2#g0KoO^4#}dYdql(EvKfy=n_;E-|#`YL#7o7Kz#67 zn#YhuQ2}P89@>=XvHom{e~L^n{+Xk)L0|USa|)lXYpJ8V@Bse(O+mOxkp73dF0(&@ zBSBU{vj4oB62{F{yB;-Nc(7$mI}=37IU=+1DfaWe+2s*w*s$Q&qi41Lz#Jb6jZ=sg zjg41^o_mU6kQ*zSW(PTmerLM5`JXAso(IbBSD?TveJ>X$@@-Uw=H8Ur^2h~&rm!OI zZ*8_Qf%~Aux10zQII)wgg2+~P#UG#`sis&D7!c3O_DEnAh|uf#1zEpZv67fBc~RY| z(P!KTTXzdpg|O5{qBjk$pb?o2d`r}n2pObayi>Mh@PgaZr)vVP@+h*eU6M;VtinY`91vp1teGF%d5 zWOc4J(%>UI&Ey!30cYC^~ae<3(FlT|5LatEvULN*NRjXC15fDTaZBHgKA*slp83o z&RlH1U}p4>7j#It!kNvxGSrc=D^rA;Il7}36gFT^Rr``LF4@Xv#pgCou45s_B}iA# z-u>Wl1*?hF0pBw61%<}U%;#JdX4XcXTuy}#OlA=1=!}(RptZ+3ZT?|Ez}Ir|;sDIx z2*~tN`I8)tHNYL}4{yju-BRRR6k3bSeEP9#m$8L@(G)5&MA+br8YWFVz6k9b&iA&5 zp8|KXclrNg>>R=~0opDd+qP}9W81cE8=Z7)+qUhFZQFL(2~L$fFEO4b^+7)8AUzU|GT~abd7Um|Bqd!fgoLLMaaiW)+W~)aTJ%m}* z9M-V~8ob1zSYIXny+!f#HlAZ40ws)_!SI|SZixBB61{EAT{D<$y*mY2nBW|3-(~5;FYn-~j1{0dgR^Hq$FJnyGFAtsUJqHJ2 zyIb*FSlu^Vu&DcwZZy|j=LbD;nD1OjsBzC!F>GLpGQ>yz-1=wS)>v535neZm- zP1jzd0CS(@Xs2{h#}d(Lo^MI5+MxN%io|sb2|M^cJI7|h_>rO)a<$j&;J>7W1bGJc zS2((xN6@BUu1@=)DdWXC!_*bi-lAXrp?kGvdnQx2A1C?Q5Yy-p01FB7@U}|M>tR|y zR<&Zqjjin-04jgZXlFd*0;9M#X!n3H;RlM1eI8_Z+?I2%{Yo<1*5HN>3BpcGif`49 z>%N#nxiqJ&uHgqC>_BHR`#V*?ryNt>AD51)=WMqExywZye`Yb{DX+<7)NUwr&Urj$ z$>mYe>Z}dk=gkSL-jlcRvRLl?=r89H=`k@tnJN^q>f`DSVpcC;TuPa%xIoG}TewX_ zzGwx+2Y!@v_wb;6;I~z&scKz354ASs?MWR$dA;!~Ml*91TC^j(u0X!z>#yB8-;-}P zy2XQ{JrsIbh9s-ly5v>Y{x|nNGZ!6jhbGVY!x7EK0#-XQ0a*8D3r0`Kan@+9cc zv&(Qqxz%bVb7-D1KFXc+=)tPCNM7rZ z$rq~(>eeqax?QX#=zvqb)AKAeE~zy2=jtq?^ZR%J@VCNX+>)pt#1Uf(jd^xLbNk7_ zpQ(KBp19D;gI;0AYuGeh8kdlWln;`H5;&0uP}e3^WN;4 zozfn}^|z>fOT!mHOon9sD8mCm46Yt`7&M0WQQiR#IeCcc;ERD%fYmZqQNv|#JuZ6lj@<&TX9(z6xjZ- zubA>3JP47R_l!cY>Iw*e0uxYsmQ@Kd02!+z5y8~f*W+Wk?2d`g-_v&jrfp(Uwj^pW zHpnb-k2uyA-A_KPGqZx-g~<1+g-Bqu$Xi5VQ{rC@_m8LR)z?&6pf}BaVERkLmSi|Q z#Eh24ZF@o5j1?vv?*)3;rA4g9#0xK-#}skaV4U@y=}9Wm+0l4thSykq&#V)LRs!=_ zBNdRV6cau=*s7G5-3s%zG}k#+3Hx+DhTr7~Ln$X!Uk!ipSR`cG48vi2u>BXDc6RHs zjX-3jFPHvFNhgaV^Ug3>JFXI~5EkNq{nq|mE){qWt58TElYP)YD{-;Tz!_h7nR=y+ zu6FKHRvkT{T;Zy(%rB;CV!pF|OqH6%6-`K`PlP*Kyj_Jm%=z%@rNDfwYXz$O>PG9f1?@L;guCDiaDlwTs$JmpJ7J3{l z#j)TZZ9Oht@7(;Z1ABJ8n*3jy_r=jWs~}(gUp&dorR2F4@xIs?AIs&}8QoEK}I=asV;8=v$>a5U-gl9h$^1p26{uK9_PN&xJaIKz_fB3R*25&8O z@;o&)N}UUOt?d81sf_BNe>jPagD5RvO{%zeqO^5vP>0zZZ%73Z0=DJ~*vsQ_9Hg5qmGC7s5yCK(}gJI2E8i<~4GM zMzIg$#jT1lh5VgB0@H+bqt!FQMU6PXnU1o$=&NkNmFsB#9Kha1sh@Fup6>0YD0o_r zb4jmJXi`Dup?B4;Ia!}A5mKvos6B1ZYVXE+GbU!wjCZ}6zwpEr@dH^|rGlIPSCCe2 zSl^8~KbaONc69vwB&Uw8?RXmM=2G?Yu(1@p8y9rU=hsg*x#;gx(4b@OW5SZqHTK<~ zk?%skbwHouD_uk*%Pm7nIW?w{JZoB9GD+0QOrS<1_pu_xN@-wY!Ekwy1XHB%`RA$9 zjE4;|8A1NM1yU>1g=xTue3c3GS2u+<11f#2G)!z)5UsV-vzFs#A@SO05{ua#N<>+FgQe#Dy()XX z<`%6e-o{{N;pQ(e_HdvuL$JTuHT2F17=*ebB6WSCQhI`f+|tUZf)SW0hEKeO#P zQzNDtM=FL71aB70?rp|3xBJdtO)aw%e1iX!xAS}6!m72Iek71xo|n`?DTtiabd#Og zxGlzGnDn(c8aEtM^yt_juxuFz7Yq9I;}$y^DbVt20UFONP77;;5(>FJar(~?uWq^^ zE2wtgaruJSkB9WbNkx567BfqCoZjEmGCj4`l^Kd>qSN`2%kaSfJi~bo87OGui@l|? zgc`&6Y^kd4cQDt#Ljl8c^PbjOP?lf67;Lt4i+@W{)Nl#qadgm$1?xra@Lo~V|83UT zc9!7lOc1r|bYYu*j1)KD4ceXkO(%L}b!6%36s28z4jY5YFTsw`%{DQO@AvzcHXpjH zM#9_4S9APr4;hNzZPRK^?#V#UFPA==PTFA-wujsl-)%T!y*wrS7eBn$!3`Yjh zatQ9CETt6YbkvFxDh?QHLVCdN()={-NcjB+_^;nou!P3DA%4SfR+T&EE~d({Ncuc~y!WapiDshLt3#vNFSx(x~>v ze=?M4_iQxTzS7Ol!scDgboa)8fT?62GyYpZjN`ur#Mqd*{|~$2AIX7@>;J~Z{xcxP z$;Qt4|4njGg=EXNX~Q7VUP8+;dgA##Uj)GE5ygcQ&lrd%=xL*x4?fciCL$x&Mq>Ad z4?ZiDh-*(e`uyoQ?enPdS*?DaZUgwg_yha_kG|Eih|iv8b_pv?ADBqz3i59{ zhyRLt`j>&4>KWcR;!=U+LyPhD>oQSp2<8BLOX70DT|dU5;3XnhHdk>8^rfX?5-x!c zhq<<;9UK9B4$LC}>0!Z~+(0*hf8t=}gS!QNWkkotqvziONqpU35?{~hD|%o79XkHm z>_?)K{R26Oae~l~hcKtI3}o3gh!oI|69DupuZ0#FFI7dlRmVXHq#^aB+2~Zzz7)Y|I8<65uU_eY* zqf=N{p^`6E`(qvQTR_UdRwK!vI>Gq?h)BLtNZ-3kVAqf>)9TI8k42L_R59k@E#nYE zY<{K&+yy-hwn}y3JDHkZP0EG$R?c+Pw3Ud zEgIJxFgilFi+=;}2{!162nY1_{`E19C&H*hHn@K3@Ou08FlmKNYhB^;lkvLC%*|cQ z*&nAN2-Qyu1Pt77$OJ_c>~;TRkrAf&YG4=mJ)yZd0&*BJUm4khT zYzYVFIS>N4{(*t-WNc;}!LEo=z<-{d226tle~^curbeJ2frX61-zdSvjF$d<_ly{D zz!jARycC4j3vGO(e)*{P2wRn+R-5jx zRg#~&r#3OU9knTsYTlB%4o;Rg8_ingaC!MTn|xO6QsY08o#R&f7O`7Zd zOzprHT3__MtBd2-Z3JWmo{hV7O34Z_cB+>b)KyYzobu$S?lv14-fP_AzG;ql(S2?j zWrWXGxth*gYh=yc##3DeP3x>1hvWSCT#j;{t{VodbBU`y=#VX)#Ucp)RVsH|4|RYf z5_Xj2Zf2nQuS|Fx-CkOqu%0U;zl>oPcW)Q5d;tGtb)SlL_mpmz6MOF^a_LzE#!mKU zfI5dlmz%}6h;5F$Z%WD5Sb(?o;zeKQl=kBJ-L=+sAiKn|^)~_D_v)_jiis)TK%H@g zxoBb}Ic=5-k;99!FDZC(5<=AVT^&1{;XG$i%{6@--Wt5!T-!;cu!2j zoi->rZAE0+Dn@sOwT*9wRG-n_3W*?r@v}Y}VRw!=^=4;v>=qpjZa&GNVt5^`Y>m>4 zTbfkNV)C=H#W)e9(?0GHzt6P&-^D@Vm7i!Lb9-XZLpSuAGDt>+X#;eIpyOK^p656+ zHh3JBzUOyeV6~Bx1_w-+u#gE8(2E=sX}k+>KE#f}XI(f-Quh9R$m%Y49kJK-8U4ih zSyQ84cQlR)F4l|7!wLYOwx`I=);Uy{RkR);SePv5+{fNHI{Onr!xbK>W@|uzdDQXl zKRvm$HmU4QV9DQ*IKOrE5WaFWD)l|`Cff5BmaNi0FBN@U@Vj6+nZ{G%+Vt4kv9vHm zrg4#dbyns5sTEr<$=Rm!ZT-Z&H5nu@5Qk6FR1xH}gOW!QdqRcX2inJ=zF-P=FB@#q z02Y|@Zz?6u5y%3qG6$NmTFrd`!EPfN%8A#5pfLl+IBT`rzjwiPy5-x@B2q-l#D>UC zyun5hXdkd=7C3XN?pdwp{wG>;g%B>ziqu?y@VC7Y^jJo7&9~ti<_ODO-{(OlhPa@ zJ7IN}f=070qN#a2y{gX(+j}{9Dfi3Q{*MERQ=vcN?3h z_(ou-rl<9z$Xi(`U`Q~x4tx;%*Da)w?#fPW3=~B~S_h~a0Vl2POwNbnBKmp*Gw0~E z9;u`sPusD_2u+^o&(ck)AK$jP1=6-y?1h_HS6how$HD1;&05uE>o%^?*|9~l(u!Ql zGbQZJW>z3A=r5O~*zL9NHZ?4-nVHkR=RR8egwk|w zBlmZ@p?zD{@vQSfQ07|?4vWZELgyfv?}V`PBD}jJ`Yfsvd`LMokYy(sSD{OwYl!68 zl;A;6wHNbjT3n=Ttz*3N&_KZ6eJ^3#3rQ~Wr+ymub>mBXJn)(g7Pl$j973jaVMqDc zx<2iP{t)BS&{_p(-GsYSCbXEMs==G=mPN!6cGCINlk-d!^m2L=o1CSRVFTH%_S|KP z1mhJ*;@izj^9UWvN&VQ9pBv5HBW;Hkh7lk`5Bfzun7St;#|vqH7s_6zG%rdW3WZ7Q z@yuLB*F}tA<<86wOIHVQ9mq~^l4SGxHs#&4NgsL7#qKkWPfxnqJ3_lWDwW%(;<}6q z-iGr#khMKb9M(6}Z?*|FBAcrUH9W6$TT2owop%^cJ*|HpNV+MimUb(>1&(aoV?bfe zPH<$*gL|d_nT1_PkUlcxCI(JvDT_gilj??pMW;&#F~A{+Qxj+k>an9VH3A3wi3fJG zs&rmTv#SF4+!KAq`2HhD78=M%Q~P2jdv)vh6rZ3!v3Q59NH4YnGw!Rx@H)Va22J7! z#o@=HVsC>w#}VY{w0-~V)i6!mv1$EVkrvqO1fgZ4FWQJeG-wXzrYJH5kZGM>7kO_y zCC-`Mzvbw$f%I^A8r7VJ1%n{OPg~0e3l6d2RX$+rwHlrP^j%!S2LbC|nkU=Y2fJh? z0n3OcPIox2x$s9SO+W`!X<+if1Q9Igm*>ZxWFn&J7n&i*>?T}x9UXl#=gkn0z$>|Nz zmz}qEcNV**53^Hx=gJ384Pg))0-xashGlHin3iLF^z(`C#QmhnXT~lqJW5?896XXa zjdH?Ly_M%Aj*!m#z%^g`b;Zk~E10Z{*T6uA*E8o#Dt~wV(rWlR1BSFruZt|-Ml(r{ z62G19FyAwYB2yE{l-hJm9ADna>UWMCVAt|oiHD5JPEget=4ZdFq>;teypXE3MLL8M zs*xI*O+g)HPwGYKMiZktt|q$V_xiDa4?W(mXWuPUDi#mo*Kw+@t0{ojN;AJS zu7R8m4zl0>a)P#Hb*^sK1MQ1^l-)ia-kjL7vGSQ(V>`z}{{BqfnAf=)(Gla8lDoWE z%Zbe7QYkEwsX99x|E7v1)a>91j+QSq69vOog4NP_6K?4ka+GQK5P`Gh4^=AXw^+aL zbrq^DH?zfdpd`S)8r`v@b@}u%VkI^4vr@_4NJ?zn$6q=c*@dTsJG74sliw=ElF=S5{ZHEP}3F8b2whj+O(wHWgUpg z>CuRX()wKaK@Ls}+xpC3PCOA~wnV;6-lp?EjbgbkDb6KO9H!k>c_lUak?7v=cm{GL z^Jh}p(s$`*nFqEd;KFL0230h>s>hRj=eF0Q--?x49(FWi3zf@WG{&#}-q(MLR5>07 z%-!~n-L0a9q;bxj%7FUVM5((iYLvlYdMGO3NMFAe((>LR?c&+$KN+N<6UCwt-!sz_^#iZ`MU6E52X-EH`?jOlarDqYlPwEf)s7J744MO^7?789!B z+fX>fP^ti+uOl(T{qpiVMTkH(w*!!obu%XhoMe9AD}o>~We{`oJjJxQaSv{1IdqCu zhxq6Vv_EuS>5NzB*>a{LJy$sVvZ$4#Y35=Q2HhK@`9=t%Tu|?XgB|Bh395=bGkZcd z<8Ts@WidDmz~<+*DOSVEmF2TpSjuoDvx@D$DRn;bNpw%}BT351JgRH@mpN>csYNMT z3mFvk4|wxmh5>f!X0;c(^ecT>S3~XTtbz)`kiVCf+}d!)LAn|qq|~ju$PtTWHJrv` zBigB(THF<7S`AL>lil0(su%1aL+Yss%*nUt?Lo>ylghNPS9Y${j?Od zTY4xJ5py}J$cKFin1z@Q0>3SJw~~4K)bgM?XQXbMfrPq5myYknFaR)Ka_}%x$=52y zhre4Y;ZQ&kL)l}cJKkp81jK%kywnH~7;ffKs) z0lSX2!WYaRx5#){VytpGoKBZWpUCqncN~J)&`RJe)1R z8FtJN*r9ChWOfw$24$6-si=Rn^3QENs2bSZ(p39ZM=fae+n7;$0WI2}W|>7obix2} zqW^S-Af^m;Z0`yz9HMM4+p~xd)rIEO_+|}zsR0R9%hTh%!`66NOYB!fhas3fSeC#3 zG4`+ag^te(oh*Q}W$V5x_;a=f@OtNN4{Z(wc`$JiFx^sDyNsT;MSp=j-B6nae7KaV5$O6!qlF zjFgmWYoc#j@*UL_yQ?~nv+5dsIy=%}aRyN#H2Pu4>O8ld5uJ8(csCF4ko+l?i>Hvr z*Go%k3zT2l$M+nUu_5gy4#V5atnkj89xXT(g$!GWu}>!pWp*n}eKzD@gUBJP|eWxUqIP1OB};OnNR*=RBvn z_i$Tj(&8{JbkpC8!xM~{EM#CVH}qd2dzy~iFQ!$tdANTA@9UA^^ggjo+4a}l)V<-g z#$V7`n&&XD%9VJ~XU!czhFcJ)n=rB~ZgoZMV=1EX&2H128g+|T-R4#7Grdn}$9oou zl;km=(aLSXg^5Q(E=34K&4-+OqjJ`*pC_QqGa|4HTu@l5D4*Ho$3X#F!yf84app>L zmS? zoD}yoQeCtn8n5-o=W~E3dT!GYQ6pyvBDQO2T5!UrKS%*8ytkygA{sKAPn~8xakL;+ z2HE+yg#1Fz@4{cC#UK`|LoC_JfKpI27NQ!+c&B2t*65CE>!_$>%b`U~FrS<**JUwu zYcY1>F$ZQ0PM;(_oG=os_y+oh#9ANE0c&vl5Crj@ay@j22Ei zGeJp6X*43ihQK4yiOM$>&5TnHXeEJneJh;nEef5FvbAf*%yh$q2vf{`Fq=CrJ^>>+ zLGQ$JAyhO)o`guNF17Kpxdq4?ev*4xbONBB8TdXO*`H)HWwQDWKc_((X~E}4w-fUh zvg}J%7%J!cBZ#p^?@RY^6~|gW$#3VBO5U^)bdfrTjJLSPe4ea%dwZ(5-=9{&{J9(J z5e*dJ8kJ{(!_l{`@K;*D#I7aVNi0I`FRs-~UH7x}^x+}j_9r2uo3##ut&VQj*F!Uu zdgvLl3Z5#}5d}G_Uxs!t3*0}Fr9kjr_V63?+HH7jlL3K80<3yP&4)O#j=$tgA5_ch zMr)El7%2;r33F~5qD?cc1m9s6Zu~pbd$Ri@Nt$U#PHu_NDOUNp@1>-0A`q5;c!*N( zxxN>4eNX^JYLSPN(-}NW*dD5;rSmYl}Fwd=;d@%X)sxS z_lO;v>><|u=g8n_JhO0ATTq8sG_kid7W^qi#+JPlxjxoZlev9W9=h*CcG16{TZ>LF zw0x*elRn(gguB=w$xO(G8m5;->qk*t?`qYxEoc!R6u;M6&~#0c!jg|&bND&RLJ4l5 z7)7`SCx0Tts3ue|V-9+Z-f(xT6e48958RxL>il>|6kfoyW)>c9pe z-PDSy-+zQqAKSTC&*i1YTMo?!TY&Sij=};oA*^99Z6-za(XQ)*i;pt zg}xeLGD%aZ4awAxbojSw^aLF}-uV-t5zto6_|Aqvul^Eui4>U`o__H{XPwzga2T9A zE{mC9Tf3?|uBLHD!BYpZ4Y2EC`gGkktd~@8@xI1jpRtbX<2qmM<;SCQ?N1otcMfar zL)yKfGQGUV9!GVP36@DO{4o~KhB6{q4z{S^>M2hA;?p95d)=vL=6EA;V z%9mzTFjCspfM=yH1Q3anB?+1l!`Xx#seR~PKCYY@ z1o>Kh#W!!4;aF~wFw0Aqj#|(f+rDMvKdN~+rh|J$dn1Crr6iW$IOx(tD908M)y;EV zwbnB1=tnbKUGVop0Ud|vHPDHgP>?q&)EzkXw*SbKB-eIYHISvsAB5(r{cPW1(WuFuDQCS1g=OwodMqYVE1M$YPX7}{X7nD@`q~35W&Cha zRQb{i%D#Y$*N!O$>#G3jI5z=e5a(Yl)y2`zcgU|!;%g4>|8n!UGP;fsx$x4gsE}_^ zOb)%qVBw~v!3i_gu`SB~-Bz>vd$8Y%tNcV~7jV;4uqGQs;DldzQjtG=FH0)m11}zl z>$GhHivu>qo$^iakheHP^gVSE#;dn-M1o>+eKA0e!Oz>pTPJSPW`H_FaICd1ZP`GJ zTtRT(_9zayV;=AzKhARHlNvs!^b=*ifRV^Z?ai13uXmT^8O6Sy=Tk#Fa^6oEjd}Aj zLZuHHLGd>grLNhf&`fdZI3ZUJa)`ep82BYkJ8xtkNC%Kwzt;IO`12<74F83CiGvi_2TxG&@ z&D~kDZUb*b%*SdHA_yR_Dw$T|8SI=I0XiSIa)J=l6AtTgC1iaYfiUykVE7c{W~T}N;c{u+`6+V^_`@H6>~%(yKFkC0gRK=Zua{St-Q6`B(C7^- zv2guwYZiu4+{)I~%$bN$+}6m|Ow`Q8!PE?fpC87>)!EF*4#smM<{!F4Z*ze@8cczx z9c;b5>)+zVg|*$HN}wfgx~_kkDc9_Qu4-l0pPuI8GEN? z6w2`R5Rk{Y?ctTN7FfOYUGU@8`}6=qasb1!6yWwrm=_-(L)-s5H56EI^6vzY;irh{ z)h(m`PmcJ&+`%o&H##jeOAD~$x7zgw$CtwJOaN@)!R>xN*rx{rcb3O82r$}DxNcZb z%E0T_!0Qj)vw+C=58Agx>QC0#kB5-4yt?|oM&a+LpMb4DD;Vnb0jN7-mq)MsK#d%? zCP44Is&0D*BDAn<4UUc9yZTZRLjWNpunes)3tW1Cp%D&>fM_gKTHOA*ot-)O z@2~h>$X}bn-<6?ru8;XKHB(`#BU{=K_8z3`qYLy}APOUjHFN zfB@Jk^#CsX3^Sg~A9IJu+?W zHgXF03yybJu?P0xS!{zY_hfd()*-P518i2UO7CzO$jzkm1a#;V9kdHi)7NOyCu_Jt zs&wq#t4?GtxnV0jDoaDrAJP>tNg}))(5)C41V``_cVHP|bN+>BC)pB4JI=S=Hlzv! zLMN1&#bz6`t{<`$=or%b`R}zfr~9GmBiGenf<$j=`b*dCm4)7Hem*2#Pm$(dj;f9>yOE6RlCW`jYpBg-QDuZHY_s5PQhX=sD_9L5Px6`B=AP2bs-?= z5y~0vjJ8x(=w_p+>KZ#;I#!;oj<{(t(Y6EIfH*ohi&~2xA9ZbA%$0sO8%AEK&c`~e zSIr+qZY}qBo(CGgKR&a-@g$INf@IugRX<|-k$KT29_aD99D^G1eA}lYW(%ClFHfju znP>*kpx6_JEyiB0zCFW&gSolvWL`P!q$We17Z9$M^JS-XguuXy+LhNsng`gZJxtqr z#48|U!iA0XZDyVavy$*DAv;qv*T#t}YwY`N;aJTExED~Tq7jqN>cn3s@b5w2>(1q1 zj$aPeKB4!&x~tqys($Hw=Ym|BY@DW0Ei`YFOB#Ref<9Yg)^Q0NB&*%Hgm147be>R1 zv_geZv`YS}o^XZH?8F7ysz#=cS8p&4anb1&s9o`-CK=S18^Tdn(*v`?bgEJ=^9iyU z{Td<+2{-a*WzD~jA&>!bjv8B3H+v|b-!VUAq2t_M1Sfzl#PyuScdgWzxAzQ|_H^4| zACOuReHBqd+#m7CTSsA`)EjO>7xE)F@DDL^&{2(_8L>c`k?Wsi3Nli+viKD2-I;hS zAd6;#U$_fn-;qEF1K&KzAeOkSinz|6bktZNzWq*8l0tPU~26)sNX)TieKz z5&*Or_!G%@4+Sa|zy&MKTksq?q!ttw>I88cx$1PK4`Pdaj>@ScZSp&CDv>iRYkweUh}o7ZfE4Rn8(K5qL)Wbu8Z!0ZYE@-;@u-rjyuJI9ewt! zr)y-N3(m%qJgrd~u`wePu4HI_hQQWF^S+4(pa%r?A=?#1C_1&)?Gb-}PLq_m;5xaW z83vk+&z64hdx79j!JxMA0e0X1{ry%d71B`g`0uXtBkabV@!(V~Omx=1rCSu1ceD>3X?8 zo62#(#rdZy%l*hYhj#nO?w1M=!B9GijpdcS$mtf{VncLvpCzqz^~XCjSdH`11FwBs zDA}Z_yhJ-OyN9dNx4XM6kwVURiecVRtm24iqwi(ZbOL~*2**#f52;RY`S}o8~=zD)t2}9!7+R%6Lr+o zv1M{yJ=*#U28cNWS$SmLa|8!}qL864r#esa<#n!8Ctu%>q8*O5ZOVH+JlTjCJMvBI9f%0$#@@bAkW6*Tz%`9K;#+< z?XTP;jv^8B*{x{(zTvoG5FUZ_Bgbw6Z>Jg)b?fu;EO9iLfnPD7ohkB@l0{kKQo^df zlSx2;^JeOHfXO5Mc;&)}pT$|a5D+cVtY@-Z9s-5C5YkW7I(#0N*(8>fy1Tt@x5TBH z816#nQ_Z*^o$wSS4#IHdYe7i4N@N}0tC7AOm9`$#s*OSn%gIoq^G_l;hullXuJu3$ z3XC|Y_|C0)!x;bA!7j|X>qnm%zwB%0PQcjc!YaYCBCOcRRad9_ROM@N;hp;-Ui>o< zB3|*E{YPgws^P5(0bmX3G{gxR70XD_?l=h42U4{=D`p;(Y1q_cBtcV!$u-w;R2^Sv zwQdSe=yI9H=x-U#&P|kPMR;n-4Qt~w{K<4h z%De`9p~b-gC6*{c$1QX`)70^*p)LkNves`ZdiT0SyQgHl&B_x@GchAG^__%XOExWx z4&k+WgI`Hpti?_D4~AofW8qrdSMXH(Tg@tyHdI(MpuQe+AKru{F4%zWTOtNU3Qmczbk|3pfn4Gc!^o)F$`R7?fNNFJ_>o!zT~~B;c6z z6b2sSr0{Jn#Y~B`gydA=>}Pm*r%gOoDI%>J9(5h88Zy01Fr~d*y{LjKxkG!D@Sels zKSB<`77a}2U>3$1a z55JNUWPY_U_cu@hR1v~|o5j+y==KE9i6je$-}dMY%1zr>C0!mw#uRo?Bh-cO}SmUdJ9;=8@^cM<7%B&p17;Dh5t&BFP6EwiAhm4 zy!2har^aXG;y8BiEsLMg4}Zjk<|!9aX_rmEZ`f>D6AgT=jR&>;tkNBGZFeR2TtNfO zz6_mn(F!z3z*vU7L3LEs`#Rn{$NWlKU6?6Dy}LEb%Udni9`y8nY(j1IRs>awBF5i% z08~ z5X1_Wy=7Ph_p{jWRrn5?rt7=o)Hk#wXW+{h1k`NRKg!1U>p6vpg#rUJnS|COL@&{b z;+b{HIxQuC$HYs%s4b6I^x-X|{5R=^o~kibC+*W4g#9pbq?%&^=siy)bdJH_^2juL zRVD@!!1EDM8^ybT89a#d?-VID>!Zx#^&(osiWFt@xR&cj<-8Ae2X93Mw(CHBdbP)P z!pFhe6Lt|ag1UW);eItSm)JUe(wVYi&<%@tGLo^X%h&!EG;r|*GRzfxsQ~*v^3oO? zOyx$8q!xaQjJV(h0d*rn%R~i6Z1->F^G>4GdO(K0OabKiZxo^u(?(__LuwT8y(&QC zpOJw6wB_-AFUhFo8v2||KRI^oYcPColWeRp)z76+J(el8X$Xg_drt&0r=4F(S~~U{ z(?$_p=CTy`LM?3=zPxUdf|pl4onTfqLdqpU{OPS z3~`1QDn2Z~c=(5Q&HBNJt8v#MGPtS8vf}6D&$iT~<;xPs@77xV1g!)cg}=_$&vLXU z8-64?=W<@7He8!$w`=CO0}Vy`w8wgUnaEgQGb47rI>9k;4(=+C%^8YiK)3EFi6LKP zYuSAdl%Y~HCP9edeqHf>#A0Qa24*$Z0>l(4M5Wh%r^L)%30dLc2^n{?5)Tde`=x7k z|BR-zj4qPQvx{6S1}yr3YhV~ecDLNltF@gTMIR zIG^oM$&y@d|N7B|32U||sOc~2hq56N=y)BLVTa5*Q$n8Zl0)EXHez*co|;bB7CS1n z%wTEwtFLnz3UWdR^2OQHh?o8DseKFErr@=2wZjbVQ<&wH{Z*bXmPxA8r+}uCBi5{Hz z_ZJXA$Li|74+2G+kR^qhB3F)RjQSk}t&WDN;zm~gD%TG@jPGru!>w-R?)Z#pm`?dm zQ~j(oN*6U=?^Hij1&>lGuGG*`0O+r}5wm5|7zR61DY5RqB3%VJ;^?BdYrB*=(xB>O zy6$o*G#Bn;{ZOZ9SQ=?61ET6!*beanU#%;l*YTV*b(l3rx;2yBM|XABzn7=u(TS?^ zsrA`dXJTgGh*2~EBJ*IS6jAbTbo6x-gQN+}n$C`K6xwjF7mNT^eGn5WIm*`IEfx5SkBc{#RTiUJd45j7sEYZ*bow*|9 z@3=AJy)*us`JB(j`ZBV7I1{&GYJx@6%L8i7=zn?)IH zXKqI`2T{wTpMzI@Syb1Zh>>YG>6FzChRM!D!-`zOTlyVX+>-ChWNGY>V94RC0cKV*ZaX0UQS zHrlo$R`S7se_B;7ts}*UAyXaaIu(Q8>DMXfuY@d}+Osh+R9~!;Ut{Qk+4-aoqof-LSUCUYaVUlkwt^@g zrkjp;J%fL`eh^6|ysj9( zWvG?rYc_7T&rB}j_2rPQ=$WFbxmB}{lxVRgz5`?`Fjmjj^#Pjp3OFX4W;rRdAoX7| zgzZ6#JwRZwr(p{wAW!SZb6*>TJ>F5a2J+{uTNJr)aI;NH4<7=dRvOWN{5aBW@iC@f z*Uy6&f!%Dwe~wLBKWc|dqK$|sBGZlyk9Pw57ia77>j(%ninm#x4HTKEdGnFN%1t4V z&a)=bCN{{qq-sV8}i6h&Y!%P>7A|URpe991T<`%s<4rEukn5d>u z+bytj?DxYzGvgtE50vtIWXh}ndYD&p-hXk#DP+;Ngn{a*JsA+v*#m$BbMYP`tazMg&)WdgvF#(HDZur#}#dOA~EbDfaFj>B*)}UPYjEb)#f~czP`Fz=ME-2#FHJ3tqeZ zs0d9|xOn{|HE5N2E!S{r_ABF;UP8xi_-0Bwab)0Iq@Y)N)!G8on`uPzfD#SHpIiqf zo$qTl_*s3sl9xHCm5DlOf32znq1`x^GCO5Ez7=Ly3!b9lIN3o z8P`X48l+_J2}RWAS0N(II!v4pMfwg1r6(t&yUR#kFu4#(aw@|dpVOynZkNeogwHy! z(WF(4X?XSDma@zaU#hFIV?dJ2aI=oiF}H4h#6brjl9f$)>tx8TP|6^HREM|R4v3|v zJR*o!^9Sw+dhmC*{#D1vczGS&cE%^R$w5h#Y}{`<3ID% z9v2Aos3vEp6^H)JU6*#O$k9bS;?Snfi%c6`{SRU17%~hKHrU&?ZQHhu=eBLzwr$(C zZQHhO?c3JYSG%<}|K|79G;^JEJ`o-lHF2!@7TBP?!_q=g%;I*5sGgK@-~-laeYXM4 zbvn7-!9Kixu2VBARK#GXjviTQa~o_a&ryu|qH=lF#PlyuM`CkUY8fl?;=EF&mNaSy z`ss&yh8g&DhaY`>1s;R(9Iac7v(IHwx10Q6h3W0`DEvn7QXOxL6UgpC!n&$qv7{BW zVj?7im27B8*9vzi+6#cdqfmvMS;TY1L%78%0%F!!ndFng_DSz>8}=@=Z+?z$%dNc z57Af>P<{;s@Gs9m<*gtA%$(LR8#_g?d?BTiOa}_E9+FB&a zN6&SAhpRF3aL{;-GV#+C^zZts`}r5PRq^v{(69v%Q(|(KeirDTZJ3ilKxN(nBa+1% z9NduY-`f|@%{*=l(buODnE zpq%k@Q}R!u#|r2OOYUiLk$a}M_cM38l{w8H-808mcq0M3zKa%xAv2zb^|@mKXct`Mho{UR#UXeUaPw@fy3SbA|;(9nxsxXV_5I z-UX+$S~E3M=12*AA=Edry*a%T)+L_9!dmBrFo1Wy0to~?JI4BS1_ld|TlpH=<1cp( zEeF50e$=rO=d!2 z4+k~WN8Z_TUGizUsu|dw@z+DnNz(&qJxbuNh1Jxk#K~2~#UqqT9)Y06`K`?vXzi_p zmtas_aw(P|t72eWvOZH{edu7{FiPo!5|}1}o+5qgH|MVW-1z{+N0iiLvgN4S+bAPq z`7>*XV`|sl7ki;oc=6;T$POEIsKJIIe14Et_MK7=M(9cLF%03(#?%D)f~Y#6T3=Lg z6biIZk zx?h1|-r*zlC>izat$CJcf%=fp{QM;@!;WBr<0PJbD^GfTZqKufe2LDvqC1fmu>{?R zUy5K~YRk8n{thx#G`}7nM6j+WLru>%NnTmhVO24KE0aFhUst5!co8+qKJ(5Ni8+Vk9^A0Gu267Snw zl9JS&RNZW*LXRzf%-&b6erq3;l!Cu`x5I=7dUH%;udqTCvjh@TAmF1^!|#^l#i4+p z=)<8U|btd%dgvT;rH(OX+vW6?`E*Q6EYH z>DIWn{OEJX*3e)^KhI694i4s3TklXWMh#@Q!T0&t^X9^a5-n}=_P1-ssw6R*mYK~~y`m&6XUH^f0`1RER}^ZFn#X1|(PeE6UgzpwCX}$i zgIB04UA41yYXeWAWpDM4bjU`cQh*34VS@xRE4zSL(4Tqt?K&cEnZoP#($Hf99Y8@O zN5yNggXC5*MSwunlL%##5NbVBXJpwhUcc(%A{4af&5&+Tp=3c!F1S##@8?DHj8c!g z$Z7DWjzTK&rlU@(vDSPm?(^PKr0~(=w?42VVAO33^02m}m)-($#UxrQer)2Fp2C~U zo9~Doz0weAUjK{cbP~%g(tEgVIvXdnuf%tBtSgFLne_CvDP3b3)c;cR>t*lAn%93> z%BEy3&}`*%KcdV6fJ>3$GdAP%Lm(V;cM+nlFhIf>Y(S{cnNw)Zst#ztNoLM1>Y`U# zP+6eP66`oVc(8pWU_i+6Ks5w_+5G-QjqtA3A6(>A2!3m|?-m#TIu1&+ANMzl!Cv)E z+IXiA0)~>t3e+NR5K_j1N|2I(>5o12aL{n^oh_h#_G37(5AK9*V6d! zROZ}{xbBGAiws+Ir@sd?z=d7Qs*S9*jOR#Pdx*iOomgsJ*K*4*JNq!vc=Nj5V;<}Y ziQdcv%#2&F|ErWIf(eHhhx{jOy>qNnueyRW6=UIg?1Wzv_g$UJ)k>x=XSPFu{O&sD z>(a|MkemVoRtufyv3R3%ie{$G%&DG`em=RI{K4m30WX#iZ*E@JM6y(F7KXnz5bxEt z1+yR7IAS5)qdJ7qHhl~#bV}DTF@ONZQn0c)0M+_vfW|CTY3SkQAkEvY$}jO|W!%~f zDQwE-GSv$S%jzYg%e`s!umANlW?3{0#Gr=v0k>2jSU66aS!x_bOXIR#unf&q zROJIQMQFZCcxsVTQA6{kcxa+fh8{=ww5#`Ot!o?-cO5awBdnpINbbj-1 zk*_W(rcdwF=;RTy7au4ee;H=qR9q3v;`XfnFx_&2phVY51-ev!1 z6||H%KG9S)mr^`tO82wcTRbbUHdbR7pC<`a3idRgu*uHBsFgp6^|!LuP_3MG&17r!n^iEpU!6!Rzt-CAfDIF~IKn!6o=hjzlQ4k&_z;((4~@jwK=%E7 z``VWa2l&+%=h;Eiv8qj;u8%CB44>pwDQTd7!BLG81T#L786v|_#Y@>RLiS^Fupu4a zVHm^ei@>ke5eV1pIcv{J3^R;LxUUsT@UlJq9U9&P$^CRn+B{QJi^C3*nTnDnHcSk( z;(xPf4PSt6{j1O@k_pmp@M zR8^xdp5MHhxw`(ga>ozAArgtr{smyx$JxV}!r~dh)<$yFfHKWjhvlhKT{k41yOR*@rPm>d1EvdZ7mz~(2 z(12ED)*|Q8wS5B6ltBd#=(QiEes!ZsSa5sl^>qTD@hbt%#Y8EYjJEc>vj?eBUehKQ-YIM0*Ya)Y& z6h1*Li}{MVk`T>?d}RL3!Ct)Pln*$nC`!Z%DGDMkr%zgG^yV6iYYk)(fjARCbdAt2 zZ=6D}KTK-x^o&V&U+bfDqPMEI&wu8)jHrvm`d}e9#J*>-x&;zGOMMxee%C_-{VX*7{W|%n!Kf zVL9oBmjk?i+w`tNqBLd4yA_w~^hHMyLgG@q2a5W>5{!Yz*!(D0eCdH>WWNq7kRG)v`8nn;=C25u zECnfX*t!II)!qsr*n?Heb@$oy{% zHvWcseKZPPbSpY!4fV5j%kqj4nXY7*5#CKfCeXVRPQFS!xsdT?!y^g(n5W`KTtF%7 zH#I-ibnbULEcJ(VzB2{yse1iMFWi#OtH{JgJ^5x+hosUd2n*R)u_Ah>cOf$ILKh$q zm<7`u4JXp)WpzS-ia`?$~u;iy)+vvN(E5 zXgpkIb3`Ub%?;bmmmV9s*B#X$$W#5uG!mr^<;Xl*%Ew4JXHM@=2>th_mUBS1vL?P8 zu0*fT;?*$@AxRJ=d38|se)$l&(Q9ctV4-RLC;t{)(w2u=S>N(XQbc8nzWljUBIv9~ zV-gWFNRQH;C~jaaPxBg6(kC07&k0em40Tu$=+|Xy_sS6gE99EY9$$`$fTEB{Q5~Ps zqUug29;?*F8rVZo`vZZsnpXpc|5 z^LbMqVQeh_GosLlzS_Zn>g_jUi^JG8Z+u6$_CCJ5M|Q&;t0%iEE6eP~b$6>(u^FS+!? za*k-^)sCE8grZ-WRq0gv2;3t|#oAG(!3Jl3S^#wEV5vxw*hS<-fBjrM&>Ti96-CG! z$*a;Xzz|~6bla|Gwo%nbI-A&fFTZ(!`t{O6H(%!t$SZNVt=GiL>;dA* z=2GOi$1#L@#CD-MuQm@cRnl(5bY@th@XXqTHy;(QTn~=4FUQmGd4@XBlTRlJU-EQi zwoQ7?g2!6gy}3%^s>vu8;W|H=_FM8yca`LE#aYtrgRio|F1~x2$mp90eW8dWIp>^? zTl#xFp13@yh?gq!|B<6fC%PV0Lw(N zzK$mG>%+ts^TAES*C6Tr-3EzlwcZifN2*fmws{E6rhv#Ts{f^=0PK5!>a9Vi#}8#- zdjt#h!qRUzSo*2aqb(J+2)0ZmpJNQWW%1$|Qz>apeu=n=2<_rO;Nt&s&-)B31j#_egrSJi`Lzq6$Ra1K8QA%)Jas8Wp`rWE5y3f1o)>-LfVv}v++1VWzSZbv- z1I)sr$H>LG1fWml={(SjW#>3_{+ls^VgDL*39^(<+N2nWbNmUpQ=>8jot4ra%zwSO zBu8o6B|dp$Z-#jOgX+)i#?6k5R(2W!ug|Skw5Hkv#fiB84dpL&ua4ls0%Be@6sj>hz1U7<$2aRqU z6l&4Z7%FYeZ1hxdXlb=q`IHx)(k~tuZqb)foM?S>B`rHDIdJ)0N4T61pvMyr&$f^- z)WzntiC7Z0A`ZRW&^1bp8YDz~KEYsd4l2{j^WT5h)|~DJ2ef(M!HZHti9_7F{F)b@ zFQr(hOtZbYbl>BN1Jzy)toyGqSMEV-^-aB9*@S=vHva>M%71#i7A;nUr0b^Y__ZEd zxKu4s(L*-(HUU+|Cs&eY;YOPoOodkA{g0p60AB}rA%agg!;BQgzc~{W=j5782{k-f z&GuO3waHX-8@$J;$~sS_=nf5qZ@z)rnJRQ>QVulD+r`)-DeSN?*9x;7YI>zlh1%I;L$RIhRoj$n9^WL}a1xAJ^hfs`;5|1-7Zo=Z zGEY{wn~YZb;L}b~$(X+UDjEi@7r{$BKVdCqQ529k7QtZL;5>;VpqH#WVNt+JlhS!D z^@jpOLq1sGVCd@nRR`Qd;l~|XAZC^ADyrdZp)kWjeGoDi^;NVGmE`*`qJ{0CNhWd@ zFEp#rOxZ08;coR3EJUuQfirer%}5SS@xdsEh_~6oH8gEvEjXT5IjVJEf*D%gV+X4C zMXIiqtQjm`9?e16>GRHBGc~~mTo_bO1PVzrc$PMZn(??f;U=HUB+DE&KEOQh#CQb2x5GS z;5Oi95~qBKGrH0absP2&E5c#tXle@`QAafM!)5=g6siaC504Suo{8c^w%fAM9Uh7% zbd0_5ruaJ(y~3GDWbyT4T8CNzv29)En@zX^O^I2OrNOK7PT{U_<(uk)I?dZ8e8R|N zPwDI?0R8-KIG$9%SKDT|6i5kAIRQ;{EY>cFko+Il*bn{1mmXv^pAa%!WPpyI7A&(c z_pF#qph#1c?#?znTb&JG(XHfCF}3iv$@?wjUaCb?&K@Yy?gR)-m8LEhEFX{_*Kv_o z_KWBH$I&Ai24a`5!|!DdgYGaH86e$(=|M8%ElSuC1!v@3YpRG##Y>M=u6T~ZQdcdf zzRr461|Huua7MM79qH^eul34Y?~@FW19sT414c)q9EFHcxeKSQ6rJfp^4q7;*|H$5 zGfu7JCL!8eJ^@oGv1dv5rgp)^0B+v^LaTZkH`c16cE9PAGq^J7L2(z12rm|pbp*)4 z{6({Hx%E@`0R@S1dw#EP`Po@h!zPmwZ2ge7S1C#M)|+?Lf~JNWPPlSjj&B>|pl2B) zIhnZ(`c`JJ7x3I4bl^QB57ASm_I)3;l4Xdai##3r(s*1##TDOjf^KtWyv0uM$1J^q zV|lzZB0gOz60D=2?-smxE&rqe<{p$MrV_F_y`G zqz)*mrEbn2`wC9dYt(y&!4LKIUe1YtMrV~ zD%7m9;p7!{<4r?;bt*o=yhD6igkG{%_3|#I8k&sd81ITbT1{i!45hD1c5J*&Ia+nAXxfL9e2k(@K7`N%HPGu`V!}R9WcXQH#J+Zu z=Y5+`ZHi7Jk8kt0k5K-h8*lFQ1o$faw8AD(fDKPUhH{Q#(*^^rrDX`ARjtFJ27K`$ zVH{RGE0eVG>c{OW6C%o+1u;5?xKnY2w%2mgTQ{j!wFU+;N`C(IL)zWee-_pFaj-_n z;$um`+L6r67csr#(16F;L_G2JX4zw4pO(b%A@(9CqF}39O+C$DSlRLL(j41q}P>whs$I!rST-)>5mMKOm^*4?6N&?~xkCtg}>U z(%nZ~R(aTR6l^0mEHT^M`T!tm8_PB}Tmou-(thvwe(Zc0rt`JCXM2&i&1T0!C2$vGBU(;&LNsNjpQ>4VDovad=z>*G)};EY_{8HM)Htw@LyToE2>|%i2sqikJtzD>+-yE8wh;23rZkzo`W-X7qSgVR_9L9&Yv${>sNHq4 zvDUbFCv>uS*1{Jeb7>~ZWcf(wN<%uIQJ#}<=@Vt@+~qyQX;0Go>QFpvWdYp9OmKH% zOZejBB1!&wJt$~R*fZ&g|FD3hT=Qf>MXg|^ZygkYVn{OJ*8g=y(Ob24hpkZ*)dd(>+?>QCSZiCU=d`Ac@N-6cWxPXXT~w&z+$^c_vjC z1t2l?=vUgRd7T-@9$Yhy%C>3w)Ld0u;wEMQtLX4639U=`4coms2z_5ZKW`{_@~q-1 z8jMqNDH#gyh;|3pHEE|S&R&nYr5BOh=T|a@xTlkR)<=9o$pg&oJT*;1V4rn|aXX#S zev2bW;>B17Q3wBlA&ubb9 z0Rmu}yK;PS6&L0tx`{)iHsoDNnxqLOUA>(UHR$mEyZSkL+$**?_zb15NVd=Rxf8xD z%nEf+JoFUAD?7#77rl}L5^08K+)~LA_q0M9kF@mKa2>O+sM<;sD>Ar{>AymG5%Q13 zNow>E5QablFZbR3h~z(f2x|hh|C=LZVqo}xaD+^(EdP@u7paujA?QG7# zz2h?ig^yy1fx(g`55$IEOF!y1?Ep993Zv;o)G1FCa)XtcX$WB^uAPxJg4 zou3avBd|Zzvw>4E02$-r`qx6B7{@Y-`g+zoxmY9fOo2A zWpDtX$ZS{pzY>TsBXj#_7G}0kt>>%w4rr z1yISSo*w}=hkMUq5Lg(-dv`O9hCl&up|)DfS=S~OK z0U&Vn&y3~~n1DRLZ)^D81AF%WTL$YN9Dm%i|9kkw9p5~lH$!G-7WX1Y2Dh^Um-Y{7 z!9~a?p`1HEJrRRvaA5eeF*iKBgm`3sU}$J%W(39jc5y`ZFAj(52T9)dTXbS|YH)RQ zGI4Hc{mLO)x?{+vlIBQ{;@sQ<#@*IN^v#ps-UKxVqv~Q`+RJHiv43{H|ADWscV%dM z&k646LQV<|YH8wD5c}pQBS0L)&mf(L?H`z)o*o&31KELhUVYVl7ZciVs3HcfB&E2 z5vKVaJ1Fzc@CJ0=2y6xq+W+J2s&5ZQ;K=0m{6Elt?7QxlX~KfqQc`O1C+ES>rLZto z7eEh;%@qKc8XVd`EDO7TWB}&=&ke-{`RXq_@FT7&Iw*wL|Lkrku=1H7{PkV_ui~!> zo5kO6?4L6TEG>e-kJ;!;250(=fZcyu*QDRHslPq{zt~5=tcO1z0!cUSuD<1Ezl%S9 zOJpxA?z=w>f78_+Se8Gi*nC?1_q|fh{ysNVKy%w0+rPdRCR?(6UId5w<}W+U8XeM_ z9{qK`XRqmzA*^!E02cVdPhu!xi(;Qc8eO;M-(H9ck+eM4t^XW$M1fr}4d8XO%2 zU$^8W;C_-vt!MT_5Qd0fL2UrC2L5pDev)7C2Y_^v->CL8;-WlhE{|_WK6+tp*5X^Qt!t2xWR| z4u1LfC$YZaCw+bMhwzIBL-yn+fIxaL52Aq8@5gBWyUN`alzI^dT`q>^M=;)hh#&vM z-uxTnhy53TW8GIUhA)=77sx-r>6^vf+4UU+YpnkP4-skiLe8&%T-}{h?GN`?4>0jN zFb7lFcEe9lF7`JKsx0v3cML=|o2P(7pawpF`7I1DwfX?hJ-+dvXE}e7^II-BkSzc& z66W`km~+Cv=;xM*tL#BOk9+!F#(teYopZgd^M{B};_$oVjgZyURfIjiy?m>Ol7IEl z7wOkj|L`sTWXCvlSNl_RN|TTDTW|uv*#UG@^T(@+Lx8R}Z*TiQ zOq1=ZJAj!*`$M?MBn2Wro=@j}{dRa=hG`A`f!M8=S~$OvgDu*D@wDqCGFfHP z>}kYh_#tu99-svG(BYX&s7oxz-)@*tIL=}R8HX|&695s5cC-!c@^I=3Sfu|Nqt2EA zH}Gf1qm@y2OJ_AWlsKna!&P&g2-_vmUs2}!zb8OtS*f#wtp*7+&v-d2I<6!5Z(D9NTij=}RToc2kYvdUt&>DFk6mdzwn%vb-R!ul#4Zla2 zYEEBnq5BsoHa3JndstnI(8Guq5QYw#kKx;P9(+gwM`KAPXQt@B##TyVIP6U3e4hal zNlW)NUdzY_e3s8&fT}HGFIf`8I)rX?o|N4z)`o+^;3CJn(cH1iX}r1*Hqqm}8|A@j zm*I!hHJt@j8NF4^K}cYxzN41XtcuDcqFpR9JKiNEA59^JAaFp!xvK`%YP(P!beiUU zdWjH(26nwe3;r+T`Ijb;pE+(H^0T}M?SVaP)1eu<{7jiDVtN(LVe)do@`FWQ)q8}p z_dQRmISRcFSHWBqq5X*7R+>A`pP#v;>atCU+x!mMZQlA_o5z#c8sh`~f%m|^@>HopYx~r~q7HG1ukGH=+F}(6s;psn^gB&@C6pX|(46wo9+nLi zP;0|1h}hrAxDI_hy29?EK^Bn^s+kGXa zC^`Iu=82Um3qw7cz) z7N={o$6hk$7QlX)i0vEc@{)zIriFMXjyG|T{(T5UBn`na7IHUABzAhT4pS&Z=3k;#bAQ%Wzeh#WC#x64Y>b0MBmvkHyQ=ZF{c z3h73p7v)gU?d&>DOKRl!=94OVp0O_VEqlkF6e-y;+}EW#>sDbSZ^q-f+LYT$3oL#c z?(D+DTHE+?<(>3JVaYtop?E#hp=R|E!N34Y<|TvBvvR04K@Qd755`1Hkzp@07W5(q zsXh4ms0>9JbBb*~l{bwvC3D;u-#!=uI}!u)`Rvb(_**m#ME%hg40wJOOpn4j=MMom$=`3B5U>l}-2^%H4QxMt~GX2R~|K(sRrH}EplS`ZRM zqCF9Rg)UP`^Qn73=;tFPlcTn4byT8TO zZLjoZ#c6F2rWai%6fIqei8dF@#P(vq*GKEdZVjyG$-36gqGTxv?p1o($)d|HuD2tE zF#VVyhW!fFLsGB@b7N|*jX`2+6Ak&0pu_^DTV9B@h|Y^N$z?uGGFYVeD9~5J?F9uP z__C|9HOkwLjQ(~nhHC&CmJater#Xru^j=Q2EwYyT&pK5^u+Ez|{M3XYC(AV{_MZ72 zUqdhcAM0za?KU{a8-F>$BBRD-iWpe>L>%*pXyT&8*V{YxF=(@zFjg1z1=+I;vGvXP+2QAL4@zX>P$9*nje8QYI*Qv(J>H-fVRhvgsXa=tCEV32oj~D~X zir+HiS)8u({+yGN8WO+EXOt263)n~88OWDA*2KlYzUM8n#6_P5Ttw@#B)jaBSkDr- zMI%6Yr3*ZiV-&do7oz`!w>!fKRGRTwjC8 zl)gmT@mJh8*%F?xg|5eX{`c@DpBZSCTy1J1I@;8z+yfORIhZ#i>8XM^u;d6-)VO6%4`mSe1@93H?XYqwJ~G2- zqZam{Br}1H&q_Z^TnW3wwEk1W;Xkpp7^z4{yy(JY@+^g0pRL?wa`$Swa1*H&PV9&< zxJ!hR(tA{sZ`5C9uXsFi zJ$4>8QM*xiXomYld&!`=`|UO6?sQasXowghZn9M04gnUncs!m2#$JvB+}PT9&d4o2 z83C$qVv2j&2BQS$G|j0=3h5q8a!dy6QZ?qL<%;*ivjZZ=vJW41S2~e1?ASQwi%_s2 ztK>#S(og?VP0epHT1vr*DjVDNpDodi7WJ0-j3k-`e8tIW`7&G~?q5!^NW-`(@gl(i zyM8GxgbWviBnK(Ubp}Tf`q();k9S#N29u#4@#Rz65~XFn6|){13(MAO^a;+8e(F4~ zQ_FOsM5(b0M{NxGV%$MfWRN!RTmZ>QDuZZjt2tjp-*v|P@+s9q0pU64qit``={HE zNi%ryhe1|C>`uJ06+h`{9THD&sU?C=ZBb!8pQP8+MxLhRW}^n}9HCTNUIPd9f-AeJ zjKPdHAA(=^w#4e7nII$z;f}*RU2d=u| zb7n?q=qr>hu7}oK8ie;ANBiw)Ow?(ex_u8#5&<7(YU(3lXCGNtbbB5N2c#X4AvRa| zazB2z4pNn$Gl7S!5N^^-Qcoo-zxPKxbQUDs(|ep^;v3o*<AhHOvM5mkQ@dyD_9% z7hyvvhNFnWqST*QMt2GWCogfuR$A?Lg}2QhWlta+E)0N2oCPWj@iG}tMueVVch3q! zL8;2yeyDA;0pf}TjbSGI`Y|xV1^JDckp1R}%64JZk= zxxI(>;^3eDxVBJ!L1tI{12T(Kf-yVBpi1xg`EgIx%wy>Ly>gKokv?{Gn5d6nc=iaU zoCkjy>x~s~Xjt2_hwF32S0p8Yuw9`_p#x$DA>9fCk(NXt9kL_`{I#iS zL&ovt6mMw!Cy{U4Ga7PyiNe442nMxvmwS>npN7g5rTP<-X=pDu4D&R3n($ZI8S5CwewnOB+6g!f zL=WH{`0M$$HCUo?YeC!XavkMevO_^xMSZDG z>tWRoeQsuu-<*kR4w^d5Q35=xgZ`|mODp$kfkPID|AQ>rqrQk%ldZjuV>FQKZ|G!Q zg=S)ys>WAMcK#?OmAfx?Q@ zR_ky^mo7-3yS!wg&O+(DafIG^lB&+i1F1S9ssDAZ))%yJyLC*YW+2J|^&&!xmFG34 zqnDV0RFrA%w0NRVdVf1EmubPXn4rL6(U`kQ4RVCl8uP)(H z=GT&m@oJmY2b-g%5o?G;;i83&M`{M6_fCBY7Hh%*THSb2$gWP>hDhMT=}PExV$BEj z?~K)F*FW-hyMal+=WhO)!RL`UjWGopfCp5@s-%+c`*(Rg^ zd&XqbafKR0q5GrvZA(GH8@X;7xWMJcv?v^;UF$XoU{)irJbM0RLAkZjr;NHjyQJED zPZ|BC^606KO>kG!WS+u4SOM_Tf{v47ysepVn68H~HpX)y*EtXoexAQz9FMFQvZ5=C z#aexRMnL_@M5{ghR|a?thvRfm$5IIvWTz^0<@tIbCuGduBgK?)QNR-yg5#q}=fuT! zVEnp+Dq5mg;TzbNO}-y%X{ll~mSFBg2h`NkRhJI*qjT3|_Sh(2!T#-`UMTOC0S>3L z#wQZfzuh4$j<0v*YvYVdpCI%ayGTER+La7ST?|>ejajc2UP|E4MUj0QHnSRrbbrwm zX2W4h-dVK$7+JDVnXg!;$@is%XfMPS&?ioZdfOtT8zDhqC@q|@w#Si9ge7wyN&x{q;I3OYT_sxI zu$txO%p1Yx7qHXZ>yCK~lJ9uX9{pq2t(3aY19W8!4(x~{a0jfIif`5^M7(by&|u1c zDxVwI->XG8X@|7MXl@gG+OVF~H#G<3QhshZc6Q)|p)y&1SajoC4}?1ir>3^VH_?a@P+UeY6H)~V>jm55x$n!+l&se9uKamKfME5r z2Ma@0#xJgNUh>Ns<|kI< z{aQ(wAvsG22G$@$+L@5qZV1A79v)o*W(?pJP(Azxvhi^`Lc0>W7L!q z*q$vf$#x_!Y%cnPsPjd7Ufjdk+-B@|O(L)(v*-5b^XB8I4CBN`D6qatY!z8hbUAk? zN9U~fF046E0$)Kjoo`B3&?3g88`8!t0jfa8-I22X@^8Dgng}==aa90i@VpM#e9K*1 zgp8q}AHvnvf5gLAnjpDn@nwnJHHYuKk+kV)8Zx@8|7GnD3FK>@V!vbQuYbt>-fan%CLZUOli7S2q_-`4=QSy%lzf~vDB@ni3(|S37`KuZ57o)H3zY= z?7BJ+YomunsGFxsMS+UmDEL;7_q9fIikYvn4DQ3;7o~&42WcU{g*)^t-b{gTB>3rV zMQ_zVYF$Qrzhtv-y68NjUlY%ek)>}cz5XdR1Se^X<)~m~;e3AP-vG@Vl=i=|exm1o z8nl(L801=14ya1&YHj={@$zD~ECQWoV%1`HwL9{V?_Bv&u9x?U({o2kHfhHATa`d7 zt~D509Vs&s@k~`dHild9bcuv4F{!j(MCzr~$0USCZCy$SKpR=5w&u>&%U-+D>c|8v z&^mqJ*9Wj1%c}2*wLGXc?6No5`bf8hcn2Qia48NDlbO-qJKBtvPlTZdWoQ5f8`gQApoS_sT+Ny;2N&rI_?_M8fG*f4 z@-R!`4)&QTm|SCUu)QdP$9H-)QJx-SV3H#{2uIIT$u%imN(kQGq&Qm%^$b=0W%!+xO$AG|Gdud==v`O+m6?dr+w-CHt5@Vj8KW5xLxEj zo+rK{Tceo}keyowP;EUB3WY7@VMQz@nxdA|$h3?j1wJ{!9Wmlj_by8QS%>1wXZOUOR1(Ixipm+J+KGg&XY@(i>P zgEJWyk6Ys*qxN&L881pO)Xpl zi~p23maxLX@}^o2RwiiJz`aWwlf!Tk&m!7^*@G>I%QO%tPWtbB$;^J^rIx8CH^oVD z_yaFTS)Y}O?cldTiBaJ3iRm^vUn=*IjWGS5q{OMq43DlS>uO;FMOFrhJQZp#W{6Ue zeR;s}$L?z!EAymWhJ0Yfq{)33;Z7R+!SE7%e^`pbhO45?Q5jQ_(wA4D+6sV9@1Kt< z%FOlugX`fcC*xxNDt7mmTI{(mpk73*v$0AsfwyPlh3+hhsCMa#svwGY2~2XaHoZ(D z{bi-4w$cWj-&I_dy%^bQsMEVv;zrKY{>A)QYn}&Sy_Vln`Zj7fh(QNEz9ju1a=2Vr zVxkurV9{9Wfq9+MpZ58&d`A0^=_knB3iM=l+0+V zN~OVVaDfGfi0kFMA+Yc9%Fo8sQ#ne@Oeq6L1ME;j@uOF(&u_q5b_!Ki)BRVKy zLOs1?Y9E-6eJ!NEj-Pyi4o9HZra~>fYEKAlS!22RmL_FJz+J!0!`g*R_=b%&ZeC4e zQ0t2sYVwPu&Tp23(pj3krz6Qf^GVX@LhOjXfXiriZJcmph}k@sZzTcaexod&*KnyU zG5TN7Y7g9Rp|LW$B~LXVK}tq$nTaVgFfA?OwZKA7`;E+HAH##W5LoUhlB~#G>qlA| zC|5&N3vqz-XZgBkxdx7>DeYY3IBxo&DmwNR5PS2pv96&K#HT;9>d8zF;L9jWb>L9D z?cYhS|M=@+m>aiaQ24m)^!|`47irnx?*a(n5TBbRJek4ouoK<#GT(iVM?#`*wQ>NH zl9IxlkkFg>YRQpM!E<-vgbPq$?E!`79&DCNe{6ccx0-2Gw8_{(e(9~56?n9JTPfYB z2?{YA6Rj~M#P&Q*`?OWJv9d*!M7_gi)N_wm#uKRJ)5Z4XRfve0oABaFzz5fp0`M1f z*{LxWdj{VNtUC1~y=YJD569L8ZjaP`SC9laJNy-C)OI9d~0|lR1HcOam1Y%|=H@2wbvK7K_x(L^V`2F>)qFj9_(q7!~mX zYa`(VEnbsn3U0&4fBph?ty~H~T+I&m*+>fTye>cV#6wMw#GPatEIXa}h>ddUtUK>} z;54i*gH$h0U4-i*UwR@74yxy{hfR1?AIWzZ$%TAn;98p1V#QXKtO?c|k#}HRO{9y! z%{AqUqOFl`N_9-e<&X<4>p`DGk46f~N8zQlQzyoOWY&&icn#tSqD@QCXQV4U!r0tn zL%2Xf#7aDWH67B-%aSC@&xK{6(Zw5g+UvX@e6H}L$bf$Df7>NS5q!(7 zIeV4w)*RwlPrL!mS?RQ)Vi_bNvRi;0Rq7 z*j9=A6y9#Tw|- zlWAxT_x5e)l(X@vpspoMZF>;WvIIWL{af}zES8$b9!sf4-+L3vfv38-yNwAL;zn0z zvTSr35NZD!Um1d!P_~wiULXV-Ia)+d`#$O8kmo@yJv>yXZ@D+I`B@dyGOWAt6DkoR zYM#4wicjPAT6@rfLIULl_(JL>Mg_|uj}&JFS)(HMQ5(nT-(A_v0M*5p+ty3E0{7{G z;a;w;VEs;PaH7L>s-*|%gcZ(VCZzy%6T;cdC=8Ib<%P60i);57b*%>?;p z6S=fcNU5C^X+wWsQlmeK@X6y0dk75)Y z+zF`wm3r~|+{*6P=F4sLKqaWu85H>G&16Oy9W1y}_c5tVLL_Q3Bpakl9C!zD6eTN} zpx@>xbCR>?2oXHmL&)RMp$~h~X*X=SN{DA(PlF{ER32-2H#*tGy0|6dqdPJH@v&H} zJ$3eC3vGGRS5UG;5XH}!T&nmad*K4qO@0hD=(9s|pt9utm|NG`=h+2%Eqk*nLwH8I z6==l_Q2Oo&g&VnOm*6>z{|Nj21q z(GlT5VE0{Nx52vka>rHb`rhT_G^FuvB}D>A!d*-$`bjLV1cQTHoni+o$@n!kLG-=p z!Zl22b1}bE__+qO$G5Hi#g z1K&ZhuzsB>>7%6pw}=#_<*!R7heM%=BSAnu1`!m0$tI1SyA~@Qrne#^r9s5;7cK>( zSM&2hZ2=qkc3f3tRNg2fQ@y5Tn=$&TbDg>CQs4TfU%_fNL?9BRRm4QT0iJDx{!hFp zZPcY1k>0LVGP?s{qg`(?EaMv&qTea(Ipc9`Ku?OSNBkV&E>(ay+d|!=fdP$_^m;8v z36lT~8285ml{e|L$=XyC4{M)OQ%IK!R<|dwD_kqdtO9o;rh$KQCEJ3lMiv3(TLRCW z0uHSJIbW)jhe*PD)96qFgrw=b^WHsZJW;l%diwMT+<9=BxqGD3_<|rj0x^a9@*MOD z2=<*h5*}Wbng&D$Rr_Nq`7vRae$#$F+=65e(a@^@bw+*Dp~0H1#DX;vvhk(LPMVTc zDb=HzOeBlR#f&&6ZOBflKi>|zvkW6{oS>W-(^4K;#K!?~BU^qU^;e5&Ri^!%Y;K&& zDZ6R67YqEJ3mYgpEv}|RQqq!~1U6Si`JOt_yeM?4a0~hyyUIaPkTyUD;-fKyzUjI= z1a?GSAj6|bSp_&cRXbAaIiAIndSFHIh`U=2(bKBD_dVErZDh+_3;%k&iDC%Ub3Ar< zuQNszrd{A*uLs`KH%B{?MJ#PxofS(tX1E0p(QH-91 z3>3_yUB7`45)-(^rzp4S&V|r9Y2V{}e)q47)LzVFMb=9=ouBNj znB5E!we9KH=km6^!U+*(aAcUn7c*fK!;qR`=oZ}>BGM$iz)KzYV?h^v@h4dM5E-b% z2{TJ;oe)|u{jp-NI;(RA16j*ZPtaso2SjPpnT;Z!Esuv2BJEEjH#?t~pTqne3QUx} zDF>S|+oOQM7S>i-4(8Hom(6o5YsN}Fvt=NImkK!7R5L4h6nxA9unCUrSUYJlR)kmV zaFu$G*^Nw8pQXy0y!jVX`A6KPKY|cUwxYQI63qq`zB5Il>_+SR?L8%+tK?6?KM_60{7V~`+w5_&_KUl>Ca@r34|i@co}6K(6GMICm{V^bvHoU+45WtxX& zF)g@+Fr)Tq44HF~@y@faJgPBkXy!~$H{5Z`*zv&}cyERCHn;Wvg~ORK1+Z zsHjJFL<_rmu%l2+ycuDK)B6Rz){;b?W24Y`*n8UE7si{PQaX7VhPGcyG>)s+j>QMZ z9l4ZI5U?A2#W)F0+2d@1yDHccG{+kL$0|d4pP!(!3?H4p{5RwBaSv?b9!Um1%wq6IN+anp)K$ z&@1_Si!DZyy~ese!S1MN^C)MHrduYF?EOOwel|3FC8i}FsZ;KSWK^@~oPItzx(ZA#7~RHSBT;0B41`Iz|VLVy6^X% zfISVh2Oo&hPm11SlGo8fODzI*>Ja(xJ=7>F9qLdmb9O5_f|jBC6SOhgqx{>)XYf?b z{Qz)W`hMA}T6d44qxxPWQaN;vao0FeySV~n?qgU$pZMB}^l?sMye0E=j@+e+@{4os zhh>18Dw{Mcy?@Pz#_Bm5D@2*17}i^se$YIKVB+1PhAXK#9k7LO)Olv@36hl8vaxv* zSTw0lh7GTGt6uYNF`Y!)dcydT`bA|`lUg@8A;{3!EnRM*D(=2`B^MY&Bs2W~o`56a2QRExob=@&-knH=N_iPdvhn zX9R3^bJpGXTqag7JRFS6C3<;3X-dRt{i&3c*7cl`j`WQ&)r zh>`h>X)qx1*LR4vS$hgS-!9q@wN-hZ!`87LXr7CqW=pgHPTwpSI|eAOh2=w8v3WK< zmAm(>c47B%C1b2rlz+nT6FymqsV&GS55GuRL9KVr>Q+Exw-D=FiKniWoOnpP=ar|l z^R++;wU9M^HZI`Ul`NDE%T%Apx0(rVVGMzDdUfu7QOMfeLjhU!PL`LIO*+nI2kI^= z!}0nJquoTGOuV`m>6$y!y>qi@9bHMV_;&WITWgoaaK(?5>uX(-ex}fB_-}Nt6)#ru zgR_Gea=RGKwgbs2lp9 zz56u1#EOkRv}=;X{UqcIxc%rx4?gIDopI1tlu=drOoGc@6L?I9LK)ff7G>IvJ=?20 zD&=SrLljE*S_q9{^Y!~pic~Z;Cgm;8?jf%Xzr$J@OAj~M{Jf?icLCj=TYN zhi$(hgTvvv2o~7xJ(+`xa_-fHMla*2b2l5({5YoQ#wZh0w&6bgm-!2Z+VEtN*gKWQ zzIW;x>Idu-2MO9bCLT#@+QD1hD#<`3XbqSd>1`U<5H~m7i0SjlS|-9SX~>PQ?o&f4 z)hAPCF_J*d7&|-r(Mofd(uD-&jps8}XWxPZxSPW{Sp>+y=FOp?G2`a9b4FL`XvZlx zArod)af|Ok4MFkf=pbl}qB+_g+dNK8Qr=?3ZDFU{o^6Q!Z*q--j0(x8Cc`BPw|_0R zX^yxdX~k4<@cNp?OoIUIlZkqb?=i7fvz3dtLCkZNa>m;L~Dv*U4#M8TpKOq^*@y+HD;zsmI(Rq41~SBa@Na*(xDqeXeo;r7t>mBkn&L00L(kxJXNLlGPLEfqXahPS?SX@TGxOW=7= z#SY#0y19Nv)$TLojg}9R2ApHhyZZMSrSBJlf4ro8k%_u?1hMk7151=wISeVko1Xtx z<=+oLpR1SdaE7~V;geF;*#hXK19_|5QNUS@H`|F7xmJ#O$oonSFcR>sh(Y4>`%lWS zSA{KuBPGW$rD}YgIeJOfpP+S6>PFL7?axhNdVNO#@Vx|hG#3``KU#X|_>1|&%fAaL zw_w-ILwJ*qJE6B7vT-a3lxW`(3^QnI|3a@9jsHoB=56))D)>=wompDW+ontXqqK!R zK$f2+8W_`8d4b3&4twbLjsN)Uo#11C!ZCb*t$*}vX~SmDTKh#^oHMe(B6~`({Zxzb zcv_A z{fx5`1Lc|*I`)l!$b{_f&@~!{6>U6v7v4*dGWFu4j8NIeSbjs}ovQIb0!H8L{=QN4 zm4SK^#sC^uIOZHbel{KcfXerHI_JW8-3SCbi zm#f!%)N$v(6QB-gSQsyWn>KbMXeUKOR{w4)dX^%!_(D*^FIgzSXycn|r`AwEr-FYm zu}=d0)Ra#;_s2-j~{lFsT~&bZ_9N z0)OB+)k*0UuV`49i-Z>3Gg`)DI!@Tlc3;l6C)b&!GP|}JI?YApr+Y=Bi;G~gFko3J zg>AcVa|eZwGp8tO%!Br-j4VjkU2QS zda9^%%!s8h-YO>3b+I{rm|nGS?w@f7%(W}gaVhD2ON8Md99AbdIMV6DLLla?=BQDv zfG!*;qlRr{1O2gEnR%4v2Ej1fnh8G~I4a{>)vvGwTBKv{H-U9ZIM4L(tc4sQtrBR$ z1(T|9^d%_=iCO{*sYYdTzD4D3#_a2)1PZy2ZW37%V7mt`u+!PSF zHvA7-BzNLol|;$v%*es|Y}uq`m!Ix?PtuAE-*BnDy~+=tE-It(jwFQTi#frAMq&y@ zeEy(-ut>CUKTI)n+b`8ZE2nVT^E4u+joycP{S~$ZqySKKTBM(ey* zbb|9RN@prUNrl|vLX|pwr#tg>x4vDO>1#QGe-bm;wvsnz+2_#JOEXbnkgJxylsSx6 zK5NRN5z%%%-5Uy{*uZ2xh>E&XoWK^h+Nc!{|HT|Ip+VV1k}xwJ0Tp&Ijq0pJHr9KKI3Qm~O?O zA)WIk7em%Ep6((N_s%{MK-e?Y2@1z_H@QmY!|A*q;8N0dZ`%3>0=@t@TkFrea18Lmlzn^HEBx{6U>j1`pPm*iQ42h0j7VtsoNClX94wUZ9@g%}WU)7I!L?kp_=u_ZQ5ro8A36 zbK)cUyN4z!N!y@r#2Wn`)vdlW7VigzUZa9-d1~wfPzCFG@Og(N-wp>0XG*V-D^CDc z*hju@uu;L)>rna+wn}$W_4i0zmZ@>9GHmh<3OS(y1s@ex{fEajsX&~Pk834*M!dBw5*QF}!$>JzjvVD{Gxy?ni&T&HF zYgesEmv8v{Ugx-7sfN#e)8rYz!n-kb$&maie?utU3%9SG>bOup7K8Wcs)~Jd&3|bA zwyifwC<7I|iwjnuT=xnjt}&veD{QmDR!=7^s(+6@*O(N_xMAU>OaRRCX(8QYUq4y^ z)m`(e$U}Qfd4ut_`iRo zyxL62vuNfr3GJj2Va&Ue%2`&48!t4+9A?rB#>wpn==rK^TjY0C$ka|2q|OY|Tin3B z3J`eAqeR&qc+eLq{Z?T2h?*$zSQA!{8Dl_4#TTrzZ!k_d*>gt)yi15}1UM2n#@{{V z=1)O_6Vuppvb@~pyg*34 zK??lI;!=~-nAS_?Zjposw?BdEWX~U%Y^14*aG4mfq%h!Um45CI4#DJd zCGfdhkGIqp69xb?(FO23aR8+Qz7`UJF5|)8*2aX`k%}C5F1+!bdr7N8pWKXsXkWoO zuk4P`KjjB&A(b(4#@8%0m(3Jp?r)-npz&91oss4w_FW`%NeWTG3(CN;Zd!J)RAzyX zL`Uoo4?kv5Uo8;b)NJT#HEF}5%F)*ohBFCp>xD2&4HM8~g##s2`9J{M4EqLxCG-C}_ZE=YV&WW!s@hC)(&sAj@$QWEF=c^uqf?yB!Yc5rA^I{bV1 z>BGW|n=7p2g{x7I9{?;&%O*fx+R;i~C&ql=_)RTl0D9a0f%@}K`B~HoRULbVc`dpQ z{5QB>+6VMP#?6yZsftb_Vsho>7I2~?K)}c?*;F=ESf3Q6FCBb{j=z~nt#b9yI=Qwr zLtJU#!_c)9d@x9%h6eyBv zYnPuCS*Ut2d0{xf?tWILB3cgm0nw{r_|`2*1v)dIcf=l}d>c`IO9ef~I#uP`Wzsqj zt~F8;`T~~lfz7LrwAd4r$ti`Gj@y=QuHkUa?Du1r$5uduge4+=!R9{Q-S1pSegtGT zjmYZ^hTT?$Cqtm=aKsoyFY}@@M{3q+F7thB(o&KtI~Ex~b|&r~AOx>m6Dl3H@C!N4 zM0@Wcp)WS4`K_DmMZ`*-Kq$m>UtXK!$RpME9{6E$xr!STFb!mkcX-qO!*)3s>tSYz zdYkttA__O7y#bf}Q2Qc+*04>hnh_1j=#Qicm5skM;fCbXOFcprIzpzTw)N5EkEH0K zlQxKxSf76H?t4l6&^2R6D-J2gc%@IZhPaV@O8F;VR=>C@+iBcLDj$m$y=>BBdh6fj zK5Q4KnRYEaf<|$TeE&PCKHTKh5xRY%_r`qsi|{3ri=Rd&c1FZaW<02%y~i;QuVCv$ zY6$otAdZ3ML-H+%+D1UcSOLNS`Bf3>ai$4?4;pz~N_-66^W#D0P6h_*yBsY(PX=}Pao3q?RjzcgQ~R3 zt3%Z8j)MTV^d_?MYQ{R5ek+=2@7`vjrYOFX>r#(`#GbBLQNPPCn4G``l1bLT8R2`` z>bhe?u+?kqiQW4csI}TZYGGb|b^bspit59Fd`G(%R@sOq6#*(oLsuPj5aMfpNe2_^ z_sXY81j8;9U~~>?(2RGNQJmd&?6sdg@d4;bl<{a2wc~Rtq14>GiE)hVqAt#rz?kzD zOFE}~>7xsNE?0uEBvOh!GiH}J&m?WyuJ})hSb-^8P~DZ`C#mu3`dyT&^7I$`qm7<0 z1X(|*NWR@-HMnMJr3jen1UKzuHk`rA{h2=O!5kPNM%$O`sDp@A`(Fr?xf^&fP2p1C z?cK6aO)Jw0`n|V^2%_z9HW^DQ@(bj{+i?*%^7%NhsmjWJ^rdey`tZpeWn&b@-km);TD7A=?v;?*GRxQ}pTH66$2s&|49>h}s-s%5-cqJ7m=_d=9^#4utPXL+80 zwe<@U0eG*E&7y+Zw}fBATOXe6+nMDT1QP^> z8~8DO>nz_!N0CkUv)x`o{7Pfh(TLlnx}2LZvRyVhAG;yAzQ`9qe7fD`qO}91nzJqUlp81)%XmNESm@cI56onW-Lbji6G_OGz*x zxb$j8(0ah?VSJ0?bdY&!5(e(EufgF27>rSbb1_kkBXpwcJzWDw4!;yuF7~_|zu;Sp zFih^bx0guMQNQ&e6{B7W!T>?%lZJ|M#$l8vRm2)&xw4YC`$KGwVG_^0;^@ZRT*?xp_$QKel97~Hy!T>Uzi}1L zqxKm=m!x-`b;qk=YCcibcH@8@?M12N7j3R?r(3}c)ygy)mLoGKJR(nYDr&CW zxJ2LaQ0>XM-RRnF0V$q`&7Z(J-AS^=EH~`jD>F3gy6rZ=d4&_5wzLpvy30A$8O^9# zb3t@}MEB*VJ){Pqbfc3KMd87Gr;sJUqoM6!e(=akIBG2=V0=V`-b`-*5~D{sy9uXc zK!FUFk&ycpr`8#nx@=9LGnA@o0sGyiI-ImE#pEY`6k&r+8+9BxfTWd)KO%!kGk$2oNuj6fT(I0BLlh%=?7NDJwqGi!`RO3K zI`UzFjGZS&UPQrjD2u2sU!R8-zUkresoiUVCpbvRUeJyDWFIk)wak9RBF6{&!8l(Q zFI6~9gC2qVE3nu-k_!;3qs*%??@frWwol7J(PG59<@(pGnOn1P-2t zJohgRO9J6li-5SlJv`R1jC#$c!FnCrYk?El4=;lt@Dyig&V|rFaxE_HHK=(49ujOF z%~nAmZysC~EZ-iCR)!CQ=%{%~x**;A3!|U#&CpU0?8amSxRHlNezu7p%dYC{w#FQE z@TK4o_{BCRsauv9sThtTq@9o!&>UfhGUhx2SwlYZ8E7SI&eDb|G4p+;>G2nOLQjcT z`2dsh5y7Jhm3Wm^F=RC;L2W~YpR#)5xGSVDsFL!7v{SHKLH974S7Kh7E2|?mKaa}` z`Fy=~Gru7=1z!n4r60s;w1Xi&pGX~tjr6k|hADM<3!Gl!fe$2MDSY*)Ydv+z$%xY! z$M`5(GKO_MY7uSHQQ-xi83^=%3U)+gOJH+YQChH^DR?!4)Ib5}qH_w|WPvqKU5Iuj zFR$i%4+0eFd6|lkW$T=>?6@gG$oU_oGK3Pd2g?JKQZI&`0zMvDSn6OmWozW^&&e8l zohLZY26#}HAw0=aL$%nBm8%{fd@OX$QrnoF03F1AzM6Yfm~aDhx*CmzaK~8I^&{Rs zJ z-m!&m%&|66!bGn@$Tm=-R`HhSq{^U3u30Jb+^nxE$j5au9roSi<$oUz*NX*f@I0hj zng0=wnC}9=7_;^{W1Q8O%_ev2@Am<^IwxE!j>Z(qK|`W4@PB0!i{5iz6B(4lwOuh% zL?61n!kF9w*EW{VPHrMIlRkf!H-O@@Lc8xr;yn;jxbmXFtEGK_CD1<(JWgsU0)btFCK#_ z7*gTHD`QfI7NLHLRh3TvwISsrdmW$g(3YOvx&WeQS{7xj-BJj5)CBD*CfnBM6FefD z9uUv#0M2#!BarL7)4Aut52?VF5zJw?Ag9z~tFFOk*yNy3qF$7b&n}_?NJ^m5AG8!8 zTUq|$V@OgBVU*1JD(EmF}OcXOGRD6fP!7UC%1N5oK?w;wzLw9D!RbY`v zCxi25lRrRUcZ@e`)u? zH!ZY@X;cS@ukXyEk;A}w52V(57>DaHY(Q6?Qny6P7ET4DfG=5r7`$$cWJygg(1k(g3ocL*(fbhJf&`aHs}kYvYU@=2nc= zIf@-Nzu^v@ljDVZXdsPRF}tX4wVqLL$&Wx#ci%0Cm1yHoFZC)xf8(}>>k~WYB>EX# zk6TnH%VeVzQ1h1MW&)y2&FH<^Qoyf6nuoC=3-ssFj{m@ZX`D;3mbZ|-{&INTu*bogEDmrFNq@>F^7*@TWiin&QMqq2$YMnEL~F` z$s}U?w=8CMm3N9G!_+Cdi;RinJ*E4)g9H9S+xIO!`A2DPH(^;YB=)&^Ye8fgBD|Jz zG2ZB@%1zJ*t`aDcVjy{PtOV!UF~kH&*&aA$?Yab26JPLxvf(2~>T(?dn1ViJV#J!K z0vU@1W(n0Yo6!T(z@Im@`KmCfW4Z2bY;1<-HKg{RrO*;i7!30G0tJ}iRMRvF=7vq; z)eYl$-i?M-{Xi3W<;!(>?NqfQ!xoH^Dqm}h~p7~h;W4&4Xcy%7cu;4C5^u}X(Hl*QjIK64Ji^sj`2MB8(Gh`AZ78}IFnDkMX z?W_0mjAmaNQ;l1z<>F_jw(=Xl;@AZb!_6}Fqg3LI1*r8N_t-tr2J{%;i!*qehw9baJ|B%7xTXUSEg{gb0XZ(kGs$ zAkTx&x-2e;CHs7)L)moD5Q{0VqXe$^wYSWowuySiaaPn_hYS)*f+$iWGkV=qwwjD= z=^t_hsvCZs@5i@Pm)KoiBb#5+Q7nM74SEDum6hoit5hZX5;McS+#FxR;G?-tmZV15mZW8Ku*Sn=K4DzQdH$N4*(mR92y%N zoQai{YeEz4Jv}p13)%bvBnaTsAGN?BqQ&92Q8J68U!-zK0N^8CD}dX7pY7UyZg_rd zYyjZc*n9p!gmK>hlE~@_oC1lU1n_{sISCb{1h05;1k%)J>88I=5c$kS;Pp=r4-7wv za0pGnTtU+^{Q#oK=G6H+Q~%*MtpKbMm=NdJ5BexBL29$Juiw?xc6N3&WNvaa2;$O) zUSt8-;~P-`k0sK_#fD+La05W=5-&MTi z%;E_>O~}#6tz+vryJ-NUCJ_O#J8SOxTvvG6+_;vo=zu1ILO~BKFX0rgw`J@mr z-|%12m=+J@ew;qy3ETqlH=^Fx0Ihpgk0Dt!7X!mm&svw!cI@iBz)Mqx2OyEWa(p!CPh{q(b zPT-l;!Pzs_zbI3AZn1m9X3#*usf2R*du>nw89A{x{yy}{re;r0&Kx}ykMQXCrVhW} zl9ctB!{2Wqjt+gG|2ViZJqVh5AR>dau=huPnW9bw-uPKy3`_xqv-On#=x;s%Xb9rQ z`{bW#*#lIs+b-mXp$$^M!5;uMN`A$7Yyi|Q{1M;-R3GezzYdju!5slKRR8{i3)Noy z=d}T75cfElL?p%@;Z&D^>pV9 z&g6KKC&rkGzHmkz&^#|{9evufo^&YH&m1m^iiws<2?9F?@p1|ukoKe#$Mo;1K2Sn8 zhM5Kv|48y|9WVmFWplG<9k%+`ro40tumvCQy=aq0aWOXe8l8{sfa5(bDR;;e!!cTl zPz#BVc4?Q5%7kDfKchyBUZIrVI+woOW=|yiDmj4RO)lfvCRZCaaXt=OjH&3DaQjN_ zNX>gx^@F$sxCYYo@@EH$!smq>R4604s?uwzX z#W4wU+ggx}#vt;yK%sWU=A^5r?RaD?&{8QTd$r28wf;TMr&`K0aYvJ>*DdPF_dGN- zJMK@Vjg0b(3`O@nR(cqrK0)=N_QI;da&`{MUKDIBXKwW!Lale1IqcPE+Iu_b zr5VMuWfS}(p5XSc9IA-yJIiF`Q2ihgpM1o4gvii=Tq|c(Ncqn^@cM4xUoTui#B~`h z*5ej8e8eMWqyWkGd!tt(K=MVSoEmz%=sen)K7cSS(*?xfp1P#B z)dU`szE!enxR6ZO<@-=cb1X!gk=nOE&%h?jg8-P3;o{ZANvH&%xMUl;b)@Qm&ATj! z$4aek|ESxvbEk2a+)9PFB#=W^l|t9g*lG&Lx}OQs6!Sy6#Dgeoq=`f6rhS$^GO{D zEL+Akb0Rghb3{Tz&A1au;3e;fiU{T$xIQubSG^dN)odvqBY1j;MjlLe(iS$k*P9-D zEjzqt+W!41wYelQdvkQp4(-1Xp2O6^HDry3t1`W3QME#CFe^7Zr=3Ur7q|V+DTZL^g9Yt_4jZ z%H4$!O$l1{(Y*`xKs`gwKoep&@_H!CT98Y@u2jyXb&Mj+5=fnZGS0Nl5_17PhTb~y zm#=t5>!SG&oG}Vl$MNBrhfw@|RW718aVYO<4UfYM!(2utXyids;6t%7k@-nxc}tTG zB+eq!V{rputyEi>g*<#LXh~0B@eu>jS13y6Zi5V=s5(>Aefw5GC{6b{_SxGIB?a15 zEBJEs$g(vhv82R{Z^aX58MY|&w)~wFv*Gp1*d^vYsIabPg4AwAi|dlN3Rp1fcWn0f zY`5u1O*jSuMn+kF@13E!hNqqXE5lI<-B8mMr$_Uo!OZK94|&z2CA;(%X}5W~V7{s_ z=~~#kfVNB#1l9U3T0iJCk0Ebc_mhQT2gtIgRX^9Gs2zVl^BI6n8}A?YG88nky>ow6 zuyJlIo&MlnzrzCr-Zae{wj|E0IT36mU1Ajx=kFr%f5U69BB?>%K9c_eXm_~oAb~x? z?Hgt1xX*iL4x>=+kwd zZ)h2Z&npaA%1CHR-83y0PFgm4RoKByO?h#`Nv{c)j>A>uF`wjDN z-0UbZej&Q9RC;>!Ue*#~wWa>K(MA~DM(6rGH@RwVZCB$JP9!`2J$NJB1C_kha_^aC zx~v-6E1>8rH)>hL`=jdC8qAeg^GGHh#-xy&{B>8iAZCdb&?)CPsP*m%v$jzat`sN- z<@I^0y#2LcnXOiO12NM%yF)?-nB2T=^i@kws^ma5eg8D)Y_1U_E zI-vF!kVDAm29(6whC==X+>r-l`DA)%b|Y7Vgojxh+tL2ar^ey)mZ&)PM4)79L+ z-kGb$|N0Z^ez<_Mgse4yqpTM=z3yzFR8&iGq14-VTsTbbE;$*-0MQ5c5n z1TB#SD%|WG_;6HB@_93lCt&UUkS<-GSJzBlKzX>lnOQDZV1v^Bm8a9{$uE&7i_!)tk&r|Rn z0n-W2*Z3kwQO*Lm009fDCeVPQZ46SriZ(KlNE%&a7i{Egsu>#m_@-*KI(w^1?-ZQ-i|fe?^jRKi_>x#38>?7b!VBZbV5}-hZk;*(}wv zmJ4ZQ^MH++v_HWo=SwM@-Ep^u5SvFGmBrHf9(T)>dd`1=FC)d)QC0^*kZa zN1WtJuD1o&@AgkVD41p9LW~XtK(F}}%KIwA-ljAqq5q{z>|Y~*+x`NR-{lTH3Vhnl zt@^(vG?7eDXOVkZ>+6wm2Ef3N=;f>84y}jRgLOTd*YQ(ll|oDu{CCXHy|Koh6mYs! z5;m|`7i!b0?gq`TZv4ue=yUrmrk7+8z@XT3wH{ z9Hm9A`t%o>UJwaQf}Z2BRJ0O_4b2IKX4kXa=e4sgWhJ7_Nv)i3i zo}nkKSmS6;E19+e%F_RmiZQRX`S^B9jC*CuNwSvD zbaMHuvoYm6H5>^*xJ&9gc1hxV6BPi%&XljSHYPaen8Y*hysbSfqS|1$mujgV_PdIv zgK{^Uq67SumFY?VSSl%hDHP$(XH0*Ez)!@fd%HgYp_6K`lMjVB=Z>DwtLL@*A(&IV z*ahf)MdskeKNFIOJ24_J^iZ>9a$Z~ZX3Szt+Q5!nFW|3>59>*o*C(UE-lZ`_8n_O6 zJ5)?o);&8VZoCYNB*kXw|!X2Bgou9ULAT+bi0Im4if>?pWKaXQ(%=W<2|( zknFFh>w-J6`JL_V%c$6k;V4fYig&vY>%VnY^$WCybZxH^gqDR?-BEp!Xqec$7`lEZ zp_zK*Iv=1D`#d-3V!$r6wZ@C5{7A7ndJ-k%+A^wI`H>g@)wzOLybx+qR94ZFZ73p026- zYv$mqnmK&VpM!nx>)LCz69x(I9aPlhYp}&zZSwlRIYq=uf473?(i6clY*(97{(_`*0VL8N^+-%<3(6hGef9rCutf}TxSrat z!jVv$7v_^^f67@huy-k~E(|?v(x){^m+g~_US_YS`YM5*mq->}&X$6ML!uq6zt;8q zQS@BV3#XH9l_r^;Wsk}K040e@=#wBQqT*;IgP-5^)k;bNY(!Y-B#sm>&6rM?RbanF z;uZ1=M<;|ywJZLlx+h$@%JvPx#C0vAmptAYqs{pWHDM=`D%aJ+wdd@Af1%ew7$fQO zQIy<qw%3 z4yqZ>8xjjb0Jc3r2O8;_8emGB=HQ=9DZoN`ncwS~MW2%&^l#=WbN5pklhX<2@c#{b zUP(GVw=lD>3sFA1p&FMjx+)nqkyHM4sJ? zBt&vmN(KVOe?*xJPO1<=c36P%WFZ?Sb7nyw8AWvc-aSy_y2!Cu8wb0O|7Ux zy|DZ0*ZWHHVIcy?`s}-9aMrmhI(}>#EGqc@0VL5vNQ(#S!@g} zzgaU?*h6*dDPCIWeJaamUEezTM&_s%aaq-ooBjpBCm#T!?q^zNm#V1=fAjujOiSV8 z?U_Ngz;_31&=wL2T~`!Al-$=4WG|qhAVwotSE4dg7vH*e9Au)-Jh%(MrW|ryI(dKK z>1}L!!se(SyQl(y>sz)W@E9$u5!-GpQG{dGrqgW6m1p?|n%i6~SaV@1pcfgLI&4<8`i%V9t`rmk1W!S@7B)9Kco zQ>;7I;2nGECxZjdTtX}d)sjvi6#CU`#$ud=EDYIf8M_(e;$^6helD$#Xx4Gt_`w24 z{XxfxR-F!<5iB}ovd}t4s~|3{$X`}_cx|0ISY(T*)_fBfV!CRMG9?%f213v1(tA^T z@TV~tODdR0s_7r2UHEfF8p2RCR~%Nf^fJvAdg$CIv^YhIFHhrDJ$(V!DxsXs+EO1F zqE_n4WMj-%1l~Bq^iFiox6Bk9kzbT`(_wO+nUo>?HguN9j7FzG-t8=Id|IBgwck@# zv4qn*SSe-PiHNgZW~oYg8l^Pn9pguaV?+g8qXrI{oz=ruA-RQ}@7w!=8ESG3@cMHKqIIPQRabH4-* zt@04~=bXmj1Er$Dunv&@)mhhbi?g323o~XeTl74ufp{*YVnsx)DJP9B7|oV-4K;e1 zW(K-{Cn#ogqaE5&wxH4EFIq)3H>;1W_LRwvO@$y!C))Yi@xt6~L?B3oz8AkL{w{|v zfk-77=QpUQM(5I)>paA+@tG+a_Q845qWX+zuil!bZ$zJ7#TT{kuUIxs(dzZ%h@0O) z!?iL_S*;sJFHgFdq9S^-58FnP57oh*zRY-=b8t76UPX-apPq)O@__E9T;pD->XP2t zpt`1Oi->FzWEiYse}?&b8M8tetD8r?va%mWh9R0U)%&7`=|pun^(Uv zzF)_~qGpr~Rc&GH>Q0Lm#n~_dl~{#@Aby5*+VBW8TxK>f1^B6H;R)w|72JXN5prU; zxm+0?c{uZU%BNH-vio4{SA-MHuD%MvI~846TZ&CIXlJ?19LNOz%}y%Sv?VY6!-I=* z=lh_fUPD~y>TWL9{$SM@(6l<5<9eC~dxec{8LV}@6B@pE zf9&n15R#n>m;>!Sqi83Yx+eiD8l2sdYSCuaIE3*d4ODGT%hQVEl9zKg)qb~1hdzMQ zAv44k&xLz|td4Q=Vszk=O05I-RyfwAMNRqS{t0R2GW^aWX>YH?@+1~K zV1yW(zuP{7X914ntTdLf%NO?c-db%f&KEWas46V*DJEYwJ0M_!0UnJxM(^Yf>b z25j$m3)N=CD3kH6;)%#NnQ7D~N21*-MoZkn9|pcF=~HMSZ{q7FHa(Acz%f^W(GYS* zeQNfFl3$-wFRLZz3t7Re>!#8|&^61TOAv#oMGb;%O8ddF%AkASBh5*DP$X`ZlvBN# z%tTLcC}YEg!OxzF>!`WDdu}7vQ<9ShqkWcUP0UIDk-%%lN=6no|Xl3s9#t{gZGb%=ar^1+!)rhTzQe1`K(F4YWy`$EO zBgCBuVhM2&NrJNV3A>e9l#jcpk=_Qq-O}SaBX!sk|6OyuS+h2zN&+pQQ4gBn1m@-N z@_KzNan_z1oF==zaK!>UFU^F}%5R$Xmdl5{R;h=B)c3i(IU4tCSlNCsevUhr(O2D= z4)PLMk@wR56zWvCWg|HEBF?+dXbYkR@Ir|~dQkZ3I29U(UB)*hl8e=_)J36YGT#c< zj{|&RxtDViGBH-QP1=!&oW2=)wQ@D*mX5GZ$COXT3f6X_}DAx_Ti0TU$ zKJ*RUSKUVyHOQR%*h#u>B(p59e7m6zBAqKpU!u}KfOSu|AS1VIi!8LH;XM7PH3L!8 z_8}>JUR!Ffp3+SZ5!$avUDSf;Nf*AE<=~*{FIPI5OHX*G`b0hAW-4SObLj%g^D=R`aGT&TA?y$GuebyDlrdJP9csZc2Y z-+8;*=ni zGGk5K#@!F;5WGb66eiaxY4&k2`P-$SaQed5R}Cu*Q8U*&`yuSU_Ly0SmL;cqij+B6p$35=mrx1s?etHBFJ%N{L3GvTCx?0vt8EUu6e-Tj$1B%b2jYi z9n#n1x{&y0m3WslXj3%>)3OENam9P5mErpUUda)qqo4WdyAi=;jab%a=+1xmnfN&U}+Ygk8Ww_;qGRP zdD*ty`UNaOQfo!ieClXDTWDA03qhU<8FW$bfIZy!z#YaVrf@#|GQ8?aiC@Mud5HF` z8_%DD+B(|qCWoB*7f|j!x#L=Z^gW(r5Q;5YwVA5T2l4^;mX^*o3u%UXrD5lTpOVQR zByLtctv-FHpeRut=B%pN6XWr``hL7^Vj~VTv5xOt3sIss8r6ChFvHzaU4{KhuO?I(S3fuMU@K83Od`n+hQaXzn zTI@?MPXe+Fb2&5PM~iZD5B=-_EG*_*?rOJ~+yOr#L3-kJc{B{CfCOn=(1?RbP8Wr9K;-kjQW_|&JuhhcDpU1GUH~m@*LphBq!e0g| zr2QzwStuN$c}OueW<-(gpC^N%%YHOL4!;}&U-Ocp{Wh+lZgo1LYlrM|CLnc}|8(3^ zA(v2Ct;h1w50K72bP{EhYaI5J{4J#vX zLZTc{5p84Hm!%}{*2?0_?W)5ei=VPI5R121rK0Y&kT&OestS$y9xV3U&BLu=TPHEb z38~7UCbLKVpx~4-u>EaiCB5Fdz#Mk(3U^*amS+nrD~-{wGjQF%74YNAn0oZ>**E513eq6~;Y*D|YTTc9hk^|Es2 zHBWoR&h$Tkn%3Y+{xEqr$lUkQW(+DKDjd^4)GF^AvEXobG>q&onmo{iHw1Q!2O=UM zOL5t02+y#vFDgJ+n|tz9Qus&hLJ;u(de^Z>-c6BpsLyp7szHR3{YAyfZ8!aUO|_VE zo9<=BSr6BL?RAZ!60oi zx?3IX?fOJV38TuNP&7I=7Mh-D`h5AyzdA6m`&_mmj-{v>^EDLHc(G;&KHl-Kho`y& z`q1m|kwlYCS9Dx)s!TMw4&8=DNWGrCIw}bKAO^EN^i7&1kYOw>&$SU+{$C#vTBR~a z-GQ_uN}<+y&9YrWew^{MppDst7(EgA$LBTetO%4dhH_upclDwVD)FFf{8q zglD}1Q~BX!9VQjiNFzy>unj9-9v)!Zt7V&COWQBZZX8Hol@b+9TXOW+7{xY?3R~tQ z6VSrHQ(XPcESv|5`fr})q^eA$t603U42IuNWotC$A~?rMt$|Ov8*3Xp0b&khe=W^k z`KW|^U={Usf?wPntNZQiNsg^wpBow+iK+}KplTTofD&Ee0aGp!1|p7<_J1a={x_$ z&+5Ep-J4{1RiB+?q4oPRLCc)v8tnL=-A(gr=DgIy%!)PVO73Y~}8|T)2pS)a; zCIhi2rRQo#HQ`b>epb&fp4?zAJ@IbJ4YJYBk%GG58td~6!Ej{a!}-pQt}eD-4GYwz z!&u$UPEsb|2_~+S)XF&isf5|9?c5QDId`FlUC{1)^?zofT-lBu&aA{PA&RtBky5Xk zhv!}PV@TH`bEHL_>E8~qxkw*BQ;cXcAa{!rKNPX@YgM&Hu?zo)-0QPcEZY@6Bred5 z?E3LKWXBXE3n{Y*hhRUZIh)5YSb~3EJ>z<4;|0T(Krt8>6>a; zCO!^H6%Y&0sW>mqxUFjAUPWnnxTxdOL)X3$UA2Tv|En*_`d@uX7B-Im)0br9VrBoI zum4-*%+ATd^}p+qME`G*bDTP)YRaFcGAZa@NeO={X$4{Q)ZJ=TswH74BqHcgMNmnl z-xWl1;FU}0N+mKBAS80pXrT&0>qwdcWDvD~_Z;PDLBdJ5 zS`0ahvY8;@u>d}xir7HZ^!Gb_mP@)e8@mMt|1>mrPT_9u1fxr-P_n^==#=gOg zk{*b752*okz{nkxKx(S0i2!_riee;9GQ4nzSR+nEuxL*byi<5h!+l9GdH^AnXr1T2 zBfqqvqoH8|8r%s**kD(6q%=^FWDcZZ{Jn@FC{o=(y*n@}7R*C86ICLk;YHBj50kcm zjsp-QO)x=LPtpUZ7DA>0!kg#;kdbC!SU=MJDnyX4@y+vu5U~68RUk#G^Zw;8^e-A@ zlrJ7EhzJu!enwYDlpBN&;$GwdHGEPkT_kysz(TzMuA!bU#t~0VAh-*77h>*DdOV2p zifmv;%MFjy{oiVDg{A-~CqKJjVehul2M)kb^(X1o#0CcY75R41jgh zLGBw8PSvVtzPZS}q5IN`??#povqj8(<=0!nL-ligg)y%33=X15LQe8*1O~3qQOLCe zL|#pm1~S0XlbmifT{yu{q+ZNBX+5P43+n9s-N2Y})?x?tdIi3UBvr0A23vC}@>1bl^mQiMIfT~}piIdXyCSI0!*Ixbo z_ShEe_sZs99I1LE9TIQzb%>bJUn4`nNk;8 z{Fi}u;g+1g|D@mTnKrDQ9)q?pN1ZYA*W^;8=vy2NA#-Qx3F(Bq{40DJ{3n(Hw#=+d zP}gak$;-uc>^9v7yx09YR%=0F3pWCM?En%+ZSxXir0SpA4WfEwo@ zd@T1kX?jEtT{4{`f!0Qps+$V{aX2NbvZ9k0_kTix5eN4 z@mBJ?lKccG{yGm!rSM*ppa<>ix>jS6yd9g%L^M??89nnjim&dkN}*Ict#P9$FApW^ zge4lzxy@&%=W7;cYcrSZH_kLlaL;4w)>m}e3h=1yf9zCh-4@IE(xZ-2;l=KZN+0XCgYqp{Q5Y{k2?3;RvYj<7Eor8<-J(AN2M)|uJZ%$xnr$th6(4)+D}Em+Rd z$_AKf^0n$oW?WiO*nL4+{hh2A9}g-mtpHm8s4w3tdz&9+@4;%xW3*JDH0!}V@7P-* zP&U@f^%7-Gnm6Wf$9yF0`LhbtJ{1b;bAS7bRgJBYWrsRq!C7USg^*@n#y|hWdR>*= zc!BXtO3zPa;dflP#3Atp@$AKkdYRXcq5r;rPPr|P0U(@5sI#s6*WMfA+PeH!A64rv zLAfEQ3@S;Os-_|tiYJ#dys)Wt!z*%|-A?H5qj2@Z$lBDFuw!@ZWOG!?H9BDHr-46k z&5N&ZWfisyk@678Zro+-`bp=A<{xp~&P03;?ytWWGg@0) zvPTNg0=nDklB@Mk zEgqsII`JMsa#|v7q;N}s58t+?U@Z)Lf`R6=VsEwDNzA>SjDv;0Z+T8emozg&dnmW0 zj~^(~)*E0S!AH3-6Ylg`cQl0Z53&=K{``7N8BN|6cGi%_FSVG3D!f2{G$zaRm0o8jzR5o17y`S zLE)Q|#6D2});aC6F45V}S7#^gmwVFm$^8q~q?5f-dXWK(`EBKoojFMcFCeY-Lg+e{ zgw*+3L-!4TB+-yJ^#Ao}8jpg)Io(A`q5F8#a%o(u>3~wRn0&rhRmh=s^QWO)>qr9> zzwlO5EwFYq#Wmg7FAOLtozTVXed7_CMsGm0?ZvG^bk|*zn!F8hgY?b zcVo!}-ie~eK;N3FP5EzC6dk25TN zq2h)utnbEZrQ)(63uR3^aeBftv{PG3#l7Q8zZy)HZ|5NZ3rqAc%-ghnlf1K0zn5ilY@; z2;p^iNM}Wr!>Kg0b#AR^%32pWNDH&R2!iC3_W}Z8FdW$IT3z&X8-7+7H-mojl-{gg zYN0mXbx&K`I``4bc1>MYj;a7#kH7j*KXY_?M_SGI$S2)pz1iBJyJY$8 zahY^}UAB7wEZXZOr%|rK@>v2JyO~7KGG8sqEF~O1p!ex!m!m9o@0*iUG`pH`pm{Ln zrx|Pyp$IuDm=*Eu*TOz`z!4*wCxLg#o#ENLD;KP?UYwP_bFVp3i8^wZ z*N3hO+`om<#yZOF>CFBB-HXXDbM9fhv7o2DCkbpKbed{rpW{AYG;C#Vba`L)%L^}V zqwe;T8n$5k$;=^?S~cyz*=j_|~l*)!aSpH_bXB3u-x65U1*8Z**=jyS(OYy-g zcHk$@Q)$KQZj}U4&)EY71F9QEpg1p%L?fyI>`LpD{YQEB_homiTMCly=g^!KThqsu zHA^6zK5ydDiPbs=TOIDPcZpxkD^<1+JC>Dk;t~h^jYC3x)GzwAQ44ZBt5wh0FdTQ< zBJGm-i&2XMHpWX&xcevRY9`RL`%NM`wky6%QhZ%|K zCMgd{dT5Qx(-TZBexH)w$=cr9p;2Umm%`{|A0Lf44K5sayJk52oj2f!NM?{f-II6O zg;1-mG_rnQ^daccsV^0WS|8)ThtmgVoR)G0gs*seu{t)FW>Cutu;jhRB6qPFO4cED?bQnB{I6l11snUplENq}L!~Me`W%PE}+7WJYDLGN{ z&y^>BuSK;jUQ@V9ws=|aegRP^!xlRU$Z)Z8VA6@w-#EW$OPDva{rrP3Wxu8WtdWkl zdPiu$ECn;oYa6%c1qY*TVA#;LY~r3!!!FgCCm`Bu_o^w6@;;ST8#lFC@Ot^Y{2>X5 zcL32shK#s?o7CA(%WWnofnB}P^K&^x0iH*16h%(Qxjx=V?etLPW`1Rdp1YA&&4ppTYC3q`{h2qXA_~jMim=SMWfS@ z8eg)oSwembWUMTYKDu&V(g|=CKAP%r?)<1aP=lZZ0Ij+DfB9ZZMn^Ua-8CcXMk_b! zor3q04=PeCR24bwp2HPa<3c$0!#fJ2w|Bwg*P_SD zONmwOwR>LF>JFjUJJwhQqi%Ln=2keQ1z>A)&P%VmvOrw!9^AsM^m)9wmyHSJmYq6& z|E3vh+O5>pp)BKsz%piPTGowoeVbyOUI}1`Qfbj#_yJRDc(h8l7Q2?_M~zNK8WZvI zrl2{_FC|fB(#NVePhZR}F#g z36X#!4q@^T)ucT0Oh!gch#SSgW3|jWg}}|tQ)UhplBq#mXa1d`pNYh#b!I{?K_&3y zWikjOqN7XUP%rh2$w$Ov^GbT(qv_CyqJX@nverS4j?ktXcYZU+37bY#sV%GL%#S*t zEDrFr`gc;S12?8k93^OoiwCa z&1hv22IbH5>_(^8khpP?1*Qmw^54jC`>7G)vg6|;V)Br(QT4urY0bNTV9!dwS0H}- z`~fOD;9RPP9z(;v%8P)YQ>+>{i3XI#?R=CiA1wE|{rl65HzTPTlewA1>aVu+i{A(` z6G`;6x@g z;R{Aw_U}5K(@;S@f-|@XSDGZ?d?t$h5~kVN5e# zl;V94PQ)z4o8rFVaJAqfH>a0${lsfV)PcAC+wHMpcKaUkIB?G>PHz8Yo=U+?gy+v*C)3~vFz-98e)Ms}} z@K)&j10{$w!<{_Qt(?%BRK8W5nbx2F@8`>(Qu71g3yWM-r^8YFo#a~{9i6_ad&j>$ z?wb15pz+4j7l@&M{k5KWTYQ|7EP3r;2uomyyEoML*x%?hjz3w6ko3F@bBxxAo*!(P zUveuK?4Gpn;80^*ZP%@uUjp0I4JFx~oW~T0qxp?Zcb5AQo^{D-v*Q+73^UEod z@p`JRp8s*b3-oSgmE}HhjGmG4b;7tsI7p1z_{4ii%MkVBBrgI%kRTi6W~!2`&TgD~ zb&_wapUQgz==(8EEexKwE19PvpLB19H!jworq!Xc=Pi|pht)j;gFyXbpP(EQdgCMU zd7k4754%pu=?)i6F8;gx*x%pW~@cBRnD=)uA(2`wjs3(y)6@3RCrzCR~ zshz(HU>bkPLty?}^~O1TI1%)EEEcEeI5luvh0j^#FO`sSl+5mwRGFolfLZnjO z8xVXsmKM;dH_9dE(Nr)1Vmm>`f=*)s``3VktrvpL#TEXf~DRGN{Esa#yLJHZQ*5rf2bzxj#nIaVs|C zrtE!QrZvbOSmCi|-V-s33NH=fs;X2JVhl+VaAT_(9bkwLPgP_IhM{I;N>q!VlfquP zmtb!XnN#>p*)tD>`SoQH6%b>LB8t*Nx}sOh<^+e64)#eENvd;JPb>N$3#o!52AZK&~UgxPsA z_Hp*E=(`RsU|bfs%+K_$sk`?kQPO|+8ta{<_W=nm>!w^KvpifuVeb)D?^qsdz^Ah-63)_lTX7kQQ zGVhv?U;r0@Y&^e+vVo?6*a*`x=q>h+(+(#Ru@|m(vEgKvaY{_3#&p#SlrLz+iSdq} zh@RH;TZkbfXJX7yzzs`H^sHQ%8uHp)7&N?W?{Cx4z!A-G3wns|H-NqCgNsC^V zvGr;{4=Ens3q(8ezA?z2yRNqyUE$%s3dWHW+1qL9Cl?)3t=z0RYI+;~vk}XB9FGCPMq9sAzP~_|+jN5D@XQ3&3^e<6re0;5|OL8*mY;oy?h>1`Vrrvuwl_!DEswq7^E zU;F4!g3Msr4GZ8@yu_H2T9rw-0~qgV=H1qFfsL!9jm?@D#RUP!`{?Un*pw0MR87sS z`Q{g$Uy}gw_8Z-sNKQ76lx;1Ka9%2Y3k_glH&$4SNfp|gPm(9jcEUCx^>~^^;ilo} z+K8xOs&nMc>&hn{e4+5H(R4CjfyrJP^<{GPF#kwg*hQov>^tnwos~|0Y_Ca8^}9!8wfKqJD9Z>QQ`h2>Pz?ckvJs(xmDdXyEi8eS z{@<~ljgvt|qb2H20s02WpWpsdyd2MCLX`mf8h`gVdhn#ar-ofB6jCr>($3()EuLRTV_>G{ zpHoqNCc#|f_~BCvK9UVpoXm6eT)NdzF~lfl(JOnjYZeJT(N*9adIEXd3_-Z8cN3V9 zdlSteyLStt5(5>(YPQ|IX1`Pv277ch!C03}eTYfcMEB#~BFgpJ8ed5pzLA z9)e6;>nf{b~cfXvBfmaqS2%39C}_FT{dD4SXsW_2du1n9EVFbYly2ZqM? z#{LBoRFaDauS6OGxv^ycZJI>^;m&pl*T608-2DN6zhmnBuUL)!zhX5uHpc%6(wK=j zxS0PlR^#MiXa4_=)mp$+Q*SWXWiyuO*jObI@JV)Zm6t%_;8;c$=yu5Uq-7+l0!v(F zJUpUVl?mVb&U(&rehjWY)@q(-dS6>#`#f|%%u7}mP7_;#wE~X~6*(ajQ*^+HYOBb> z_D@ewj!#bqjZIg8hlvaNTQp<6i7YS~;iw-8J6c#K@V+ZjnEv5YD~Aez$TgsVCSZYJ zB7wlgIy!-Qx3`CWm_xZ@fJ|q02GoJnPYO~HY} z!oW9wIzY+r1?LQ9Tqp%s@$6xpJ$n|H@IV&AwSt8l=YL=ZABQjyJkSmfojp9xSUnsM zLb?9*n3_TUGZVD|(Ivu8Y{Hv@1N7(xXSad>mNUxJiU-EiZJaQ)vd`e$+TX%~@KO^Q z${?+rEDoYugF8d;?!qsqs{&W>1Q!HgSAUufAOO1AfQTsW_YS^azVV^pzK;HN453{d zStGoI3)z8e7{S04GgJ;jeFipxPZg{8!cUPfH;Vf^v$?R0?H?@fDV$sab4q>Av|rEYE`7M&I|(oQ&Jm#%_%@!!!`L{cTue8H*Sd--74Po z&#f6&q&?rE)ac@Ox)vHzXe1WR>|rEO*95q-6LALSXJ_juVnG3M0SgkOx9RqWTzPT_ ze+LGvB)oli^9t`c4KACFJ+gzy20Zzqg^f^-D@eEWHSP0-O`p&h;@yyYJ_OI`FYd6v$-l%m7?7?$(OEy*&nmWzhQ-;q z#{r@X;hz_Vw|P=95U)VmM)PvR^rAs`wm~@!wPdtgyzF7)quA{FB~7j#i`;CzfP(^x zW_9ykE%&}e+1ros_6GWCXL=?Hm$TkI#R!76%#yd~v9mQ+Asg2xnzb2^yoKr;tLG?B6$)$ML)il;jOYZv-Rv7~Z&b_apiW-gN0# z_Z=-sEQz?uO2ql7aPiDuGB+iZN%B4!X!JaYW%9|ah_&23;KJ^jJ?yJ3bvYgivYA*C zqM8$v9f?tB#YGvo=qUKnR;Y`BrUcB?bUri%FkD*QjaAe^#7m?>9K7W@mF&f>7IRGC z#oA*BoJ!!$O1Ee9K1YxQ?iY43j#9~8dC<_Dkng5-OA1X6W7r7)(tN9>sP<;`wH)Fi zm`suoS^{VsT^v6S2SU{)DtYg4Sf2i@43au*C^?;g0 zJZgS{`t+)$EB(6ylhsB!N*t!7vGHd?X=0N_?N?cI2NxqV70q}QJr6;^OOC4VN2#zn zi{1uNvG$CH60v#AG8dc|>k!s({Bavu3_&%zE$Th6h}f6Ykrpo?i;Lj=uy(qeAy=ka z$higy+2ArldKccW229nKTCJVt7)9Kx^(g&IAJ_9qU@v>o!Vg^$7%XuRk)&VJ}th5F*6e zc35?}bDRzyxLR^V>&*7`&4p1qIJHSV`ES-0k8l{dP(Q!HD(yuK6#TAl5YR~@Z1OTu zS4ST5AZFzDA&ngvz?PyWW9A!QX2KF1{;Y=afQQp_?Ztpm?bJp$1e~5 zp6s9}6RLS+9yP>+m^-PK*Rw-RallCGry;pp$Wn$#%I5^wdlFbUT`DjfB&Lku_XuHd zB#l43={Zk07nEcw;c2o2LX0CUr>ltRmIVGtl4ryf0zVjw37&3BMEfyEO&ZeWL0p5? ztTJGFkL%QPDp8p_w!_Pl2Leg6DzV^L4`e6Xy|0xv9+~%wEPkLStS)G1 zltM!h9&2`ip-m(6vi zj_8$Qt2C6^aM571b~oIIKKVYvv1vc2g_akuGc)BPRll&TQ%}fbUK`0Otp;4ERf`khd==&21RFKpmP4& zk|*3(UHM_|*l3M^F}n9&cUBc?k!phF4*OdVTBDv3mnQji}H zebBdlZ0!$f=6CS50En>Y%Jblk@88;A=6G&I7LapD@d8M<&4Q2VN}~+7Nm(xbT44eH z8?t|48D6S=gg&VvVG-`>@!d{7)&Uxyc<*rlp;%xORGhZBI6yqoho6Q~|TMB5uO?4HpWU zM|2W(v$t@maBbEq7H|2OcEEo@09WF(VuxGj?xyIEfIac7S!;7evlS9A6F6#W>9VM+Js zAqhh)wBDb%9OLUlOjmtdi1rRUEk9RCx$#dC+Fy_T#&>f3+CW)2FT|&+4$t&IMCw0Ykpg`x{j$v_U!vr0g z=xTpjYkfTdu=IHTjkC-y$aXfD6mE|ieofR{)ToH&WKSznGCHiG_XuveZjbns$SvHz zQ+&I5PfhA?k;Z610hepS0m*YwIm-;YDi4C3owGXp65jwjBhUBjM{<9^=Dws$MehdQ zJ6};gOb)q+1o<&AUqi7*?2JiJ?|(4Dn!y$j?c`dyULJ?+co+$*K5E8K(&Mv-&e1Ll zd3|9nVCVX9{!vnqn;)ILndK%{@GAK489>o7nN9xZt}G%wZ!S2w+{C%WcQDC;Y!YA$n}|H)AD!*#+Op6k!O zq%2WDKlz3}Rhu}%3WleY)3xlUOd81y>_B_d%-1_G4_knw0H0`=rIle&xMMwjO2jr{ zYxm;HNzk5J+4eDJGvJ;1G#7s&>ew2x_iNpY>2kU17lyEM?CI4*iI$v}hK+cF0}m~N zD^_HrvVtaUPA*)|gdVa1Vg{As=HD52l0!>II7!~xIhVRLJt3E30JqXfSeh)JL^_DOHZh79502h)zEHu|rr@EJ1_Sv1e zWTeJaJpEj&QMfZ$xZ<++?mU&pZ0-?>u#1_o9I*ey4dK*LGK7uz-sjs!G+UnNt|AoG z4hT=}$|cn9fFY;D%@3du1rTl8&q$D0vES-Nh!4Ly0=_ZAetLY1Tof-b%BOeKzin>{_SouQ2d){QGg z^Kno8wBryFFL@qonTmw(m0A~+FBEH8@ng@4qNf>J<>W$Vi8Zp^RnoT6^X;P zZ-yP-3RWwQg2Nu86V`)>xNh(YTU!p_A*4Q-1be{f`J`Anr&%IxzfX~D{|OA#pwy>4 zd;B_u<+-;8F;Amngfih0OPMpuyTvk)Biki;4r||jVx{R#Q1YHvP>)c`hhdNsZXdUw zvXt2UtEU`gpS9>ci73axLRUDbpmKpNu~~I(d2=p0s>@A#7|f*7&DTHmT6OE-jz*}y z2%2G5SzO;@{Vh?5noeBHwdl4`z1Zn0^M)H*FB4_55tZm^uB-+B)Q&g#V>Lz%ih6D;LmAquH%ONAS(y_SGUPNBERxbV;nnWcE}& z==uFQ>cVj7c>rwjb`^_(&M77xx4%WU%ED0?`8qv$HGbgkeDnO<70BLirTy-$9 z1~$EEjBjo|lhBhv1`iV_SO;%D9gxBe>qKzmOE9s0n3@-NE$3h@GL}_NiSsw zv+U-sDvfkJ)jjCxih14!M97@jdcoJ_U`TFrSG95bS4a=uKRy2-!VW<|PIbq2)@mxb z&}Wm9t8Wp*L63}x8~P`(*|W@|-Wr9DlCeN6aA?PV`z5BJ-#FnqLtnS%nJg>Zad22y zSn=iZr7$lWH8N9%5<$qf-684$hy&5UoU{J;rYz_qp(LjEgW$iNr!*IfWSW=?@A~;( zP0S!K_EpW@oM!Wo6l=8>KXBoVi!Urx8td#IW4Xg^rPDT8#T_!Kwf2*9WDA(djmi{z zR(BXzRT{aP)E*)*wOR6f6V*ntwKW6$oWFrJpyfxHk ziXb{OXN8g-Q%)F`5;xGVXOIjj#Z2h!P0veUdW4ou8UE|0X-=?A+=$~XOlCg zB`UzRdxi>UqJZPt=o+-25o)9+vcUepCjpS8s9I)XFZG^5&1_c@iBqP&v z0-CK^IPDDC%dNtsg$%9cl_B-F8IDtv?Ar-Zv~ha>n8)^68^zzqUxwE#gI;Q_CYaXI zmpR4GFdhs^!()KR0D7LAzkouqn$L}hCxx%xV-AKYf|B)+L}`^?A%4@mUnTY8NZWZW ztI}fmVUwdTa6I#D@H{+#_tpv(hr55A<1$-5a4=c4FH}FwSr@=VN=V^14{2rCRp8H0 zu8ag5nQSal>BjU9tC_=V+FR&3ZRIzSsAS2w(7a{GgrgAXxwCa-?NNt@@0YZGKg%g( zw_z5e)3y{Qu~29xkB`tQBZXGOeOFc!jJy+qifUpAv*LMm=t|@{*@o%gy(sd@KF2 zrQH476Nu7JLy}f5-FpMR7C?1Hb#{Ol>bb&Y3Z03^r`yb7CQbJ?{5|Mvvm<_TZdOYc zLSjz96k3To(8tj#Go8=XM%pJ}sH3otpKn(shkbcRDU<|ed9}9+yV?Zks}^?fL=}v~ zUR7m{>s=XkKQW<76Mo?Bn)20v+m>Ro)=_{?rHK`aNxD|=z1TQ8kYEe&d4(ga$8~)z z18APC4HbV8D@oqdZdc9D)960zRPTlgLCaI8zDds*QBn%ikGeTX2w7lS3;A%)shO`T zJ}j!M+w(de8&ejgCA7d&bFpKOqD5ui0kldu^`dMKe06=oYLI1hR@g7MkXkU-o82yd zzW?#QG0}8(O5p#|wwfzs^*g=^mmTs8I(Gt#wS^5+F1~yFWlyCmP{Ru7#;GpcSxRG+PeI7YHC*T%GoZo0=$Qk;Sj&or2A~wN7&jloO zA6gCKe`bG-#Zo8fntS;&w$9%$3GxD1H9KRsT0ppu_%8UemDc0__bSk|rQPR!dOt_5 zb6Sw#mF3&!!sn^lkkg>Fg|b(n(oEx}tVFkGXXQEwiwivTMZ_9YnD@1<>9p?Tvl?86 z@gyKq3rh|Qvvbu%p{RUQMe@}%&S?PO*l?19;tl%lM}9udgz|mNVdQ|s&1}hkFsv-X zO;mG06DjL)G8jZIy2d+Yb)ccFY-eVre&uLC!bB499d_$`N$9{qD1YVWJ(Y(`h0 zx(i@5%m}NCyA8wf8Ax7h>tz^iu*HNo7~XR<>qo}>RC3lVZo$ULB&NDRf3GNp`lIcM^LAu|>x55&Sl48Z{y zVBK#zoPXrISJLmaP+(wm!yMjNE$qQZOsT0BK(+(&Il6d3Sd=?`5f=VfzCJ2_INwkM zHYc=cuV9;K$i*}Z&#agDrd}Ijg|=#2LvIna=NL6QQeuxu=qj4iTkIBCqcPGutLnEA zN_ILjp!q8Qx)FI!P`TdV@@JgwbsvN8y9qwP|eZ2>dCX-i}|+y9|I_y>;1K`R}t&#eC4 zuyd;js2d2d1p9(==B|otqlt?(SzigCz8FdB0F# z;qJC>I6kZ@7iVlHXXtfH3wB*HrR$6d_=xC)vvZ$NH%D8qyZIXUyH3xm;f$X%*rShO zg@+-+M^hB#Y2RaMLRrr?`h~Z8Hil+T#xJhkVNWIV488Gi%#9t~+_%GQ&N^@rS!cNR zTpfpvB`cC{cfrD<^#;v~3y_BhENZ>YEnwB>K=jd0Q`d`r0fzMGYwN6(<#B2^m7cHX zB{IcH{3qqNljzWWK%i{M>o~@SbG~nho{=!7B&#!Vf0hQicK0<}^SI~djhpdNn%58G z!=Y<5Xis=|sWInLJN#U8`NZ(%@CTrNt@?FZNr8x%F?IFxJxcPwdB2+fm-#m;-=KBG z<*nKcQ@b=E#QM{+tiW@Ks>_Bm@(`>?OXIlq6^MCfmYS6`=G1P8USeO?aLs+I(lF)i zuur?~`uE<|?x;1kLK_rG*gL0}|BdoiqV*4I_{}yh)=x>rF-dT5wy!+e1{`=S4 zJVakd@3*M)HpsO^H~u~h=M;vl!~D}|CqV_W=e+6E)hyX69_tFJo4rV{&)FQ|?q3<| zK-Q!o%c(cCrc?^gZqK9Z{S+$JV_T_YV*J`p+>ypjxB+T5CwJP^O`jT6TcQFhXU z+gi9_kuk@k?ZkXn0+zJw@n}Q4o3aT^{S;KLiDGY9qN8v2~^xT?x^$?g8F( zQG|p+xl4f+P1>+r1C4B4P7jBqli4HIuPQ!&;rBujNF163xap2Z7ph~^m?TPG=Ar;(5v zvjV>A6L)e&5^KAV3T+l>L`?zM<3y75*NqBD zJJ;h6M|mN9?!>QQ!vIrTErCX96~8@Pois^5u8m_~lMk%p^~ZrqTo~SD8E&D*)7|LQ3vBNeBv@fPMWi>6S&|I|tm?5(f9^AkJNIc#v`z43c&k3ZFLSJn%8Wk1M`eD3 zVM=x?EIz3+r*I-4_r2-FyjgkL)s+qaH_4^i$bE4YJ+MP=en-ZFGJio^F)bNgTzyVb z=F%5Amm|HM^4fm*Xhy*T{^*g(%j8Q4DM9ooS|B*7rcN;7rj-Em{o!~aTPlawk?}>+ zYf^L>WGBwu6z%j~1`f~%Bf7u-wDw7#3|ZA zW+y5cb%*O?JdR>cpn{iz?~AyxKBu@wPd9U`h&!~It5$)uW));|>0$jcd9LOLkF$0R zJJZbch*qprHOcdK-?@z`%_U`bw{8cT<@XZP&v5vyL-nK?I$S2IrmP8yh?kMa))5sC zbZ?NZ%3hu-@ZrcJup%#Du<&8J%r3;6Wp#AWbUEDr9CgbM?J|^S)g^$@A830*7&mYuL8!da=aXR3CkFd{lPmj(-!FFT=ofiG9f|rPuZ=c08O~ zfX>o^>-wxVy*!l^Yc-avA*rLZYm{a?xs!vRy3jR-usgNi<`Ulv(dsiHb*k=`fm!~k zvGp*L6k7=5+PuWjCtz-)AL>Gjo}yEc>!=L2P3B$UgT@5o7kiY${_C-4pz9T_8_26o zUxX2Ma94PM_31Q%n;;1y?ORd42+dr`C)tB`NbOCOSTJMw*Hj5MHUaM-({FOQ)QU-? z3)oK-7^dA{LXl1l+1xsI(nssUDn@*rv1nyQq8dTj2-L2 z(l(j^>*MT=1|U(xr&5{y?K$dJQ1N&%Zq-?{%YphEwX9=RCRtOj%b%_y@%8w$3enli z84b_FRcfk!0N;kvW;*RH4cmk-*Z0wo#71`(K80o0PIw0;(V5HJt?kJ&Y4z1j8X&Oaz)xm}h=~8MnWQXEPJ8y4&Uw61GxGdU_(e*uISl_Z+?W zbV-ih{Nve?(o0hJ7g0~UkEcf8x7(;#!6L?I^WIZc-XjsGH`eADxqS_enOqw}!mni5 zZ3{g5Pbg(atm~;i!_#ZYB!6obM(CXL6b~!rLU%aYJpei-{LrVxPgu9?iGB2;6f zSTdlU|FjqnKi8v=Jqsy1)}I38qbI)19q1qKz;_vWiiFx`_h7M;r{4}6Xf9SV2TFYT zj=~wFzLwOAaGZ3-Pgyq!^>&Q!|<6cMRQKICl1%Iv3pScn{F;`!ntM0NRI`#Kc z)hg3K)=|Xri2mqqxS;VHFNyBEwyyhU9U&`ZQaZew5tS%%DqU9%D9cTfij+v6qQVIm zx@c8^3RZbW61jf{CB$A>nh}+4x18B*7zOzT_5hOm_&M7Uhm0?2Z>J6n|DI1v{Bk7f@rkY`!ZVkA#36mOEH(h0)^I34qZWi`Xc zvE0JcZVXI?6FHWexZS)SY`M~AnD{z_u|40$*E7lp(rD>O%W;NHie&6eL&hM(W_u7|Bc!k=H9q991wVlj(ZK%Nsq{7y1uw=3{j7X(Ckna-2@ zZP)3~ujWomgWBVbX>K}q<7rw}bzS2b=HrA=+^ZZ4Dc}$s5`YvSax?UlfB*mj1q}cw z1Y&(7MxTQH*)H3E2jubqQizD;H>ltoutlhVfs_j+vphx^fVq1gAf6t8gbENT2>=LC zkU#%@Pq;V?gnwcd8w}7K8h|+-qMRJCi)e9X4*2vuSn%%Yjo2T08WtcTD(d0K6@0u) zkVjvi3xt0Q?gYd|y$tE-ubLr>YEKaY!LF3w|G1 zhyxHNKUW^!jsoPq%Gf_Phx%F00tn<9fS5e}Ob<+`7CMhzn~M1e+@SBJ*xTzJplgx%pQOMaK3Nw=iwI>BJ};n8XSyRXXhHhmp+)d9}p}I z$XTTsM821T*B=+?G0GhLO}Oyqru#jXfEAICQZ@WBz8 zS+swU>W_I9M@Zfudl7cu zpB5Mk#L2&q4+4n$rn>+jISiedVxJH8PaDyn9{>U-8faYz*8n2WPfTn`KCEx`{MW1B z8!(l>`I{eLzizEBw|X8bhyc;P&u_`EO@({AZ~aEwz`l`){iz8uUDZ0UVs6ACPMI->!3bS?u8{ETOcBH^L%eAqW%f-dP8I+L{Rll z_w`T2{BKx2NPr;z^Gc8bpTB5K{PF++!UZ}2=U71iaN_WRdNHVIKoEsJ@x1osz?W$k zY*0wR5P~~B`n#_76hJ%keGUZt^QfWskKgG2udXkFMf$eq&b|hJf&(B?PU7{(!B1D=uUA+eVEDf^3fU_yv*{#Hz2=B0W8Y5zfT_ewn>jpGv?{9w69R}6b$c1}WSjLVM-7?VEOP-WksL3*=$oPrsI;+|s;4P^}2 z#^e*9(j={^>#5llrA};dBb^~`x;A~^yB8fsguVdwpeTBYjmGr(hbK>*@br1arO&txqw05J?JcWFs67t2`!4`*mRl+HoeiE6_sE-t?82fbB0i)BG&u zh@~X!rd#OKu#W8UL!hFkp7EIeSE5a+2m9qr8yo(CsRk336_A09zTs~cBN-@xt}4$n+lUo zLQ-nSNn_~ItVF@}pv7JNm-9;i@^0>N_?(*fYd~aiGm86jOV?JDbj>3R8ggIF^29}x zZ7t=@&LaPoq#pw=!PW(Js#H*B=tTVi#TOl9Lm`RJGwl}smwNAUR(L?NC*4cC351ul z%7WT$@MIWVF;vJG!0 zg+$g!*ZW9(Ydm;QZ;QWR@ z3g*lOqikk{m%UR9-5E5^1mvk&xCu{90~}k~Rx71T)HZRpUy_J<$_2a(NqzZlxF^^5 z$0qEx32*}*d%3y7Saof@Y#NY`qt@$$A~yjk@`7u0&IuTZjn{7#z7cs>kut=n#4VTa zex%m8--jbaQh_Oj?xt2Vt=dR~Q6uABpm?2A7}GYe+N<+=C(|vRXiF!}RgICRP1WV3E;^+o< zpX>2*&2u&V#6^QQY6E2i;YEPO51sZt#lHQnjpTHFU|wj5(1_=SGYnv+p;1BZb1ZnQ z=yc@f*#!jvQnQt^sp49t;Wu*TPbmXIBNSA*(m?a}|=?l7IX3)R7+9l-q;HM+M z&h8Wai8};`9d+(Ub=ArmOjo8au?dKYPrOECKB~hhbjfCKF}k;B>A6VIb`~RE z@e^7N3qMdOK}-LPZo0+_Cfs^N`0*VWNP6(bb2K(@l~e!l~*f*G_`P+~YNI$Dvx-VonU^X* zeh<4D;<%oeYQ6C8z8TlI>3Zy%Ybf~6&;UbXUd6ZFw0t`_wFx>PXWhVNqqZM5Bc>RW z4KDVjkixISG?}QZp=#})?#Fu6XgNz^Pwirr(TZJB11}k0wJ%e>?A2gn+fz-)YC_P~ zB?8hP+4kUdh&SmEN5x7P6isYSZ}^mXem6C*wWTJABfL}4>=JIc${`_5{dx`!&Mu!PmU#hs(G^2}BM zsN%~cJdx8zvD=k~S~d(Bzbsf(Tbhw*(Mm7+^(8O*4ioI0NPc8eSp1v5f9;DiSo71X zqFTv&SRaFD_3nLijtc*hRpUg2^`)X0X-xpz@G4*3&vZV*EYjfdBXe=!h`J=gs?Jh zZ^^BlZ%c{dpoX0%&LzpA?KYe4A-(nOw3?vQv?KjnkMdm3+)l&v=mMD-c6wIkuH7^8 zR{fCxTh&b~Ev=PbR zv&G>Ws&}S!zF@v!j{0~lV3=1L*LxJ^-lmNg&jRX@-mh)_+|+Ozq$x;~lrDyT=ew)j zg`_ZeGa6pKP6-dH9%YlXIccqX!8U*qbiuZ>tNlt!4}WmzS*joT4w)W8Zpa`T(|h>a zJfm)|Y#*%P4GCy*ir%TL96ikG2T@BgY{U=nV=<|s$56B55@#XFT~E!lvS;)bmtMN! zzSF4x`d{e27DmY}`1Or9p+AogUFjU-%dS+ZeTmm3lL4iz?Ftn)YDu1z8e@+fw{sbx z5d_0-s1E@NtPkylMv$M{yC*fQ{mQ$^**%+3> zxMxS^=1cT^9&6dxqtd`2>D8yQmk!i48G78h>_`fe$NcQR zTJF;f=Icq6u66UCsrT(e<1pjLke1z6T<{HS%if^{ZD*iQo$`k7DU&eDO&l}`YIF1s zri2w$i3i_q1!HCkSZ&3OEL>7)jptRrpwkuYoV{2FKY-UtNv(+DeFgT@}5g=EP8?t z)~&CCg;*ZH!yhD>WLa-H8y4+zX7h%K?haLCmgrX|N&7;*{a2aCC{bYm?>{_na%zXv zqU15o01JYQC$N8E0*$g>Lkr9p6fK;qZ;ux$I2%=8W_;zh~>#C)=A zLUI-Vj(X%B*wC_Mr;&bMOXjk&M>shuU&yW6m`uVMz9hx=*tJ%Yh-X7)jG3XnP0&+f z1&dv3@%Jw4#Sj=qz}uJBvz#XCX?gcId=@#_FBLDzy<@cFdPd#hAvod`w$8~Ul^-?i zI$Aw31=k1SgZK3C?HUfP-r>u=U1D?oN;?O3w;%$Oxz?vyjU z{cWC%RCE88Ks)dk@CAJ}&8sjG`VZ+TTDDTdlP|wm4}3@-c%Dl-xJapk z7b#2)HqDNS&ruU(Y&P|{bf0R{-9gg*HB~kO9g)jfsqkX!T{W~?#EOVMsy54IxCxF> z7zdzgU!=LyLjVi4s`eBYLD#-(Inp}0{1&sf-&D zDPpf~ErS7N`V4}@RL_t%U4(x?~bnj3r)Xo(qH`fWV&nlsg2T~FO6$fguxA71D;QT`!i!M&r)ksD=_V)bxn zx?Pd*^|AXegzRRA!Q1mm<3SM1n2>KBEO5IEDqlioDX=;LE;}jvXu!PzL>4N zy77+LYP4(gAnnzb&xTH`kRTy?nw4?}So%qGm$Av+8cg%b8qBBqyY11m6eS}YODOw! zHu1679k4^dofD}I*X#2xl>V;%39YWipg_W*Ib8dDh(=WIG|1oske-f-m|!b*N^5+u zk$E*2*e$s$nxb>dzfbi7YBJcC}`c1_w8JsQ}$*ll?-ik)H)fCeJMJLx! zp7_(|DROxv!Sk-O+%yBR!!#4E-}Z|@r-JsiNrN%v+a(XOQi8~0>>KA*LU>)CO2cWl zi?m9(4;tHQTe(}dJz<4@OGP9e5?&!j*Ob-2F(<2{3uY+a5B+E(&`{jHqc2;}BF+iyMTJ<@zO88NX<}$^AdRWZc6vDE zGl<9VFq(=oRwJAzMr7$pN-GX@1R}%Q<*I(gyGHP%DpDTd18NssXzm9ZHy2*Y#i`^a zg%Yg|m@W2R&*K%xvQZ)DAU8%ZYP~5E_>zJgmqJll|&wGpGmY5eBMPH4}8Pw|v6^EpU@R+KIN+i+rIt{ZBv~ zw3WGHO_4+31=tPmoqD{u>xu9ODeWc3V9ft{qM{ZlOwE1OhLN5tJWeGev>trN?L%); ze%VeXL~5c2pN>r}_oVT^<8nr%<5V)$9BrKtcno9Km1R>fV_>Y4n@7klh@XE)eSO?2 zHpd?!xWkNGkibTwO;mg=rSEY1Gy{+i(7hrbUvpCK1JB8oyfof7q}Gn>d$sO zs<16u0P0?U#wjnIS_v&*H4~bpB$71H({62&h2YYPvg1;O8YEWj+fID%i-OqWC)0RL zt<n?j*khTy}8oDlA7u3$%8FL4NhxqBGTc;egTkZ_KEcT(Ej5pIACP$K-6g?#!YdHr2ZK{3xHxCE)yl8D&ep zQLaZO_y6JaM%0wGolF**$}T><6N0W!;p%36!83GDvgKd}`Z+yBllwNbWR8W}P?~cM;;)gVb_3%Qs;+Fb1%YN}dVWlO>Yv#M%5Vz_l zuX`PDs-?wm|lnLp@BAbm5UhUVFEU3lTM! zT!1HTZqAx$jf=m;LvdbFOyi>Hw9+%ahMSHW;B-oCTdr4fo`(?c`Fo)!TSxYLvTE*$ zyz5r7>zyZZ^rw$uvHDc=$&MCtiin{Qi4c`81FPe)L3dTWU+J#Vm7uDZPr&`t!%P7e z+?^Hr#bpd#?rB~|u#*1DJI`0bN6P(X{5NaPNLo)aJl?i*i(*)#aNY~9pLmJTR7ffD z$9ax2!T@c0sBYvBwuiyUot=)I)uc$8^{!yW1hjf;R0fSJ#?u|kTDIR*fwW3-D)W9X z8Lw8?(IJ+$nYfSUpp$rO{GRt25uSIT$qfz@H=juJ7|@Ed1X3!cp@H6=R;!Luclm~; zj%d1eh0hs=_0yf_=Q-yJ(fC5bjO0j93QAoOFn$u5metyjH$U02bHgS3oKN@d3Xdv$ zXpdO0Q6*dR-K`7trKUhH{dFSSPH)(ZZ1w7WiYR}V$G%Cpo{W?}`2qj#GDvao@5F@vjw$Z7`&46=;u#7Vugmj-UV11MjG* z6`(v_Dwbt-#%q%Hqf0T=6OqqiJn5xbxUtg;-gC5SaXPxh`@cN1EHekj$K`JK__6!@ zU5O4ycoNt@*wq717Y#|6fQ^+)&4Xx=3znvB7$4smZ0LRo8I}F`4aJc0*LR>?Q}o#} z%B9ePbva*k><4Q6rQinKa!IGMJu|e=m#DWxi;n198YW+5%|joNbpr69Q_;p9ohaI8 zsacI)zi;bfUp=k$w7Hd*7uvLq=#$4|=>HbZZew*r#KjR8!qxRK9*;b}AnHvn-WFr~ zM)&0BuHMsKGirHgZei~Mo&%vxsdG{-9IU0(I!<3h&K~U-D*fuDrI@9ms0&B(_utGup z!drN;hx`wOl!5+#BBUHF?EeoTWoKplKd1jU@|&HW`Tw7gwt^}z-C*;|h6gJKic{iC zUf3i%yQL9OHo%Hn*T*2m!$GDX#K+SnBqJe^ro_Vm0s>kj%Yynb?V|79{rSz_{U>~M z-ErA@d(wVmV%K>250Ms7Xr)6zMVy2|_>%&N*bozk699lgL;4Q{8fRyVLR2$d<8xt_ zF~GzKeI4| za$%eGBbbEw8}$4s1xifBAq2OFu1cd^!2Qdx8g%0m4ZYRiNVD&cuhhA#>*HXiLaH(Ip z0pR~{JQ@ygyyxiG=!Ygq;77W(zEPm9Z4QOpA%#y$j~gGvKfjh+f$FCgd#w#jZJ z-|meKj~(0;Lqof-cE#!++0CmTlyyJnjlsn{h#H01Pmt|53-PG|<~BVkUQRSQ0vbAO zIPUve7R*0%ZGUzP`{~pnE1pcgc!m!mMo@5{XT-mwBhVHP!RD6UxtNEM9dSKv3O@uy zq))FdEe_$|0Z32B1wJnaL~N(FPDg%RhRGI`7e_sb>VGuq|3SD0UClWQ6ZALQ{v}4-4d6sC>x%&J{dBe`m&zU@pdfB` z*L1I^2D7-Lu*|r;|HJgyCkqOijJ`hv5BLrm5)6naV8E}e4RZfwhsuLJoJZSlmT@W0 z4u$Zy^Y1qK*JAxB4ygUN8l3HZPm_}(+tvVJEn}LdpGco;n{NECY2B~n`_JOrEak6i z`mc=`)y?co&$o6D@DIN)26}MgOAbV~?N)so&b-Jz%l~^%9(nz4WF=Gp@s{@QN@Z+w z-&Fw5;_mxSfQX(7f(}C&KA^?vy(DJuREzIz8Z#jHqJTa;j}E#Y9x&){EKG}*i+uYk zU_jzA4LWoy=Re3xd?P=fCq+g<1ht;Oz`AR!pPdLIA`E~JVT)ZrU#CX|#t$Tpf|drf zU-|^RPrd$PIrw0aM~IU*`(JVK8#QbX1NL82-#s)KFn-~`yPZ$;h#)Q4Z0fE2B{)QhBxPb2)Tg_@ScCd!r@;)ScmhHKG0@|UkXaKey9gI)6cRyjvvslq1-AlbY2-$(Z zeLX#B`gHW^SZi+-6LQQ$b<9O~b=tY(iN1Mi3hCVj+zu(jM~#dDBMClng-dfW3i8XK z@2rE7@*=skRS;;uWzL;aU@ncBcixD^eC#?)lEAImr;k^?L|)w?^A2cQ%U)j_Q<~u5 z%_W;<%m9C=kNB<0ah` zD&=u_Shfl${niuDzF|ET4Gz!6T^b6;HHL?1czlMhU<;R&Z51ar8x=B-4v}PqrA~Z| z!|&}v>AxF9wRu}3ae_3OQn5RQTzcYd^DbAKIh`~b5(J&MlE)@Zy$7d(4}lFG;-Hr- zLhjeo256vUWKAfNk{2iQ0jxwZPwg1R((g&cpEs0islB_iiN-9$W5yL9+rHgShZ;BM zYgIn}t^X^viY zSwAvs)~9V2wE}ep`fNf{oy)~NJ?cJi&Nlw5 zL;X8c@DoWJ+njvZyZpsw`3Z!Wy086ew)0C8Eq7~U$yLf2vO{Xh`K`>D-V+;1CW*SX zxDsAtSRBio#sC>*OP)eNbbXg*tymRPC1Qx368S}NYp)&C{Lraw(2qB9KWvE@N1a)V z*!yc{B+bH()SKvi-HPI>6)M&`HWP=^jQ#VuAUzdnryRkhH2$UhO34C>)oi+DZroe% z_3t8;f*oD|##rp935W%HCWy}u9;rV96Za>v@7}t*q>-4{sTh__@NFzH7>|XuO4k`A zq2nk6;H$N=l`0}5w%{_mA{u8D-X#|`1Ehi%6bBu0n%_(RbFDR^e&bb0byXCB$x^<- zara%{s9K-Sg)={vzq*G{RetuB=H)EIcPj$zFM#o>))M$qu#V<=^{}yW^*=iG<+y#L z6+gL1h|4d-7Tsvc?TDe(GDz?Y77^*~NjW*)o{lbM&3KFNt!s@krMKGY4(g~g7aJ}L zEmt}GhonSYH#yJ*3X(lDkpFH>k)(ga?bbCU``ceN~IF2*+XQcby~ z_iU&YAU85@QOV5wq1BgDROY+#r5`0XI9}a5^~qbVDv5pm%bg@QY1_aItPwO+mD8P2 z;Tz2+lJnx~Jp>whl?P&#(o!%(nq7|E=6FnUt=zxNh$ zwP&*NNg<|aHfNC%i-Vb1d1QEGioz)L55e_qqsk6Rf$w(wlbNeh^LZ{vbx^jX7?h|G zidddVLG^sfO>zOdAeCq`+KL^ivEqef)Z=Iup?>%gx9g>=9pYm`v3exNIz${f>~sv?ApSKIwikENe}pM+ zzy{dX88g#S?L-q-?=g*lKx$D@vI4r=P4djIYU7m327F`kgk?9C1^4>GS=z9+_&mZ} zg|vq&IjtsZ*B9bRsqh|8tCi|NCGAu~5r}38`qPw*QhdqG%N)?UnDwcH*mVc&%vgQLe@{GRSS>;K0$61A; zb)sNN!I1vg7sO~`hRwrj!|GHW)yZ@J6GHy=j|aU@3Yf-hFGZ6#;!-(76y>EUat!yM z{$40Vq%gQmd}oBN^s@-{AADMK{+*$mpZUh*E-Se~B?i*kksk3 zqX#~v?aC3d;gR=r7~1U$KAtxmh*tOdCxWlX;(q(WUUjROoJ9LH)#)!MjfKlfK&#&M zW^loDS*aY;#wCL)0>s8T$eD8RSB{j+?=KD#y4KS#$F{BQl1f^9kNB)yU)OW>eTgG( z5)<8%2g)*6$XJVnWYV)X_q^Vr56iD)lgrtW*Cc~fSM-uv%Ley|I6J=2-k1JaD7$Gl z1SPW%Y}V;$NfKP$Hhj;yj9UI zy~{ccuhiLd?a-1a>A|)0^buGZXv&x}8ii0ScRFd{$0)2dKk#SSKc-*Mu3AYv_}WZO z!BO4nhR&Bg2cwIRworDdvy%kv|y7JQ3pQmrY@mf}5o-(SRiuRIrQ} zUPBM@{j%~?CO}rlVL8(z=tsrUP?Khqv{%5%idp@0Z;-nSy8sM5^>4PtD0v)R* z3|ZHjE%Kvvg2&+H!LmXJW?UikHz;Uqu3+}5%B$>Xv6|ygV`&*xwLF^^G-O~K8o{Q%Aw9`-jNSrNwD`$25nyi!B#k2kMJh-M25>lmBh zk0OJS{D_zl@?O9>#OWV$nAq-^&64nR1}n2-WN=L}>xWe#3Wdcn1=WqNtP@I_E29FL+$iFXOOt`v(tM?KboaH8LLC}30 zvJJu>5;GZ`Xh9seb@pmp1#&9sCY3#|;;b8zi_y?iv3gWCP|2w|Ml)Jwv!-n7mn!My zH|TRMehxthCa!t|en~5PT{is(e5}lJKo!v+V)7?SgMwF9E61!uO#Y)>CzWdG zKYi_aCYt3_**;F6lZd;{r=NIQ8`1(Yk~?KztOb^bvbyIhH7RXRb+1gF&ky^I@N`0K zcO5-XgZ*L4dP+^B_V9+4NRMq6D;qSPkFe=|$m$n?d&qm@3!1Y_;SA1~R&tJ_mnk8VGKEDx~9^$x`+a|>9WFpHG6 zU)s|;N)-FLr2U^dS6paBc&_PjWS1v3NaR6Gbb{wh_fB35KPGNiJR4e9@4*l^u^NK4 zzSG*ykOGVHoLc{>2I3GvyPVSL;O+v^aT~Tt@Uty#Nv-oxA%aJ&ow28lWhHgi6kEbT zd?UGyY&w_OY`$7-Y$w9zwtE4Y@OUG#YuSlYWs4`rAKjPzCi2N!9bmHT2j|ps`TcOq z@%Om%=$o{wPiof{^Z>(k(_5?nJ}(nqu%8qFf5z?Zz_Q)Y4~PQll_Huwk*O9ptfhtf zJqB|T7T6Xu9BmgUoUpC^Yfy?&($-nUQS`kJ8f{)f>vQE(cR_(1sew0#?@62T&I6eD zucE_0nz5#OOf$Hd)6;Jm@bei-Eo;FzBAyT)oEPh39b*(67g#TjQFKGEJFRYB)8J98 zsuD7bmRK=xKm2FTzBmF{Wn3yeT%p?iefF`ZhoR;8)VI`}vIg_voBMrrtjeXk#Cgb0 zv@)-T7lq9lKQ-wAd)A(Dh5leDdSsJ}s?Ya+W+=QA;d7Fz0clAA!wf^x7PDnhnVTaZ z1*ieF=-fEF&1lJu)CsoN3d4bf#yB%j6fLD~@2c@(l@Jqj^IC1de5;}1#o~?de6qSV zz))WDd+VU2dEm>8N4L>G;7;4+#$09b4@!;Z z9gHt26cO_b^XzLNmy7ppCLMZ$1BC$Wvq7;%qPUB4?A^Bkw7$j*F=X%iMvHj|o=Yls z*IS|yvLab|hu|Koq}GFa$Wk?`y&~5?VZ)F*v>gqbG)Iv(2|n#ue+_M)TH>aNWu_9v zB}7Wer6MrcXDHi>53UT$BYaS70V@ z)kZPX+$A4yJH-F;^I{mi*)v?zMjIu&Hn1U`3JAiJ9Z)yK!k}WG*R$csD2iW=rh$lO z6e{C`e6ATfbMu7xELU{AJmE$w+*w6+?w|3&lc!|-p&V-9(viH?4Rqa}^o9q+6cWOV zyGCFJVvjNmy0c{LNf6b92)t(0ku?4~UBHwR%xob$U+Mg)>j)94T-L zYp-=1_v*}cP`l;`Il32_49{|OR!2<^Q#Q2Ob@is||D1K_F)Mzd3;u9Do%a~JILvyx zcyZn>s*21yg|%n&K0_aC5LP4{(Z?;mtM@X(pJL|T zOS=?Lyu-drE7zw{y8k$52L0gx$Kl;PF`+98kE?BdgBi|bAOD& z5UjH{RJ*)TmnS^#VfT3b>TW@L4apOp39`6Muf+)1Ya7E&3C;@daZ7glM3=CMTwx?^ zc}@E)zm@XGL2}pB$v8ACp~5{nhV~sC9r?QPDC<2f!uv-FES>65Ou!U<4XC6wZc=rJ z*kx~?rKW$?A#7-%=(IlPi@+v%1VZr_u1sQnb1h7{08;ClW%+?)l70pPo1*XltJ9q}~At?zeJK%Mn$F-@eD~zUxVV^DwOHyR~ZCKkdjQ zM^XL7shJ3K_54#R%8=WqlN(l=`NoHh}wc7jm)M6+IW$5iYh4DRx41Dl726-C_g=khCzZ=Zt-Nh$LJ4WEry`4R-#$%5V0F%A1nRy1 zUNAuj3%t!HO$%ecN{_of`*{ zVSxV=CGPYtKcZDsI9n$CTr3%0C>LGkfal4LlxZ_OF6ucxabwJ0?C(W|3S;wG2-R(% zo>><8;7{oi^>iqsGTA2K50|ky!`x9AT0Dh0f8h$M#a;+Bp3HLuo7-6Y%Jl~A7nS3x zb>Pse$jS`sVMo*tFr+bDjxL05HAA#^3Ut1rNr@wzh^L3>D=Ch7CX~~8 za4Ola$P9O!T6)~^!QOLNsU>cWrWK6Zl$xH2_)U*AvSf1uTD<7JPqc%fwuc-tJ6;#)dUmgv%E%z3#+VCe!_cROO%%#dAUOM2;c0 zAxFasXUvq8XPvh*lh!yL@z&XDu_GXLP2SklCtoF|rj159Wp?E$pmNb#S2|9Z>kj;= z_ghqC|B)$8plX)Q3xD>eDKOu* z7ooXL=4`=~B^%rzIppOXrHX@3>ha}6 zSbkIMlI3Mb4l%zEKOVzX0Nf98DO6muEpp{H+baZZjE(F@BHy&sKP{~KxlmT0@wPIwq;73m8QE*018Gi==V!(Lv!Jhfto@8Llp#o^y}I@XilJE`ypF ze#;Fla{<%!t_|U+ji$Z?bepRF+|{l&2X(nK>nQtBgxk`~GHvRXWWr^_6v9Uxb< z1i`gjqm7f@?`uunggzfH_r{vx_ZU7=St*MZhnxD4-Te5d%;N<}Ip>HoTEUlt_=SiR z3m%TxAjkK*ro!Oadp-F(>F`B^^R+XR3}Ne_9f}GC45@$gfEQ_Qh&>_^9tD<+WHQz8 zt;f-pXc((#M#Ft|ZNbIB7_o0p!CRu9-?8eTBKllpl`|`q9VTbtTQ+3llm?=bBKn@Q z<;=AfeUWj)+3=3F5F??35LEQ2yx^~kH`7-dhfeF(FB?-(9kq%NwVb6eYOq-0a}RV? zpPBIol`Ol--}e%g8>Yw(vB378B5N|-eG;B>lwU;TtvdDJ#fynxaC9~gW^ba)bKBp} zSWeHsFW?kSE&G%^{9N|2(Z7^$^>4-8uXjrcZ|1_xEI~Eg$F!(=&^FFWrC1oUV&0HX z>~wx9giLq+o+Yn&ifB#AKGUf@dz6%MkffW}>zDXBsh)Rot3 zecSqsOyrugm7br>WZ@dxh$lpQAyj?VHqHTf&_O;c_quTjI%OJj$IA3b{q|-%2>kbLU1L*&3{*@+?tC&^A#%`LErGs`!n-)mj zI+?V{HYt#N#%rgTF=nXIhb(BC)Gi%WbrOpP*<|~jHdi{>OOyC6svnK70U9jJE|?Cw zscLTjSnS0lBqrl8nA@Cvb3>Hul6-N;d~{%47s0eGRlldf0#38@k-YPOoYZc;x>lCJ zKE|lhw?MNaV(*VN&h7NVsyUoQspqv#M42ZaGELRdzdzg-HkO*k$-8_yU;E)EkQy@e zUDE~vC#)0QDeOY&Os1bFQEZ{g1D^Rc;^ki4+|rPD-sv>k-;Y}4k=q!pqhNZ*x#war zA9Sdq81kLh46{|^H6|7kAiP)o08Gt8#P4g%V*NRVbAZ&eWl21aOV3TT!RP>}=+ZN^ z>*!n(UDRhGK+PB7@!5*aIzh@_s=oK=5PD19nLQ_cG#E$fCEn<|q%=9@C*@!yfX$_E ze#b_87RDzfe6hjuCNNWf+_oX?T(esAtAv0rnIZ#NeIse2)L#+;xuyABLb(!jEN1V& z2pEEUsZbimnVOhz5aFbGaqT@pcj-H3D;NJn9H<+%`JeLq!2c=F&&2q@RR@5C047GJ z|0&N81Tg>KYY-^HDE(4LS5C2ElF#7Dt@j^u)mID5k3({iZ~O)xMoUddl9(?LMygCy zLfVfB`5hD4-rOq2m@A47Hn$#@nm%Njj-D`*wxYN=gz#_U-)Z7yMzn4y)5%m2sOaX? z^Re?Y=~W5?8dAOO#-6^n2hf|wAAsxU8(}h6y)}xfPN+mx2UOsp-KGUCnOd%k_bd}A`4)thD@c56k&nt5m4f}_|Xd$2%ALo zn>j=t-+x3r41A9S$k&PB@AcNLLFFGA@IDBpqL&N$&2Q)h&(^aDB~SwZj_>WDP(_r; z)589c1As3&yqO8n>O<*8R6+C_fMjm;$s)q^>0vM>@xJ3OIe#Ok$taecjL%UJkcrPT z(2jJ{#;l#l!ra137DI}NQFMNG{|gKhebAQ9#;p9&r<`)wC;i*N%dZl?Q$PZ-XBrb~ zcMsr4Bt0+NNE!IeDrCT}+7z7hJ7EnMk@h2BwMJM32#bSBJwSzIpn#7r$ zi4vg2zfF&Z_~=8Fl&kT;-6=sLsbv7eiMbBC7kw%gxG7i){ylSZZEe-yJqJVm+#6B-YlaIl6+_zn_@PVyudNii+yRX||vzzLK2! zG>W4zlK=h`?>*tCEcX#rc7>#qtIy+~d%;!hB^v2W^Gp0Gk4aigZi-bi$k2}mGfS7W zJbB5Jf-1;$m$HicIu&^Cj`oz#bi^2S&MRK|ttimGMG z8O6e5l)AL=TCKE)2Yi0#yE`}iYg?&Rq&LYaf8NO)^;Aor=lHKz1~Re~$Abobns%RX zs*>ZE65$!H`4ptNNox=nkPYBV=+w{?$^y4k&gh^&z#KMibs^K%Wv}TpSi||V&QkJ} zb!D8=Ya1=Kq`l5EJ%=+(=+}n5X~v$(5&je!`Wf9$gwKWriCLQ?}!r9=9O1=<1b7(Ys%c^Bhg^o^2cbkG|-HDXl@93;}zWNiOc=4klT5M%L3^ zdX)&IDbr*)CQ)JySpxPvv3k9Z6s(w{OJ*mY?Q}98QTh*DJoSpEJr~P-zX#{PWE9Ss zQ%T^vk>W;&6Bz{VQzp}jVH~2+reNlJ1^UW2(no*xlatqEE7{y)DrI@w`z&o0zfH|t$)vR2sxX9GQzvmzMEk!vs;3e~U2wOK$JKx5??4GD5 z9p-z;o?F`Kd-yc$TmRj;3l64#oG8-y^>%agJAzSX6yGq*c*K8R(z~>zF=rqhqI~bd z%Dbe|q09=+n2Qc>32vNi-ISyi2MVP7sp#5b>7O>6ziB@xLUW#i9;Bqv2#0P1o7vPA zf>u-7siKhdv0E}{-a=lPvC9Tb=TlNZPnxTGL-u#rijma5tBTx}XXcnsU6- z9iw#PL)l2%l2zw?)9-A`K1`DxsQ>QNE|YGo7I4`U6c04-GGph6M%yhfW##N5XUqF9 zlS+{DDtf)+c?PtM-@=e*wjFnA>s+Od?EuEmS*8)I z%E(gQby~f?ak~I3-CnxD)Znlpt}M{`>nA$=!fLP{YHAR`dS2m*irOiV0Hps#{%3PwhXuYS_D zM*pWSN)GyVcE(183?lkgj>fPIa!SH#^dinyRtEYuHve%z$;{l5@N51rM`#IEj2#@m zPEE)}4`2qcd}&3oFwp_&S^qx|zYM;vUCP|WnDFbW091ru52}-yqp?1r%l~W0O3y^k zLH`U9P~455*|I5zVG#pL%^pLFG^9 zIv*6{9J*?c;jWqYUzlss4_H+&3~_M4s6<_gyfViuY=~Y+1~`ROHSy}Gnqb?WELyG(R=)Ul5qhph)Nl zD`63OEwS{FP66V|VuXc>{_gP7QfQe$S850d`Sel*`f3My&5$>+ZZO+^z*dP>bD&xx zJl3~XGH4wn3U@*LT#yQNfOw#A0?}^VnITsV5p7<`p8)*)X670AdVh~u4@8|5_U6F5 z1cX8oUh&Jn2>G#Kz<6Dw-v9_AQTF*?4*pyrgiwEKh$+CRepbs-K@G)xqPFyaV+nL` z2U_t>Ak_v%ifIW`$3~3O)FS(FN#&w3|7O_HacGy27qkhJ4^xQLQ`87ZS&ZxZ&iIph zmBwG*OjyrSdL~hiO4{D^FP4cB!H?fqSh7qye^bQ+JRiUB7JC#~34~>+d&G)tj&Fw_`WuK; z(c=!8)RBu?brPyRp@EG71~I1j+f$|{r~Vn=Blr{GsqPxT8>=@ox;@{wl1I}217psl zKRt2q8#e(!@7C*#+Z-O<`;S2dWGxU3<@K#3|HIpHD?m8T>Ji*b z*$9&UQe{OIb%lT3qLcu3cHJmbMSF4|Zf~hAV3oe(dy{X^I{EJV`%SSK_zLeIoIAj&|c9up+3~d`WANBqf$AkqQYfyfmW%KeaWKrN~I82 ztPK(gu-4Q|loNq;LerTo{PD;f?2$E0>yogUR#{@DMj0r)duovBy(w5z~E~m_SEj4Rx$P~5c}{C2vB|T%ED$TQ9}*P?Sy>rTMfW(RhK~Z&R>;wpM^O-4)^Ql4J)W!O3bZbm4iWdJF<&&fVc^E}8$v__wD}|a8t-|CqcNI- z96@SWB8{7HJyIDeyAwY@Q85MgxzJK8?;rPesoRwOTQKg# z#qeV@n%*p7&8zat-ReOFciTTdO)yoUAu11-=f(>&L@OD5Pu_{TQN=o-^~+Ya$!4^E zLt{^)UCV6L8YCi7o8Z)@?TQ1j+n7}W7(K6DKYP}>;P91jtcfc$qvKL;;xhSRF7CZw z8uNXuWZ>MNtwjQR5ZErw?omICLuZVV%XLj%mz%80THmm5Q(qvh_9yFFd`qVpl@r$0 z^pu7;exoKU(T4PRwP7A0+(|xn8-`A^r3vb3(jI@&L1&w~9ey*zkV=@}oUz%fDUegM z6D=u8&WHSanBkEbVa~jx21z_yCk`J7fkxaZTfJOva~SKvIh3h*IDNGbxJJ+wczPBq zpljQZm#hIX5T({8u79FB;^AJ>T4tD7|8YbcM*7FJU|}sDZ(rr_EuBhL#LHf9S8KTqp|uUHo_ny{l(06TRmJe)v9fpXcFaA3Amn?Y}(T(H3UIqZ38GcN=grkYCC7 zVWqD7E+QKUz3W`}!x)p9UewX!>N=bqiR79;BfLc|ql)6b=wRn*H@7TdQ?!Jw2vIjPgRjIi+BGOZUOGkcybBrOu+gqrawZe))~3jS z=NBaJ8FBVvekva8Btb;m)*Vh-=L<}uz1D3>?~`4=BkPG&~9s4 z{FZ3Z5knMyryFdhjB8LqA`Rl`ovM=s#9UD(HupB|hjcrNZ94oU$065dY)7Q;XLBkY z0AfMwuwV81vQqzn+F4d7yPbo_r0)#YI-N#4?D}Zg*j0-lL{O=fWceuNb1Uw2Bh+oP z#J#E?rxCm0@$xk5dRU| z|K^s|!s64CwNBelu)Ti4Tl(eJ#vV8a_b{y z*`nIvb2SxEmz;6NS-+sK+*SWU1;BGT;vg<68}(^;x-Y}1%8IAvl&Dd^R|O7d=Y?&R zj22~3sAlH>tmZXC62Tiyet*}FuDVB6-_@E*JG2yErS1J#+B+qC!OQ(hap(Q)po&%aJwV1Z zEh;#uk^SDezF%P)2P>Vw%tjm=F_;gjcm^>J>)74$MiK0}JE5g9;jN zO2}htFR8+nsE~-081;&Aa4Tf)`o&xGQ;EZsF>jp{%{_P5@Z&;K#lm%ojI-T8f_uAd ztwUuj)m;PK^l0sG=)SLus~2A~1FCT=2G&a&YpE4RZUt+*Hebz6^s>SP!X)Ebw5Z6% zz}``_&av@Egj#LrieV;(Hevp?w6)R=8fkNzwt@9VmL?d@k^MwXNXCkw$d(BiJkMmZ z2DGU2`5#TS@h1Sw%v@LYpp$eNgz24cESJf7E6*Es2WFk^iU;I{F9$P4D^{RPy3?H2 zK3;RZV?*Xg_~DnzZzwUi5i~z`n$-lD`O|X0!tScUG5jO+OK;tFuf7=EQc`o6T*;8O zzOG(M!ViSc+1%fOoNwvjEPg-grQYs z#{mK|q_&m#BRXckp|f-yyt75oSbOrJjE~{R1j1=cX=EPI4QSdkRI`$5ZGcnE<-zb& zj-`(yd1FAnmZtWXMABxe)fV_G*}mzV|=s5n(at^Jj#m&?X<_(_3?)pC~chn zdyBr#Cx_9(89FCCXQB_}ycHkDecj5C##?LgQs(vTsYjacl6-;7+?#I>hv($~gw}xm zTMp{KAO|%CV-r{gaT_CJH$qKDLLd{PHY|gpxyKif!Ssbes1j-d2$={0UmF!{ZJh{# zK(?<#DE!C5#P)0LKk(0g)`SQ(xr9a7h1nR{8Ce(stRjNK?Cb&}Odv)Ul-hSVa^A{^z%#7=sJ!z`WcqkK>J$q;Dt?q(0yJA$h*Rgfj4P81ynQ z@<+t}X86{R6pAc(yEtiNe)(#{d30fFl6gK(yXbfSch_Vism#bGPuF2tRo(TrNg-8p zF^)P#U6oUm?HDYvQB$8!H@xM1;3I+VpfPQ2*K;Ld?PEPDYc$Exa-FOKoT7Qmecoak zOa|zJ(D1z+stA)a{2!$DHnU-PvCTAu8oH@mLK-DYjW;7mx%<1FAAnUFho7N&7&Ev? zT92u@A2V4>c^aCY$pyCo>dvr)E=s@2AB^^kf5Zg|UZ0Bbu%=HBQZmv0D+U~_M#U4Y z-qQn@Jg~>)gt7RGV6v_t$hI7i2|l;Hp@*{!g>VCw-WY(VM`8A@kW_{AycXjO;6!oSX-j1pZDL@X>JW9uqzvhsubdGb6N>5AAPyc}bOah1=aO;raHi=sr zWk0mQRC`$L@YeOz!4{m#coz53n}1&XZrjGcReJWWhD_>#xI*GG2!rrIN;SPd`ey@! zR7cr5(LSpNZp@bzp!XdH&H-(oT1+vfkWi5SkNzAjOF6tF=#%})7y9;|Fw>+B<;0rNx}Is+QLAp