@@ -163,22 +163,9 @@ unsafe fn workload() -> ! {
163163 // First - verify both proofs (keep reading from the CSR).
164164 let output1 = full_statement_verifier:: verify_recursion_layer ( ) ;
165165 let output2 = full_statement_verifier:: verify_recursion_layer ( ) ;
166- // Proving chains must be equal.
167- for i in 8 ..16 {
168- assert_eq ! ( output1[ i] , output2[ i] , "Proving chains must be equal" ) ;
169- }
170-
171- // The first 8 words of the result are the hash of the two outputs.
172- // This way, to verify the combined proof, we can check that it matches
173- // the rolling hash of the public inputs.
174- let mut hasher = Keccak32 :: new ( ) ;
175166
176- update_from_recursive_circuit_output ( & mut hasher, & output1) ;
177- update_from_recursive_circuit_output ( & mut hasher, & output2) ;
178- let mut result = [ 0u32 ; 16 ] ;
179- // TODO: in the future - set the result[7] to be equal to 0.
180- result[ 0 ..8 ] . copy_from_slice ( & hasher. finalize ( ) ) ;
181- result[ 8 ..16 ] . copy_from_slice ( & output1[ 8 ..16 ] ) ;
167+ // merge the inputs together
168+ let result = merge_recursive_circuit_output ( output1, output2) ;
182169
183170 riscv_common:: zksync_os_finish_success_extended ( & result) ;
184171 }
@@ -190,43 +177,31 @@ unsafe fn workload() -> ! {
190177 // This is similar to 4, combine 2 proofs into one, but now we combine N proofs into one.
191178 // The advantage is in the number of proving rounds you need to do.
192179 // Option 4 requires O(n) rounds of proving, whilst this requires a single round (time will be closer to O(logn), due to recursion).
180+ //
181+ // The right way to think about this method is a rolling hash over circuits:
182+ // keccak(..., keccak(keccak(output1 || output2), output3), output4, ... outputN)
193183 6 => {
194184 let no_circuits = riscv_common:: csr_read_word ( ) ;
195185 assert ! ( no_circuits >= 2 , "Requires at least two circuits to verify" ) ;
196186
197- // The first 8 words of the result are the hash of the proof's outputs.
198- // This way, to verify multiple combined proof, we can check that it matches
199- // the rolling hash of the public inputs.
200- let mut hasher = Keccak32 :: new ( ) ;
201-
202- // verify first proof & keep it's output to ensure all proof come from the same chain
203- // NOTE: this could be any other proof, not necessarily the first one.
204- let first_output = full_statement_verifier:: verify_recursion_layer ( ) ;
205-
206- update_from_recursive_circuit_output ( & mut hasher, & first_output) ;
187+ // verify first proof & use it as the seed for the rolling hash
188+ //
189+ // the proof's outputs are as follows:
190+ // output[0..8] - the actual output of the circuit
191+ // output[8..16] - the verification key (should be the same across all proofs, checked inside merge_recursive_circuit_output)
192+ // merging is done over inputs [0..8], whilst key is not modified (being copied over and over)
193+ let mut rolling_hash = full_statement_verifier:: verify_recursion_layer ( ) ;
207194
208195 // iterate over remaining circuits
209196 for _ in 1 ..no_circuits {
210197 // verify proof
211198 let output = full_statement_verifier:: verify_recursion_layer ( ) ;
212199
213- // Proving chains must be equal.
214- for i in 8 ..16 {
215- assert_eq ! ( first_output[ i] , output[ i] , "Proving chains must be equal" ) ;
216- }
217-
218- // build the rolling hash over proofs's outputs
219- update_from_recursive_circuit_output ( & mut hasher, & output) ;
200+ // build the rolling hash over the remaining proofs' outputs (ensuring they belong to same proving chain)
201+ rolling_hash = merge_recursive_circuit_output ( rolling_hash, output) ;
220202 }
221203
222- let mut result = [ 0u32 ; 16 ] ;
223-
224- // TODO: in the future - set the result[7] to be equal to 0.
225- result[ 0 ..8 ] . copy_from_slice ( & hasher. finalize ( ) ) ;
226- // chain remains the same
227- result[ 8 ..16 ] . copy_from_slice ( & first_output[ 8 ..16 ] ) ;
228-
229- riscv_common:: zksync_os_finish_success_extended ( & result) ;
204+ riscv_common:: zksync_os_finish_success_extended ( & rolling_hash) ;
230205 }
231206 // Unknown metadata.
232207 _ => {
@@ -239,21 +214,43 @@ unsafe fn workload() -> ! {
239214 feature = "universal_circuit" ,
240215 feature = "universal_circuit_no_delegation"
241216) ) ]
242- /// Used in hashing proofs for verification.
243- /// Keccak-256 implementation, but hashes specifically to be compatible with our SNARK.
244- /// First 8 [0 -> 8) words represent the actual output of the circuit, which is what we need to hash.
245- /// Last 8 [8 -> 16) words represent the the verification key.
246- /// Verification Key stays the same across all circuits (already checked above).
247- fn update_from_recursive_circuit_output ( hasher : & mut Keccak32 , output : & [ u32 ; 16 ] ) {
217+ /// Merges proof outputs from two recursive circuits into one output.
218+ /// TL;DR; Keccaks the two outputs together.
219+ ///
220+ /// Note, a proof is structured as follows:
221+ /// - first 8 u32s are the actual proof output
222+ /// - last 8 u32s are the verification key identifier (proving chain)
223+ fn merge_recursive_circuit_output ( first : [ u32 ; 16 ] , second : [ u32 ; 16 ] ) -> [ u32 ; 16 ] {
224+ // Proving chain must be equal
225+ for i in 8 ..16 {
226+ assert_eq ! ( first[ i] , second[ i] , "Proving chains must be equal" ) ;
227+ }
228+
248229 // To make it compatible with our SNARK - we'll assume that last register (7th) is 0 (as snark ignores that too).
249230 // and we'll actually shift them all by 1.
250- // So our output is the keccak(input_1[0..8]>>32, input_2[0..8]>>32, ..., input_n[0..8]>>32)
231+
251232 // TODO: in the future, check explicitly that output1[7] && output2[7] == 0.
233+ let mut hasher = Keccak32 :: new ( ) ;
252234 hasher. update ( & [ 0u32 ] ) ;
253235
254- for val in & output [ 0 ..7 ] {
236+ for val in & first [ 0 ..7 ] {
255237 hasher. update ( & [ * val] ) ;
256238 }
239+
240+ // TODO: in the future, check explicitly that output1[7] && output2[7] == 0.
241+ hasher. update ( & [ 0u32 ] ) ;
242+
243+ for val in & second[ 0 ..7 ] {
244+ hasher. update ( & [ * val] ) ;
245+ }
246+
247+ let mut result = [ 0u32 ; 16 ] ;
248+ // merged outputs
249+ result[ 0 ..8 ] . copy_from_slice ( & hasher. finalize ( ) ) ;
250+ // same vk
251+ result[ 8 ..16 ] . copy_from_slice ( & first[ 8 ..16 ] ) ;
252+
253+ result
257254}
258255
259256#[ cfg( feature = "verifier_tests" ) ]
0 commit comments