Skip to content

Commit 0a0b78c

Browse files
EmilLutaantonbaliasnikovgithub-actions[bot]
authored
feat: Switch verifier 6 to rolling hash (#149)
Previous implementation did a hash over all inputs. This does not line up with the verifier deployed in contracts, as such, it should fail SL verification. This PR adjusts the hashing to be a rolling hash matching contracts verifier implementation. Initial commit was tested E2E. Upcoming commits were tested via CLI (old commit would generate same input as latest version/commits). --------- Co-authored-by: Anton Baliasnikov <aba@matterlabs.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 60e2958 commit 0a0b78c

9 files changed

Lines changed: 87 additions & 85 deletions

File tree

.github/workflows/update-verifiers.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ jobs:
2020
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
2121
https://api.github.com/repos/${{ github.repository }}/commits/${COMMIT_SHA} \
2222
| jq -r .commit.message)
23-
echo message="${MESSAGE}" >> "${GITHUB_OUTPUT}"
23+
# Save only the commit message header
24+
echo message="$(echo ${MESSAGE} | head -n 1)" >> "${GITHUB_OUTPUT}"
2425
2526
- name: Checkout repo
2627
if: ${{ !contains(steps.commit.outputs.message, env.COMMIT_MESSAGE) }}

tools/cli/src/main.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,41 +672,45 @@ fn run_binary(
672672
}
673673
}
674674

675-
let registers = match machine {
675+
let (registers, reached_end) = match machine {
676676
Machine::Standard => {
677677
let result = run_simple_with_entry_point_and_non_determimism_source_for_config::<
678678
_,
679679
IMStandardIsaConfig,
680680
>(config, non_determinism_source);
681681

682-
result.state.registers
682+
(result.state.registers, result.reached_end)
683683
}
684684
Machine::Reduced => {
685685
let result = run_simple_with_entry_point_and_non_determimism_source_for_config::<
686686
_,
687687
IWithoutByteAccessIsaConfigWithDelegation,
688688
>(config, non_determinism_source);
689689

690-
result.state.registers
690+
(result.state.registers, result.reached_end)
691691
}
692692
Machine::ReducedLog23 => {
693693
let result = run_simple_with_entry_point_and_non_determimism_source_for_config::<
694694
_,
695695
IWithoutByteAccessIsaConfigWithDelegation,
696696
>(config, non_determinism_source);
697697

698-
result.state.registers
698+
(result.state.registers, result.reached_end)
699699
}
700700
Machine::ReducedFinal => {
701701
let result = run_simple_with_entry_point_and_non_determimism_source_for_config::<
702702
_,
703703
IWithoutByteAccessIsaConfig,
704704
>(config, non_determinism_source);
705705

706-
result.state.registers
706+
(result.state.registers, result.reached_end)
707707
}
708708
};
709709

710+
if !reached_end {
711+
println!("WARNING: execution did not finish; most likely ran out of cycles!");
712+
}
713+
710714
// our convention is to return 32 bytes placed into registers x10-x17
711715

712716
let result = registers[10..26]

tools/verifier/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ CARGO_TARGET_DIR=target/nine cargo objcopy --release --features recursion_step_n
4444
CARGO_TARGET_DIR=target/ten cargo objcopy --release --features final_recursion_step,panic_output --no-default-features -- -O binary final_recursion_layer_with_output.bin &
4545

4646
# cargo biild --release -Z build-std=core,panic_abort,alloc --features universal_circuit,panic_output --no-default-features
47-
CARGO_TARGET_DIR=target/eleven cargo objcopy --release -Z build-std=core,panic_abort,alloc --features universal_circuit,panic_output --no-default-features -- -O binary universal.bin &
47+
CARGO_TARGET_DIR=target/eleven cargo objcopy --release -Z build-std=core,panic_abort,alloc --features universal_circuit,panic_output --no-default-features -- -O binary universal.bin
4848

4949
# cargo build --release -Z build-std=core,panic_abort,alloc --features universal_circuit_no_delegation,panic_output --no-default-features
5050
CARGO_TARGET_DIR=target/twelve cargo objcopy --release -Z build-std=core,panic_abort,alloc --features universal_circuit_no_delegation,panic_output --no-default-features -- -O binary universal_no_delegation.bin &

tools/verifier/src/main.rs

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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")]

tools/verifier/universal.bin

-592 Bytes
Binary file not shown.
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"machine_type": "Reduced",
3-
"bytecode_hash_hex": "190d028cc90e557122d0cc8b4a905b916ec9d0a3ce9b5a556b42a1e988dd4949",
3+
"bytecode_hash_hex": "9b9e6a3039a15659ede904a2f6d604f7d108bedda75e6e8c2555e8dc3c2c6af3",
44
"params": [
5-
1507285083,
6-
1629886555,
7-
2581269536,
8-
3767387307,
9-
3339214042,
10-
68130942,
11-
3352680445,
12-
627368356
5+
888351984,
6+
940558052,
7+
3830943380,
8+
1193990115,
9+
1248532493,
10+
2415499945,
11+
1815829353,
12+
1499096935
1313
],
14-
"params_hex": "59d7585b6126185b99db0c20e08dc4abc7085cda040f987ec7d5d7fd2564e1a4"
14+
"params_hex": "34f32cf0380fc6e4e4578e94472ad7e34a6b180d8ff99aa96c3b5b69595a6767"
1515
}
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"machine_type": "ReducedLog23",
3-
"bytecode_hash_hex": "190d028cc90e557122d0cc8b4a905b916ec9d0a3ce9b5a556b42a1e988dd4949",
3+
"bytecode_hash_hex": "9b9e6a3039a15659ede904a2f6d604f7d108bedda75e6e8c2555e8dc3c2c6af3",
44
"params": [
5-
1708230190,
6-
678606975,
7-
1975657360,
8-
2228014280,
9-
4164773463,
10-
839555725,
11-
496381199,
12-
3220715007
5+
1495014810,
6+
2087910462,
7+
4139352863,
8+
1966499135,
9+
3477645534,
10+
3754295931,
11+
201124820,
12+
3520443595
1313
],
14-
"params_hex": "65d1862e2872b87f75c2239084ccccc8f83d6657320a9a8d1d962d0fbff835ff"
14+
"params_hex": "591c1d9a7c72fc3ef6b9831f7536653fcf48a8dedfc6027b0bfcebd4d1d5b4cb"
1515
}
-584 Bytes
Binary file not shown.
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"machine_type": "ReducedFinal",
3-
"bytecode_hash_hex": "4e5e33d92803c79c997012f30b0e65ebf6037d17f7008ccaf351d8de192a6d4c",
3+
"bytecode_hash_hex": "657b3c2089fd941bffa22bc8eaf7585be0e22b7b3be2b7509e18d5d8328913bc",
44
"params": [
5-
3473599108,
6-
1490869294,
7-
464289112,
8-
2020737700,
9-
1310155971,
10-
2046873517,
11-
199288278,
12-
4288235415
5+
1827654521,
6+
1740281205,
7+
1402311827,
8+
1988274110,
9+
2172001405,
10+
1997754489,
11+
188561396,
12+
2215923557
1313
],
14-
"params_hex": "cf0aea8458dcdc2e1bac7d58787202a44e1764c37a00cfad0be0e5d6ff994797"
14+
"params_hex": "6cefcb7967ba9575539594937682a7be81761c7d771350790b3d37f484144f65"
1515
}

0 commit comments

Comments
 (0)