From c99e66ccd6fe440b1cedc4d41ccefe3811223307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 2 Aug 2024 18:29:56 -0300 Subject: [PATCH 1/3] Change order of pub inputs, simplify fns and names --- operator/mina/lib/src/lib.rs | 201 ++++++++++++++++------------------- 1 file changed, 93 insertions(+), 108 deletions(-) diff --git a/operator/mina/lib/src/lib.rs b/operator/mina/lib/src/lib.rs index fa0d89dc48..e43562efed 100644 --- a/operator/mina/lib/src/lib.rs +++ b/operator/mina/lib/src/lib.rs @@ -1,6 +1,6 @@ mod consensus_state; -use std::array; +use std::array::{self, TryFromSliceError}; use ark_ec::short_weierstrass_jacobian::GroupAffine; use base64::prelude::*; @@ -35,7 +35,7 @@ pub extern "C" fn verify_protocol_state_proof_ffi( public_input_bytes: &[u8; MAX_PUB_INPUT_SIZE], public_input_len: usize, ) -> bool { - let protocol_state_proof = match parse_protocol_state_proof(&proof_bytes[..proof_len]) { + let protocol_state_proof = match parse_proof(&proof_bytes[..proof_len]) { Ok(protocol_state_proof) => protocol_state_proof, Err(err) => { eprintln!("Failed to parse protocol state proof: {}", err); @@ -43,25 +43,21 @@ pub extern "C" fn verify_protocol_state_proof_ffi( } }; - let ( - candidate_protocol_state_hash, - candidate_protocol_state, - tip_protocol_state_hash, - tip_protocol_state, - ) = match parse_protocol_state_pub(&public_input_bytes[..public_input_len]) { - Ok(protocol_state_pub) => protocol_state_pub, - Err(err) => { - eprintln!("Failed to parse protocol state public inputs: {}", err); - return false; - } - }; + let (candidate_hash, tip_hash, candidate_state, tip_state) = + match parse_pub_inputs(&public_input_bytes[..public_input_len]) { + Ok(protocol_state_pub) => protocol_state_pub, + Err(err) => { + eprintln!("Failed to parse protocol state public inputs: {}", err); + return false; + } + }; // TODO(xqft): this can be a batcher's pre-verification check (but don't remove it from here) - if MinaHash::hash(&tip_protocol_state) != tip_protocol_state_hash { + if MinaHash::hash(&tip_state) != tip_hash { eprintln!("The tip's protocol state doesn't match the hash provided as public input"); return false; } - if MinaHash::hash(&candidate_protocol_state) != candidate_protocol_state_hash { + if MinaHash::hash(&candidate_state) != candidate_hash { eprintln!("The candidate's protocol state doesn't match the hash provided as public input"); return false; } @@ -72,115 +68,104 @@ pub extern "C" fn verify_protocol_state_proof_ffi( let srs = srs.lock().unwrap(); // Consensus check: Short fork rule - let longer_chain = select_longer_chain(&candidate_protocol_state, &tip_protocol_state); + let longer_chain = select_longer_chain(&candidate_state, &tip_state); if longer_chain == LongerChainResult::Tip { eprintln!("Consensus check failed"); return false; } // Pickles verification - verify_block( - &protocol_state_proof, - candidate_protocol_state_hash, - &VERIFIER_INDEX, - &srs, - ) + verify_block(&protocol_state_proof, candidate_hash, &VERIFIER_INDEX, &srs) } -pub fn parse_protocol_state_proof( - protocol_state_proof_bytes: &[u8], -) -> Result { - let protocol_state_proof_base64 = - std::str::from_utf8(protocol_state_proof_bytes).map_err(|err| err.to_string())?; - let protocol_state_proof_binprot = BASE64_URL_SAFE - .decode(protocol_state_proof_base64) - .map_err(|err| err.to_string())?; - MinaBaseProofStableV2::binprot_read(&mut protocol_state_proof_binprot.as_slice()) - .map_err(|err| err.to_string()) +pub fn parse_hash(pub_inputs: &[u8], offset: &mut usize) -> Result { + let hash = pub_inputs + .get(*offset..*offset + STATE_HASH_SIZE) + .ok_or("Failed to slice candidate hash".to_string()) + .and_then(|bytes| Fp::from_bytes(bytes).map_err(|err| err.to_string()))?; + + *offset += STATE_HASH_SIZE; + + Ok(hash) +} + +pub fn parse_state( + pub_inputs: &[u8], + offset: &mut usize, +) -> Result { + let state_len: usize = pub_inputs + .get(*offset..*offset + 4) + .ok_or("Failed to slice state len".to_string()) + .and_then(|slice| { + slice + .try_into() + .map_err(|err: TryFromSliceError| err.to_string()) + }) + .map(u32::from_be_bytes) + .and_then(|len| usize::try_from(len).map_err(|err| err.to_string()))?; + + let state = pub_inputs + .get(*offset + 4..*offset + 4 + state_len) + .ok_or("Failed to slice state".to_string()) + .and_then(|bytes| std::str::from_utf8(bytes).map_err(|err| err.to_string())) + .and_then(|base64| { + BASE64_STANDARD + .decode(base64) + .map_err(|err| err.to_string()) + }) + .and_then(|binprot| { + MinaStateProtocolStateValueStableV2::binprot_read(&mut binprot.as_slice()) + .map_err(|err| err.to_string()) + })?; + + *offset += 4 + state_len; + + Ok(state) } -pub fn parse_protocol_state_pub( - protocol_state_pub: &[u8], +pub fn parse_pub_inputs( + pub_inputs: &[u8], ) -> Result< ( Fp, - MinaStateProtocolStateValueStableV2, Fp, MinaStateProtocolStateValueStableV2, + MinaStateProtocolStateValueStableV2, ), String, > { - let (tip_protocol_state_hash, tip_protocol_state, candidate_start) = - parse_protocol_state_with_hash(&protocol_state_pub, 0)?; - - let (candidate_protocol_state_hash, candidate_protocol_state, _) = - parse_protocol_state_with_hash(&protocol_state_pub, candidate_start)?; - - Ok(( - tip_protocol_state_hash, - tip_protocol_state, - candidate_protocol_state_hash, - candidate_protocol_state, - )) + let mut offset = 0; + + let candidate_hash = parse_hash(pub_inputs, &mut offset)?; + let tip_hash = parse_hash(pub_inputs, &mut offset)?; + + let candidate_state = parse_state(pub_inputs, &mut offset)?; + let tip_state = parse_state(pub_inputs, &mut offset)?; + + Ok((candidate_hash, tip_hash, candidate_state, tip_state)) } -fn parse_protocol_state_with_hash( - protocol_state_pub: &[u8], - start: usize, -) -> Result< - ( - ark_ff::Fp256, - MinaStateProtocolStateValueStableV2, - usize, - ), - String, -> { - let protocol_state_hash_bytes: Vec<_> = protocol_state_pub - .iter() - .skip(start) - .take(STATE_HASH_SIZE) - .map(|byte| byte.clone()) - .collect(); - let protocol_state_hash = - Fp::from_bytes(&protocol_state_hash_bytes).map_err(|err| err.to_string())?; - - let protocol_state_len_vec: Vec<_> = protocol_state_pub - .iter() - .skip(start + STATE_HASH_SIZE) - .take(8) - .collect(); - let protocol_state_len_bytes: [u8; 4] = array::from_fn(|i| protocol_state_len_vec[i].clone()); - let protocol_state_len = u32::from_be_bytes(protocol_state_len_bytes) as usize; - - let protocol_state_bytes: Vec<_> = protocol_state_pub - .iter() - .skip(start + STATE_HASH_SIZE + 4) - .take(protocol_state_len) - .map(|byte| byte.clone()) - .collect(); - let protocol_state_base64 = - std::str::from_utf8(&protocol_state_bytes).map_err(|err| err.to_string())?; - let protocol_state_binprot = BASE64_STANDARD - .decode(protocol_state_base64) - .map_err(|err| err.to_string())?; - let protocol_state = - MinaStateProtocolStateValueStableV2::binprot_read(&mut protocol_state_binprot.as_slice()) - .map_err(|err| err.to_string())?; - - Ok(( - protocol_state_hash, - protocol_state, - start + STATE_HASH_SIZE + 4 + protocol_state_len, - )) +pub fn parse_proof(proof_bytes: &[u8]) -> Result { + std::str::from_utf8(proof_bytes) + .map_err(|err| err.to_string()) + .and_then(|base64| { + BASE64_URL_SAFE + .decode(base64) + .map_err(|err| err.to_string()) + }) + .and_then(|binprot| { + MinaBaseProofStableV2::binprot_read(&mut binprot.as_slice()) + .map_err(|err| err.to_string()) + }) } #[cfg(test)] mod test { use super::*; - const PROTOCOL_STATE_PROOF_BYTES: &[u8] = + const PROOF_BYTES: &[u8] = include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.proof"); - const PROTOCOL_STATE_PUB_BYTES: &[u8] = + const PUB_INPUT_BYTES: &[u8] = include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.pub"); const PROTOCOL_STATE_BAD_HASH_PUB_BYTES: &[u8] = include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state_bad_hash.pub"); @@ -190,25 +175,25 @@ mod test { #[test] fn parse_protocol_state_proof_does_not_fail() { - parse_protocol_state_proof(PROTOCOL_STATE_PROOF_BYTES).unwrap(); + parse_proof(PROOF_BYTES).unwrap(); } #[test] fn parse_protocol_state_pub_does_not_fail() { - parse_protocol_state_pub(PROTOCOL_STATE_PUB_BYTES).unwrap(); + parse_pub_inputs(PUB_INPUT_BYTES).unwrap(); } #[test] fn protocol_state_proof_verifies() { let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE]; - let proof_size = PROTOCOL_STATE_PROOF_BYTES.len(); + let proof_size = PROOF_BYTES.len(); assert!(proof_size <= proof_buffer.len()); - proof_buffer[..proof_size].clone_from_slice(PROTOCOL_STATE_PROOF_BYTES); + proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES); let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE]; - let pub_input_size = PROTOCOL_STATE_PUB_BYTES.len(); + let pub_input_size = PUB_INPUT_BYTES.len(); assert!(pub_input_size <= pub_input_buffer.len()); - pub_input_buffer[..pub_input_size].clone_from_slice(PROTOCOL_STATE_PUB_BYTES); + pub_input_buffer[..pub_input_size].clone_from_slice(PUB_INPUT_BYTES); let result = verify_protocol_state_proof_ffi( &proof_buffer, @@ -222,9 +207,9 @@ mod test { #[test] fn proof_of_protocol_state_with_bad_hash_does_not_verify() { let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE]; - let proof_size = PROTOCOL_STATE_PROOF_BYTES.len(); + let proof_size = PROOF_BYTES.len(); assert!(proof_size <= proof_buffer.len()); - proof_buffer[..proof_size].clone_from_slice(PROTOCOL_STATE_PROOF_BYTES); + proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES); let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE]; let pub_input_size = PROTOCOL_STATE_BAD_HASH_PUB_BYTES.len(); @@ -243,9 +228,9 @@ mod test { #[test] fn proof_of_protocol_state_with_bad_consensus_does_not_verify() { let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE]; - let proof_size = PROTOCOL_STATE_PROOF_BYTES.len(); + let proof_size = PROOF_BYTES.len(); assert!(proof_size <= proof_buffer.len()); - proof_buffer[..proof_size].clone_from_slice(PROTOCOL_STATE_PROOF_BYTES); + proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES); let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE]; let pub_input_size = PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES.len(); From 5c11fa3652e6fe3f2f181add3044b0e0efbe798c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 2 Aug 2024 18:55:24 -0300 Subject: [PATCH 2/3] Update test files --- .../test_files/mina/protocol_state.proof | 2 +- .../test_files/mina/protocol_state.pub | Bin 4184 -> 4184 bytes operator/mina/lib/src/lib.rs | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/batcher/aligned/test_files/mina/protocol_state.proof b/batcher/aligned/test_files/mina/protocol_state.proof index fdf521ce49..b6cb9a77ef 100644 --- a/batcher/aligned/test_files/mina/protocol_state.proof +++ b/batcher/aligned/test_files/mina/protocol_state.proof @@ -1 +1 @@ -_GDN0rHPMhIV_GpUTS3i5d65APzbYLtzeLkCbvwZXzgrp5nfGgD8tb4r20Bt2j_8Oeq15eiUYrIA_MW_sBWNvN6P_HGDnGHsufC4AAAAAAAAAAAAAPxH80vYrKyoAPyPr8iyWEPN3gD8HODH4lLGfNb81Qffsqevsi0A_AvuNCRLmUaK_LRB3xhg7u1RAPxVxGx3g4THofzaP1uzcx9Z7gD8FX0Um8BEL3T8hVPLvbmVj6oA_Ey2tDOnNa4Y_IGi2qW81oB_APyt3LefT_usQPzDMGQOGQQe-AD8qnQvVCgN94_8YpMFyQ3JSBEA_Coog0JF1TYI_MX3m6JpMqSzAPwQvWA8ixi-_fzCmeOFBT5SegD8RlHpYGTCN2_8ods-CG8m2aoA_M-nxRAGBe6h_CmyQoaJQsHoAPzvae0q_JQsdvzEfNfObIOyFAD8ALCi37pwiK_8B9hE3Tz7nisA_K8Np8FBuUJ8_J8fTW4VYrs5APxEA9-zctPtE_ywMlExx3v0NQAAAhD8YMjQObyV55f8wnU_dPsbUKD8WV5_vGvFrGL8JOB11pLVwRUA03Io50DeI7q_tnniTMAdtja5xdSegE9g41r47gixjwbvfIYjN4QuACwYxiYJLw2riizhGpOKYzFUiZ6PzadnP_wX0SWMWStq7Pyp3svNcRl_ZwD8N53GHHPl0gH8wtdfEjhWLfwA_PeVJT2_bys7_MGU1jU1fOvuAPy-MX-iCknAC_zf6S3IFdP_fQD8iDUGYyFvu_X8CWSn9l0qZB8A_EYRaOC00KaY_IlvQMv67yG7APzUCjX0nh5TXPy303eIhFahBwD8IE6pi9M1FL_8dRLGtRNQnzIA_BMQu9kils3Y_CgX4CiB_5W2APy27k8NBLMXefzWZQ2fis9fGQD8fVU34pPRpBf8Ft4uVFKMCPMA_It-S2ZUUJ-f_Lc7GXYRtH93APwVM-gTQr0-GPwECdrVU47K-AD8b7hD8f2Aw9383a9-Af6METsA_MbQUi_uvM0n_NQYZ7NffyIfAAD8VvH7rNHVhyj839lDzFI1ar8A_GWMQMO3p_9I_Jsxbd5VbSlCAPw3dsda2uybP_yEQ1V38LamtAD822I6ZOcFvp38Iohx8aG7ySEA_PsEIOygswg7_AnQfcE6cSq7APyOToOoh0bJJPwEEIGYJSRxkwD8k8Bi0EM9zMH8ufKmD7kB7goA_KE32xyKbFJi_D3B6rahTqlYAPzbEssKS91hGPxNLL8iEu3AHAD856tk-a8-rq78IdKw8XWtVNYA_AS6zd1IU0DJ_PchSBiMhQCgAPxZLivAL3QIO_yGIAayJSqMzwD8ay8YPlUcFQz8xZeBlA_Bv_AA_KeNXWBx33Ds_GQcuaJUHKqIAPx-n5_en8Zs9_xQJm7y-HpZ3AAAAAAChuwn5neITbvEuNucBU_ZlABlJSmD21bdf8otAU29Shtg4ir1-52YhplVWXOLlAEK3RKXpiJ7UC65oO9rI6GoIru8E2gTOgAM_1tz-sRCTQbCOQyPQxRCMDUo4okitiMKEJQA_MzEEleS062NYs46wewEHZZ8HW72tWA2dg14QRQC_EQleyVUaCaz_Ew9rYaRSFs9APzhiB0_sFMty_xXXUanGMoX8QD8RdfUcc3SO6T8knqokMTpKmcA_Go8ru6GPTbv_D-J2iDTTknhAPzp0hwOFnwMrvxsQQHB-x-6qQD8NT9zbLcqOVn8rDQXEEpW6uIA_N4zP0kn3gI6_OxkH4HvFw6WAPw8pUPa0slf-vxe4ehAvFBXJwD8tMzGdur7r2D8AlxcyXYnewEA_GgUQfzKZjJ7_CxIVgBOg12wAPwkBEn9O7T4jfznP3GsSAOX_wD8t5T9LmZugSD8qG5d3JnX1KoA_IjsDF7mO2_X_CVROCVHISJpAPyAP4fGk6g7o_wwTJrvf5nVdAD8fmKFloHNTDD8M1A0IfolgfoA_BZRptlDcu_U_CIIZ2KW5JmvAAD8uSqnVrRwc638_J7x1SP5TzYA_AB8L45iHIdZ_IfMJqJz9secAPyv8raeHYJUI_x-9X320Wu51QD89oaQoND3exT8aCokQM5iXmIA_A6tVjJjG8av_PvhH6EQcoAJAPyRQazKvh5Y-fymybc-mdUeVwD8vcNkzaNQTqr8aMX-wQrnFNgA_G3eXoLfrB2y_KUH28UXogj-APx_qubp1g9Ogvwsf7lOmDr2_AD8ygQbcSuIMcP8KSautsesOZEA_O9Rgf1Hjw_c_IeVO8RDeqkAAPy_MobRHtg4YPyrBaqicLyz-QD8Wkev5eDSdZT89tLDrgKny9EA_AR8Lfn2D3i-_FTi-zKRWD3hAPwTdTG4ErdwxvwIPkiaM8x1FgD80bjKsaKwwUj8zrFxwOMEZhsAAD7S4QZcr0KBWSRAQiAvZEk8yvUsVmRL4lZen07igKE9BSeeBnrdZ2hkptkKSuVwvLY-SO5d2tPr1wTnGG5PLicB3_gORQder2qZ72FsdRiA5dUABiKG5E3oK0NcdOsODD8BECfwDKE_wPKSAxHShesXddu3hi5s3L6i8jixGvyYPj8BPatQbdDvtWknmaLX5sZE0xVskxDB5EMg76znqdp-6ygBCRcL9L_sYrvNYzjBfrdAqiKxjXqfb-W62YHx8y8vzxsBfjAKVsIorEuObTM-26Mm35jqAwckNpRzyDPMfVAmwgYBJ5wARdPhyHnz_RASMXnuSXj02s5RxxwYagBdqFYyGD4BSqQyDTsvsm3bAxUMgdOqgs4YLdbuW3rMRFZwY8y71hcB0Kqqyd5SDIHneBzbl_Polu-ky_gbX3taq_NgzI1yWQQBS7ScnUr8bXYeK2e-iBzL5G0typPcELe4eSKCjmlJmDEBdxn_L9xcbm8mp6hi0Jpg97S0a0W0KUG5L5GsgUCd7zMBKrTCQAOAVmnGeUETpGej-iUP944TMc7b0MObBBvyfRsBrm1E2gEOIFK54rnhb62yC4kYiq6-ghQ5-LWxzgSCFRsBukamjIQb_B8aptA9vcJT18p-yrNwKrrpVOyZ6pnDKB8BSL9dMu8ezDwpIs7CM04WdFOt59wHrN6J7-LsvFGhnykBGNU_uCg7UCEsLxbXozD8NbhzZS113niIBK0qr_eWnDABWZH6Y9t6w6QGO42-TKjV3QpeMuEPjjI8jhWdh2Hs0xkBrz3wM1axx9_Nhko5_LgFOIHK5ytgXnmp_K9sHzTDJgoB2rZTE4MCT8dzwY0HiARd2vMJLmkd0Uu4vGXNYouScyQBMFtCCWG6nsXHm8S6bRLI5jiWFyxjvsPVknLyIcaXiT8BgQrhi1w55j2RijtygggGQSKWMov-RMSXOQBESuaMSDMBDwL4RH-wPtnxSigzHREXhR_jy5V0l2YNEwzOp8xtEwABNdruo9R9fdQ3vZkv1Q84EAruK4nzWvCEse1BjYchMyQB928paExvNEQVfiijUd4I7AYktCNeaQkuaqE1kP2PyhoBJGAVgb08jjedu95_0Sc0Va7RSF7vphxzj3F-vxyUZS8Bbg2Rz5LDH1Ky5m-feluRtTK1QSjJS3MN2GQmfyHO2x0BY40-OuiTPzBcGoip-UJm31wfH9RcEOpi8x4-OBcvkAQBsQ8RHKtNeFVKlSuFECPAqS5pOnLIP7rLrqeBu9nNsy8BvyFNnzYEvVzPOvzS7LWKAuUYWPG__6AUgxi6puor-SkBHRAV-tGU0csji1HIvtfG0EdCWA2QbEvVD0NVh4SkfycBUMWau22JLmAioqc-85xdBkIIoNZsfOeAqg8PxkcgRgAAAXwypuA4Nylll8ytpZ-sLVUVD8-8dHqO36bIwFD6HGIXAS3YF9EjbVktxThV13uFsGBN7pOnG25IJLDCcYlU-nkbAbinXCiSXLCuVGBLDlYhLFaTQHaQpDJgYpnnqzGL7C0xAQk-iFY2FYNnQ5fLkI88TVOz0y9LKn0g8Y2b7yF1xVEUAb1FNro7q_L8p_tFGplDf9MBbmxh0s2IO_Bjc2bG7mwvAQrKB7pGzXYLkUnzLq1QzZ_gQXC3KUavsQ-OZNUOtKIdAUyYzH8Y7WEP2itZslk51DSFahM4IOQ36UghRC0jfzAvASoz2WgqPI-0fyqVn2Pilr7stquXyIhc2mjQiIQvggwEAdoTBQXxq_KjPEe4SWrUsOecGdeGyuBSRAncE4nT1bkFATOpinOiLevIt345tAoPt13Uybs4cB85E4dOa_v98ZgXAbz8oKCBbSOdoUeoEvhaDfoVpCSmF_ztWYQI2daB8PQjAVlXRySq5Yal8BC6yt8FP7voczhvA53u_ch_FzzWB8IGAQgDiGEuHEoD9uxPilOY449F9vjvhZ15d7Hl7CQhD8cyAeQztPpBc3NT4_ZMcAsTK8pzsN4bnoo-mDKE8mO0RRsCAda2zFwwPf3iHFjPo0Kqmd6RztrfRa9AG7TzFt-jn48bAfVvTNxhDs1rAMFjfckBOVZPiwq-n2d1hS5fwx9qROk_ARY3LQ8sPf87rgwWCDeqYYeEH0MEmaH9pUjly0rYpKcbAVU8GHNrcr9kp1er69hPkPtUU4_-t5c8aAa3nA6HPt87Aej2YGfAUeOMMXXcPIAjzW2f0OrHpvLoUR6FCa1XBlgGAaCNh5B2KJW_6SU88tnGlmbYeGzsIfooKusNGdiJFIYAAV6UHz0bs1x0hw5YwOK5TWPBzJAyfpx8-TJcOCgMw4IyARSDFP51DZ9H3Bz5b6dKRsCaPYxxk2pthnfXR43-uUwqAbojGq95JtSOjxxhwAuWaGdZ4ODF4oSJ6rxwyezgn004AZvNifBLatV7YdhZ6Z_SiFElWwlp379nPKL8pcepP2IBAf2zkbVSbAZ-NB34jnFdvKeCKTviewJ5gdp1HeN_mYQHAQ0VX8TUbiu4kepAyq7CBAydblUyM8ZYqtwZCjuHBbADASuK3piVWElKys36NEWH_1djs2sPn_nIThAaDbFl-I0rAe8XtW3WdbgSHLWaMEKVzW-01RU-xqPJqgPF96nkJ-MfAfy4KNk4Xy-r2hfBWGJfRelTYIir_3xQmUSu8dx5YjUbAcy1xVW-A0snt0lZNbjS4jX_cf7K7UwlixWjknQrnCoMAAGuD3vdnnQkOMjxPVxZnfkn95EWTob2usRPg2IHktHnMQFhD6b-ubnTc76U_zVcjMN-jwA_2OcIx-wfbUFGBT7rIAENbEjcpB56xKw0Vn2CeLL-7fyPwS6ovhuGw4rV5pVtKwHsZqlOJV2nbLo78_z9AbYMoWK8y7E0-xJ6hEj-qKhVDwEX0Rm5xxMxFk5QliRvzCW0PGhjkILzJXiRHs3I418xJwEzBR3iQhyTPmW4zpca5qGFdxNNRUOBTFUV-ktiKCJpBgGXEgC_kKss2PO2d4egHw2hIE_O2EVdAoZmdddvctWrDQFOrsB2MTpaHRNHiMr45VU8lMPoIlXxrnAeOysPClZhNgFCodr9VwYJOuBQ8wWtBJ4lZCHtQKJIPdxgkHN3XVo4KAEzNDEl1KXkzxXMo3ujRRU_AL1XZ_rxVavSYdAPgI4FAwFTHAROIa22E6KK8OHdWizhFLiJar9pkJD2B9MTk8ZNKAFaE8lAeItjuFixzeglYgHC52Kc7FGAtF4BRNLDxgVuIwEBHDO0f3vJWeScHPGodLhUnWQrfTSISKNGgfbIeKMqCgF7r3BwEGPToHjMoRzdITifdUazTI0yAlkJ8XVPEW2cMgAB6Lc27mGRIAU8Ro9NBg9pVdQcDOOpJlv0z-RJ_1TNUDEBP6zqwTJvUNFAV6V5HBHq5eHovoHjAXe6uFuYIRAvuQcB3yjwc9VtX-voVMHOoJb1pzympFamStr9a3ioRLJPGBEBJmW8TsW1WJ23IzDAhbsqq1ttMRTfmq0DPYt4-gLfJAgB7pTdd5FDnLicGgG62REVQgRA4V2ADaz6hMakpkrVHSUBhir1KriuitbggmTzvh-YPYea6PhRZiLzGEmD81y2NyUBWzsED92bg40R1AnNbuO9jXjkU_qdsA3xLX4girjs_Q4B_s9p0M8jP2o2OoJTAkvUixjgqz3TM49qdZ0awm5ikC0BuOwW2DpF3MQ08WeOFdIxk8MUGhR3Q9juvjef7k28risBtne4FDmHpJkC_J0Aj_uVPDNHY57zfA-67LDjhT1gqR4B48EdHcf9FsNxFK3Nj6Bg5b2PzQAyjvpn2XeyJcUI0ywBEPB0xaHo2zG5-H255M6gt4PsZeBezOfX6jnbEfA7ZDcAAAAAAAAAAAAAAAAAAAAAAAAAAIJY34H8pmRYfScDlX4mXv5iwivX4kK04_pq-BP7vegDp8w36FP8Gp0dxUEhzd55XPZYw8AJNyekRVtOf_opFSEXO98FZ1YdrTYku22WCFv7mbNk-wyI9SH4IPnI5SFlEkniysOg7AFf8U4Y5zcCU4fGZF3-rOtMtfgYgAvJySo82uXm9WBJTgM0iGSRxQZNWyKttJSzPABZp16rgcDDDgUiukGJKZsMdJULxycuKkfygR7H_z0zGy17gSBxCf6WGuEk0Y-qWxLCi-T-e0_5l4jvmsAfTcvsZaFPuM3-Rjo85IdvXfp9R4qQh4qLvxi8yTftSE8WIEJ6nRhENKG7izWfJoWPLQIUpp30aq2KZIMTSJHcSZOD5FIcS9ONy_s5Gux4ClEIWQu2AXZdc7MaHs_Xm04pX1El7r3sclihJN4HSEr29kQ_sAaxcC0JrILtoS5_D6Db2YhnLWfaCkWspiW5KJMcRYqxRZMk0qcEYWIeI_p1vmukmaYM2AaAE83tJBajrrWxCe1PMlrmEKx72moocjd2tJmn46DX3g2Ktk8Y4faah68c3lR-1KiUv2s-rL3F6qetOWTMENFXbiZZjC64Uc5sint8EMrzaO_nCVepjhtFzWQ5oawP0STJ5LAmLc0lxaLK86iEXtPWiTR8CieJiq2d_ohIlTYeUfya3fsJqmxjfsezHE36x2YupzjW_XNahCEsEChpjiY6ELImPQkEMzm3zNxFEOWo-rBaYS4uQK674kdC6nmONLp44dOxENUO8LNhIKKSrE8zulCk4H8Qeh2QmIpMYKJUMuqf8p4otYzhCe6EL6zBHN5nY_2C6T07k4of6NdVc2CsR0MwEirpcugtFn5ecDNh8NzsPdzUejR5qC_hL7guxLhJU4rmFZkgMecMTbbwsFFZeVIp78nzWF3qYmFvyxjY1j-WPlUCCJ0HtwuRIPdSbu2_d9hsX3VV9MZW6nBTvcwJmfc2BTUlTVJrePLw5vGfD9t_6buqBklvnz8bkmGAYnZAOmbeFPu_8YysYE_tEIi7fnBn-krlKY0kSFEB0v6T4V1rck8KPPuLUwedHqvZIlhRBiBUVdBaat32JcVym5WzP9DIyDO68haBFhxBnp3Z8loXjYICv9sQq8kfvhHOepgwKs1KCTmXVjsEJuj8vMVjxinjA8edJUaqN_Y6_X43is-a7iUymPhjIOEnbCRN0nFE7dNONeJGDNF8jnBXyBiuDYQzCSBqOnZXSwV67mzdnz5Jx33v-iYecBpmDxqjaNByG8c8MlxK5wGxrXPbUmovh_hlFuGBhPN-6YylbcLMbE7M00c_AFnjjpF-sEhEUkQinrT3UpliO2AJNamwOXbFGpOm5e4t9fJC0Ke_oNQB8WEBgDMneWQkSptyomV6VnbNzt6TvDuoGk0HvGavBB78cagRwX8pO5c3zAvsOgLc5yRtbKDTGfhh79SXP6dgtDwLovVYSRzm9g-uKzb1fWChJTT3n7Ia1mrJvqEgYEKeUtqsNepXVuJxcu8tt_uG3wa8l5PYrBHPoACcVUH_X84slK20pqm2Yvx9ViE6rLlHN07KREVzJLYQOM2EjbRmQ96dCP1PnhGwB1X6QUf6WpIvO26fmnojyxnyBxzm9TyNTUqcJfrxVKy0hPONlizGs-Zn2waVfSzwrp5ibBo3n2Ta7K111Qc64_Hse71e1k6KK4PVxNfaGNF70Ymfzo-nJ4_UBPahCiMt6vO3fBU-weAjGvDDWu80bbJszIH4W5tT8CETIJBeJz2GxxIOf246FKLQ48H48Sxm638X6nmtE19bUJfDvisKJXCcwtLEiUU3PP_WnCOAPh7r6kDx2IRVTyy4aI-6KB-ccC85WmX-fK-SWaRTcAkoRnUiQZPY-0oZWQm7ZZ6m1wn07FkFnAgBNp4hM_dwbBFScKN9rMgq2TU-Gj0SA4kQnUzM6btuhgyT2fvhcY67MG_sh2hI9xNz4uaLGSKzQ6QDVTGR0CSlDK3U4F3sXFwmACW47tFZkKgNe5LYt0S_ceNjrwm1MvcWyN5RIr4Cj7MeDx5eIYzVSjfa9Dm3PBPa6SYN4TVUlYeEGiYFF9MCPSZAXNSzfPrBSKnCtqBirnkC4tszYoHBv4Iv2FcKa1LzHSqjd0z0XxZh-skrQrOD3duVKIGmY-01HumuHTVb_GMmS-hhnOZyqvNUjuKcm29_59uBKuea7DYDnrwQ0gUkmxYdpG6-zys5GKtWpySX1ybWP11zvyXc_CivewSqFFoRInYcoMV0eAeZ8dVsUopQLHe72OrOnscI3G_B6aTjlmIjcXD9h4VHWKhSytSnmemG59d9nxyvTg6mSPnilrn4ywt2ZhHQUTAkAqg1O1YgNSQNbYIRnhCrBy4NvFdeXLvHKrffY6UP7a3YkJxrgnWFnPJ2YYJsyRrucPI3gUGlr4MlwpkHvErqqUxjdm2Y1thmlRUfkMkIzWx_5izogeY_3BIJHJaD7xs1brKqBrfgAQTU2QCG2EsS99D9-JgPxeNfDSXnktWZYbPDCK8XVbdkLmgGHfxUobOfe_CQjswnaRoGVy1H5rCBLV8GhFjGXzKhItkQB38g9XTfG1wfUlfcuzc6iMhK5stHRZTRVW1PG0IQr7uAtJZViteqizsSs9-dBGBAilVEfpjIDJoD4gw6Qv7Y-P3qqeiM59-xvSRp5b4NoqdXX2fxUwXH31Tr_rr8N6x9BbSydD9nYCfAL9LqVyFHmpFRWlAxW8TpTuLqkP4vt7hNglip_lCixMEnSR2MA2_UnzF91lsMQljYMYK0pdShsZTa-wlz3npPVaU2OnoSbVQX8KXDFvkiDhT5cH7-941w6pPjXMBeBQvfC80n8AsYiwiwz3gD4NpYGf-nK4nvoP-_5sTfc8rWe8pgHVlgKLUSeLzNu_Gvoe08F-ND9upt98qwTm17lbBuZYWYc-wnisNnaSvBUWtm2pPG76CDQUyRI9jOT323qZM8RHINQgTw6Jl58heXDtU7aH3J8yiebGO9JeApcicnzFrWkpHvF5kpMT3jirzzVgdIMSZ4qIWVejF3DZKXifL5bqftPj4f_i8hAr05ftLhc1ck2U_C3oPJWaPc_mmsfkiqiv_uIDrl08PgrD5QcRkM8rmRkg_mMiB3YwigCerpKsIQLErhI-w_Lv75uAyMRkxZHceX4Bh-s_G45LYbrPgOFXQ91143DurYbYWHggQhPhE3gZUO0h2vuU237gAt-8NuyoQojCF9UWduuOu8fr2-lgFwwIcDKs-AXtfx2hPlENCIdE61GQAA0Gda3SdWkPnZCM1AGB-vLXq2Q_PIaugitF-FL-psJ_y60Hg1IPE7GFkP1--ewI34IQmuk_mDGnkK58Ba9PUiO7JsuebFO4sTsddAgPansuwUdvecsHgFoyc1WdT_PT1RV9AsHJVdWiHivHeVeVWC0k47i-BNTinXKtYOo7q-HgrG_2RE9Z4x06uy0dJKquKK8vbtiTuA7vfZ6hoM-uUSgRq565Ot5p_7Ys4Js95RxY4haqGGJVq681Fpf50gSybZQZhqnlfZD2aa_glXJfz5unu7euAbItk8ucsmkDdfNLJgONavEaCRLgdQ_alI_V79hQJdx1tdvNelyZk-hIMTZuNAshJhc9ki2UOCb1smjoFkzdv0Jy4kSMKnXlmkRzGEBF7l3LXT_xPpZh8gTHZzwmnZe3faARHq-NpI1WlWKo2fOsxwqsB5qH0u03PbGOc_1_4AA-TQ3w423uDScWYTJHDi-duTpLWnM5sKNvExoakEyx3ASYJiwuDI2OrNVR6eFDkiOCGPPGqtfz85adSeiuSW6ex3Wj0opHfbwz9lIMdgMVCSNdKzoofVjElW8b7GG1tQe7pOnuRyzkpcR5M3sy3InlhNSKX8Cd5b3F0Hvd1Way5CEodmsHqnpYD2IjYGp0kVgO8csrHpg5m2jejwmJzVI7elNbpB2HfgF_HdCBPQ5ydTav1-WEbbINXukWQUi_JC9kq2KJxJWRLbT3gofAKQLBpJ_OE0S1s8XWmiJ9EIfBV3m4vUxiKn81sfjj3N1_j_AUuw-VRwKWhrouPi1V4upovYZ1noebkW3dOPIOoTuUQWvr1MR1LnjymCbh0C2KsHAUlimnBKXZz-m68V4DzY2sSOJbUdhIB2h2LDN_rPmucFjtBiUbNA_C5Msh7LunilBLYAO-h8F43TV_0-Xzy1ipiP-kH7QfXNyV0EL0bkYxSMon61n_QkMhfssKxdXBKqeXHSWugKIlEXip81PEV9kZy-YZzEu5SuIwhm_TRe3CJlAla4z_U8XWICSxuIsQ4_Dnfo-2b9AWc9mxM2qQaAuoqjd2aXeMKd3YItDjAKEXCq8zDsl8drhx7NhDl0ssPtDZXMk9rFHsZhSPU0zz5waqMWX8e76lnyJnHuQAghucbHEZsSHdRcNQVary8JgjS1t8T-Fl3NhYI_NQ4yX8wwkzSafYhzLvtGRNXEDn_22AvikWJuS5xDVzDmeieQYHWlqw8mb263udpY1bAuNKB7ue8aXIUXNZEK63tQo3es2DRS4_mPKiPLTdbflz0AkVEBcR5iRA36F7nL2K-ArEVJEHtvSHnvxou_9zbvLxeMjMNesHupuqUWD07UvxfOvrZxgBayIgt-WJI2zM11BzTiDdkbDyScoCW-bvlED-Bu4J1bjddkXU5tTjPHpcslgbHyASSy2y1NUe5WkK3FeBLgNUOERIyfDR3W3AijNipAc2ZoLxLoe84zs5VyRzFNbcMcL-wNMxTXTx8-uDKzN1sFjkceLwAhaqbYUmySXle4Qe1r4d_AWWozjUiBKzkUczxAGEbsFax6V5FSJd2zITswTnVTrD3Gp-mt0K0kyQbu9hcQ1uxXOiqD7tZwUYTG_lYSvQruUnKmO7UYjlC9PFRhSXKK3p69YjlgZkRg_n5TCYKc-xSeFrmT3GujFeQ30LnQWdE_84Rbx0hEKVjN_8hU86kezbKqgOzgOzlSCC9bpyobP3-NjLSH2I6HJwqzpLlu-DT7pAW_9bZ7kjgbBhTXRwMVvmh9PTZiXye9r5tc6pUVzHuxIphh4QBpsXcEDwXOIChhY2wysGnkVyJIClfkGcUoFm8MRqnPr-4qEBOyiKPEJNIKDFtJTmMVpcij_Co5mmnEHJVO7l997oubAQAS8Kr3EtFPwxI1dpaPLuPWiHdMycx-Z8iiPWfFrgt7CPvvwN2f2NcxD-6p7D1s9UCjLLVCKZfrTVqwLUtkmL05PENqyCtjKud4px4OGjb5ceZN-x73GbzE2NaCyMtq1TKYnUla736_v3HHdikgs8zilMealKXrBjoRE7KaR8I3NvaWIxu_t-Y4OkDTvfH2QAXqxhsOXF5cvNhCogivRHgoxHY8Logkg5hP9UknC43yGQdUkBglaBa7KXn79Qh-EQxb1vIsjFqiDRPLnIKqgjRuBD6xI_bGUZ35vfT12dzmECE0rxprisbkaZIJVb6JrbtiWeEl0Z1NzhUidtdKkuItQ9smjlliwHAu6_hznzp8kX-YFKcoW4jE_2GCYFE9RgynzD2-8HHwOXf-OGE0Dfy4D80ZAlB0_mI6UeCXRk6uKUGLuH_p73sBllGfm8G4lNlNAFBhrhA8-Wft6Oqwv0MZbqLacNCu4UWFxeqveC6rIgvOv-dFMXYOJcZEUNXckRDiKUzExazhI4ShTNh9Ju210NbwOSv8Mdo9wgdK41ItAw9EkzM_iLvH8dwgEn7yah1BxuvCAsYyrN8aCH_XEwvhIjnKf90E5NJi2f3aqV9zqX3-w8k4Fk7F_88kN8h_BU0YzGWf_tsQwZMAGhIZtGQKefe4r7ZeK-BR9kbAJkrcaTuPfi8fFamQ1Z6BQ_z1zHv3JX-JWUPzVpXR7fliGV6kNceGpRcSEryhbDOKR4bUwcUr0sRmpSuhl_c0kZbfG6oDLRNnc_VoYMpzaX8hoPhvlnBIE7woFzNGlDp8Oi7OUTw59vd_aYtA1vdk2ISZPCSEZQEKeJIkPlcvdvO01HcaHzCbd7EHULTyda2TNTQnmQuc1EfJPd8rZMO-AJhOLp0wkikNN5jZ4NFdO4cnlNeaCu4OUGZT0hoi--vtit7gqzGFRKxwuMdYj9SV0UTSUqDVG2sH4dACiAJNWnW-SI4rKKrEU3wd5FgMxxm4hILEM_CZy2BXwFG6iKN6ND5u4-kHgEqtLPyca4ig0yLmYce_gDW9dHi_tFrNuNrQz_RAqSb584hue6nPjRhzzfocSAnwscqyGq8m4hhd8fHR7pRUCbK1-OWYXefVszbT4_d_2nJrQ3eprSs9HVDNnRgseAkcM2SSE0JeDF5TSrFSqgB2llLO1RXuUL8tub2K1Ac59gr81sHVUD3eALb0xPHrP9eEQuNSb-p825Z2qnlrG8nnF2CC6HNAUd6XNR62hwlN4B0-uuRqsD4UgONseTIiKX0eCHuM-Vs1pQzstuHhU_wI1IV4g1gFmBMN8ep7LcSr5xGyFOz2zoBrxJpOcvRx53qTD_zPfjo_0Zc-pWJoRmhHDK9yRlqGPwBF_IF5J3qfw_qqYO1bADNcGn3rUdeM5wUHPkneN-U6WhgxC4g7yG2vemjnquNtMOqu2aHn7P3AYCNdMU23kPKUPiUVM5ZBBCvLgxKFZdU2736VIkskNitlCKJBpn1XJCz_UozTcvQpYg9Tr1EZZfXT3ZsO7fPq9BAJ0lrll6qs9j26olnqCmNKmuvY0oQh0bmB0vLWHFLEmjM7RZScyirtP5Tb-R1iR0IY0GnGHAhZIbt-E7VKtdPzJG00gsoTlwYodmIzk4dy1Pdp4coIWaSV_qFuAtkcyv82PZ1YNTTqQKw-8PD3QKVjdhCB9Ahrw-AupQ7loMhJXiW7RI2c_jr1p1s9PvYZlkZndsR50NIjhd4xsHeHI2olCpLB_TiLwDHk5-LuJzc0z4K9DA4_Gtps9TkETu0_A1EN_oaH1cyQmDnpcpaIdlBoQlz4RalRTbE2_AEh0PSI-xcfji2vseNBQ4z9cUcoSHutjUGuD2T2d_i7L9o9SGD9LHyg1mOMdiEy-Ids-EcZa0uwMDMQMEbQEH6fGl12Apko6wrQAmFjnnXK4mGQe2fwQDx9LBCiXSWv9gxWcXocDTuhs_TbjsXVmyROegrU8tcc4a2OU7UoBraETOT2ZXknPc4G5V7WxmIc5UG5ywSiEjgzy3NUNncP4kiJM6otw8worW6TVvfQ16y3PpCFhKkLnb35fCJr1ZNAwySENB5ouAiagvXcOdOBiQBa7Fkh4OihLuMxKFdKwhDefiVqB5axA6haNChFgp0pORASUE0KKgLHrK02iO5Gx6C5xWR9yCwC50KYkNzz1z_zPuopHA4WqiPldAswG_yL4xcpNLIgVzcZYTcrK2KcX6dgIJc15rbjXx4m_ngl4yT3pT0T6zroCJVn9uwI4oWPickSWvqwTQ8blse6_TtzghvdfVZm3RUxbD04F8WapoV7QEHShpXwSqMaVHW7U6xsuofq9DiK7BWMtUx9K1-bd7sbqevV1tDjhziVKRgoGwuSKXXFuA6xCI6naSFy10e2u28SXrXO8v8BgqAEcyTTi_H1bsUyJTA37ZI1R_2iky_UHItRFYDB7cTnUayj2j4CLWmEqiHV5gvkahyiT-kbRfuh4cWju8ImTeevxwWWT6nRxWTV9fkUIKipm4FV4HATl7ZwIHSXkBHacyVP8hz-oUEylDR9hIo5fpR-AHmy8cUHpDPyGHF1bsXJb7SK2SE7EZi_5RLMTCB_U-EA4QCkMdoYcU98Dkqet56d13Y5D20TI_Yu0NPlFqJ7PhW8q6q2wMNTb2i74F0vPUvgvgGJ5dguHBCdbq03fYgxpQjenoQ8Iw9uM4O3cbTRpLqiEwZPUTQM9hNfAAEiQWHwmmrflIbiqeO2cUy167B_I7BIlwJ0-kvkL8kTFxkgEyg6Song_wqu4rIFXh-Kn3MAAt_VtItQwJ17FRcqR9lccewe6uNJGv0G7w2wRIhL0pUCzfGLP-pD8MmAeT5h0NK4XKk2saRXSZOK5X5bAAkdQrkTF6TsQMyvu_5oAwVU_7qkEZ40HeyIftz3aNt7PA_1O_vUOSRlPvW-iAMrlCoa09-0ccdfGl-w4GALN5-cBwsuSbc_dVn9auIP7CKRPyaimG8q0Qg0OGd8pz4b3pFv9xMIUUp-qezHSx3sHUE4nkxrMTQHe27hEBk8o528e0mF3C6cq95kEbr7Yl0NVq5kszVhLTZCNJjtpAlZFk7n-3gzPFAoxaXYHeM_3xbOKWthqh2F0RhV7x51-tL53ADkzcU0WGLmQ-ORh3U9I9b7tqL8a8EKTqC9KwqquMeqPkZT66oRin9AUECWrLUFbdk0kPuRvBnJS5I7KQtbLzxtCGWo0P0VdbtOxE3pVgNMvZneI1tr5dyzA3fBpWlwidXFFnjsO2mnIGumzoofAy9gqpc5owpr0UveK1RCWqQJODBz-hpA18J27Ys2T1kKja6wAoluKE8JjXDWHlhppdXfUfS1qx0Hr-yfVQbfKgc= \ No newline at end of file +_Lv7VSktF9BO_CBCP0MdA43bAPzWnmS_blpEs_wn-Po60UA6LAD8OnQM1iQrN1v8JCJKxPop0CIA_DxsfKp1tzSL_FqAvS4JuRZuAAAAAAAAAAAAAPzDAMf31ltx0fylD3Ya_pCJ3QD88PGgK6udrf384tCnM7e-QUcA_G4G13VT-oR-_Ogdi609yUCzAPxw__6U-pfTJ_x6hf1Qnz38DQD83SZkbZJS8dX8rG9VU4SPq4oA_O-Hc7EGIBXY_NqlvsSvClcLAPxDdvMnN5aEO_ymd6MLZtErDAD88zxqWOpBX078zle6PirPZCEA_DO6I51iogYg_Fvbo4ltj-J2APzUFgmtlpTCtPxbWO0Yo3h6aAD8HYRxK3HR_Vn8WBYWKPOGrN8A_DpfDdDuHRBI_M1Ol-TDaaGlAPwqKVxpIaKuV_zlSVj4aXIfFQD80sGCFJ49ktH80BxelEp_3Q8A_KBpzluzyyg0_D5SPHaFXsS7APxwt6GYnR2N1fym-h63yJL4CQAAAhD84jJu1t8XKbj8OICh9yiQi2v8WCW9teJc0Tf8D15KLMD6Dy4AKRbHlpQ7O73LREN2huMFADSCQDeTIALFd4IYGpKuERLgvTIwyAlEAUJzp-J-1SNqHbPGIOZ1tAPNW9BctdwCM_zZo1KyR3kocfyYFOq7gbQsDAD8yNHc8kYpyrD8LZ49JznqG6MA_JsaTbZZ5rUB_MTQ_LfkNQf-APw_TeCnTyMr-_zH3rhWI4jl-wD88mFfezrkZAb82B9xqZvajuoA_BlH6iHuPH4x_HXR84M4ZxLYAPwp9N-gmNAPE_zhZRlaafWVAgD88JEOcyBy8iP8nSakFkUknIUA_MbbGY4XkAt5_JMjAf6sXZoSAPyXU9T1HOGWyfwdiJyp0CPQBgD80KsDLs5Nss38JDMyg0tKKvQA_L7EmsuRvfmW_F7Z2eDXdRSuAPzvwRoQNnMziPwXoYxCCtC4lwD86VOPWEyWSVX8OkOTEjL5Ft0A_IaEGxFUraPG_FDkL1RYwQLIAAD8GUqfk6dh6Xr8hSkamAc6gWYA_A2DHWLZ6aIj_Ld4s89c4crNAPz01bT7MjdBbPzbhdqH6e9HxgD8rxNi1ZHeMn_8Lr4hGfbyNhwA_LtvQeeC10Yp_LbxOo5gDLkwAPwM6J4_SR0k0vzly14qxRh-mwD87pj9WDsGULr8PxJH0GaWgi4A_HLnMN8nSpN1_BFvGC3Q2QOHAPzYLlE2S6yH2vzy85wweCw2YQD8fUSlMZAyQkX879UKi789-rwA_FcDTue2wbsf_IUdUfBA_7bvAPwMsxwv3exOk_w-GJ4DTDkjhAD8f-Rm0E1ovKL8kg3PcLNcdn8A_COyW9W4n6zj_IitTYrjwqhvAPw7CoOKGCjsZvwkCjY1XuqJ8wAAAAACRVnE1wSysWD_Hfke11e0pQLt5NH87eWLRe56CD2pfBZ23TvtOisOCH4znhpOUWymFXxAY86puge5ddoXT8BrBmbiHB5N3o40-MOJBgHcZPPlNsbj6YNkT3WeSr-UqsAfW3UUJABgYvH7Jz68x0Ol4S4iZ1z09WRW40YAbpVwNCIC_P3PxUFLiQrV_NY18-QWKuARAPzIgI8Fr3p7NvykIDhFvvg4fgD84VrcpXXlKcT8UlDgAkb7yXIA_KRkuyDt0gWg_GA7dSrnxf_UAPzeOkm_nAZQ9PxVrADgTKGxNwD8n5qPl_s4e9H8mCIw78GAUJgA_JBc08QOWWse_HyvvksBN3TFAPwJ_P6Tufdj8_xj_Y9W1CLHdgD80U3jlfG740_87480gyGtMWMA_OHDVPqn2GPL_K-ty1E7lPDqAPzZDg7nFwcjJPwPpSCXVweMogD8NDRtMvLVCe_8tb_oFUPro8gA_Gcgg-34XRhZ_Fos9eYsUxrGAPwl-C9VgW0XzfyM0bmASWQzLwD8Q-5s3nz2oYf8wEaqB77BliUA_DS7ZMW05EvZ_P-bJjaT1NdDAAD8uSqnVrRwc638_J7x1SP5TzYA_AB8L45iHIdZ_IfMJqJz9secAPyv8raeHYJUI_x-9X320Wu51QD89oaQoND3exT8aCokQM5iXmIA_A6tVjJjG8av_PvhH6EQcoAJAPyRQazKvh5Y-fymybc-mdUeVwD8vcNkzaNQTqr8aMX-wQrnFNgA_G3eXoLfrB2y_KUH28UXogj-APx_qubp1g9Ogvwsf7lOmDr2_AD8ygQbcSuIMcP8KSautsesOZEA_O9Rgf1Hjw_c_IeVO8RDeqkAAPy_MobRHtg4YPyrBaqicLyz-QD8Wkev5eDSdZT89tLDrgKny9EA_AR8Lfn2D3i-_FTi-zKRWD3hAPwTdTG4ErdwxvwIPkiaM8x1FgD80bjKsaKwwUj8zrFxwOMEZhsAAFFOATuc3vUR799l_98Edgj67nGGn_7Tb7ZYFtq-0IEN1pkxAb7rRhfpjNC4wzwXMJnNgFr_2wsZqaybMBF6_yABssJzRGuSIVbN3S33rL2UCbBTZ1HGCmX6ahpTPfRWFjsBxr_01tEsQ2orjFl5-7lpmf4D1qt8UbKT9C6NXt_DXRwBeWRraiMIy8hwor8_KzvSzjUgpNm3oJswdqwegK3ADj8BHO5AjwFZwC9_wZ_Msh4vkwGmSN9MXaXlEHHXC5_kMjIBnHdjoMkhNniMMw4-sk9yl-BAMhBENbEo34icNBzthBsBKhb0A8bQNbrst5tmOlf4ClXsmilMWcINvvnzQCG8By0B5WMOqs62AodhiEAcGkXVz7E9P0oIY6beT21WLSoW5CkBLlgeKfyhFLrTZFtCXHqTCiCa165Ir92qmsAH7N6qOQEBdZ2glNuD2aOKl30WjLvywe6AXxAsS_NAGhXwo5AzvSoBJDoErEpVb0fTSgVy4b04TwzHQvYxGCEbPT1GU3cqgQMBeY7UPlPsosxjDqxMY_8HOTlgtwvkydy4z6skhQq2tScBvwWikvL_IA184fpmFsCgK9WoZyfZWkLuc0FcqAR6AywBWlpSxbqZbwopOTSZ18ka4RxC2k57asvNH68ruWiTTTYBazSIh-2gpCv_uC3guUhyQ0zXL8RmkatDma4UXxrirBgBQ1ar7FBhim7pW3EpZnhMKTPLhkdRx-ygdcAWTFlQgBEBAhmfNpkmiDjT-rUo2I-hgtZAD_smwZu9-tTMttn1ziABxz2YHEHQKTlU9T_yZ6gtwrT0_5UIBKPMMwEh00fGZB8BQBtF4rdMlAUnoygkdQnzzGHFm57NDUvnNiZHSlbr4RABdVllluv0xPtoTVTFFtA3IcSGoZ86SMzH-E-CyDsZHDwB8eMeTUEJhBfZwGpJr-bL8ZJkaIGclVRg7I93gM-FMRQBXZwkleGW3NU1JW819oeKtxSE_E4Q3NPCevVR4MB6DQoB_fKOjM3bFkxRJMKDpbGtQ6DBU8QizTZbTCZgbyuVzgwB23zGW3N-04JkyDmDttm2Or-m4wkNTNuohxej3phP_RoBaE-a8tWVdTVu5VbfKaG5Q49HVjercowUewpnzAbs_TwB2g4B1oL2Dy5j7Ye7THHEQAANA-XwWG64s4hYPq30UiwBIweI7Cjj3WGJC8HCXn0u1aH84fNBAzZ0_jj2Vrp-gT8Bt-YupgLGPP2txtZ0HE1a9_jVfbHEo7dn_LJ0MvDGXB8B35XLScLwuNWCeb4vlj1_ZxCT8Dygt_wvs9PpQBMvVD8BBNivUEup1vql5Utd-3ZxN3wdmV7v_9H8639cLsPH-AIBUCQIdq8OdoXtA_i-_r7IgYfldEb9KC4Gg2YgAvoEgTgAASUcuPgjs_9IJP9lyP3xdGole21tb_Uo8V-SODhTUBAbAVcLeaQF_dtd1p1_GAawaeXTYhqd6E4mLCZuSIzUoxwQActnPWSKyuAgzhSpIRizMgJQ6KC8X7ePTCukH9MaGgE2AYAu39eQDEhCRgtbg3JhE19XhXLZBz6H7o1gnImm9FggAXnV5JmaP_XY0zs1WcWUsOFU7IgSjpgPAeZZZMB2f88aARA4pfQfpWUVj2YvON-5K5SUnek4YUY9ZjmkwK7jPvwdAYgA4XNCl8MILlsMEFpmVdfUiQpZWZlA37qhsh_nEco-AThGZTeLa2S_3gZ11IYK71hHe_yj9mt6sJWOrqbPND4_AaAJ9Bu26pZw_J3vyxh2AvAiKqiORoGYMPx2J1e76Ag5AUWLlKE4Il0sT-TMrwVwqz7L2HCWYE4ZCYmtLOds3NgxATiwy3ujx7BiN22HaoMptA_fdDTKzfOLPGd6lbOzrmUQAf0P3e2UwNT6Xx1046hhdYHjF62JRjzIvPv0ShRwz-w6AUEapLZ8vsztyDKU7-tIb9L4aXaXDJOP2DcUpnfNsuAWAWcdV63X-tF5d9hs394hBw66eEQLxtnDBrVJJW2IUkUUAVe_0JCUFFX6DKmll-Lz3G4fWiy-2rI0celeXPxUil4LAS2kjxLCHBNZRCpMo9ilkgiCrx-8KiFLiMQUr3g-FrwpAUXse8dD1hTaja5vkrHm-ztaPVXyVqRmqtVIpxgWdtwnARMsKovZhD4yH2KK3-c7QNQmTExJvsIRI8ZyUJKJ0fkDAfERg9JMMfQFGTXbeRgVkeMetMDK6amEBW56cq2n4hchAbTrVmoeJqsDcGUb7-neNhr7Ly7W1Iz80UP-mg6Bou0MAXsDjGcN9h14yLw60ekYZY1DVW-dIcAVi57m3H9JNxUfAcYvMEXc4esVd5BKcumd42KElwJsnEsgjdkqycFzDgMRAdApmjXvDn5Km04aKftmF6m7rIoD2n5PzUHr6SHUpgwSASerR3nSvwYlUBy0KlBYHWNFZOiTAo5dVkI3z2yyL-sdAfOQHxMTtoNxfP9LB3236s5eG8X1KvD_fthvAV1Ht3ECAeTzd-I4_Iy7S51ApC2RW2NeNtlJmJ7L_dO429Jz85wPAROD50odO0Rw0NpHL9dkzxMy-WU4ISaxtbSP8CSrNRUJAUQy1wsqML2GTNMqW0yJ112QedoxHgXShqQpUKyy2egQASt6ZqCh57EqoRzZheyT6cH9PShnac5LWRSMVsZD38AQAY0z81e7xNhc3RNrLL4QTFhXQ8sSFMyGZXeNvDyumCUjAAGk1nmC_ZaqHmhLYkxP6qIDA3PuDpIu23PrmtdCVRiEEAHe0LPZeFufpTQNZiFqtQEUVNLyzkJXa_SiJpB9hb7-MQHaxVAvF1ABda3M7kE6R--76jHvEjcK6DCY-PAU0p4NCAFvp-PmhenTcvdh9GH0-RXSvCWbH8yPZW6Erzu7hUqZHgFY8ImQbAcgQYkOOUTNpt1nD1YUyUXqpICBXxAPBNqjKAHIguxEAp3h3QC4i0azjFL7ap-W4B57h_8X3f1biqTZGgFT_AP2WKkWw6z0vaolZ4cWlZ6auGRukpD93_mhmOxQHQEKXC7VOlNxuAAzhwixfzx45YF8cvh8GE3lxpfHjckKEgEnb-U0ralqHEXtD-MWhnjb1g8UWb9z5zv8SdNN01NnKQF1OWm2Vruc7NujJETlYYoaJLKi_xrYNK0XDZEktjyOHwFTtu7bG9EQGPjqehLy3Zc6HugW9VkSwi-qG-GYQTw6MgF6WJ9YvhBpwdJrSVPp2l1irgmXSVg-QRCeaIPlqHP_AQFumeDf2l9T5BvzeYXj4FQwWFTH5Xe9spnConWIF9DdAgGTLMPxOnOo1ygtnH-C3BC_Ixnsrmfp3pnGse6SJ0jqHwAB6YQAl3iajj-YeUjnSkPz1ex7kH7wMcGI1l8oYtyrSgIBPRwj4YDhjhfAGgDCI0P8O9tSwI_350BVbwb8plUytw8BbCgVjVOWC3miO6KmKC8K1kxxdkzQ2patlIJy9aRRayQBMYshaN-fIt7FyGHNrZBdCa5BbIP9RJ4g_V2_PE4xeS0B61p6h0uSl7-T3rZnFJIR0qt4G3_lGXSAIhFnDPhneREBRYTlGxyX24EWUNO0cENfXTGD_gQ6jTu6DZ1_AAmCiwIBSiPotKxo-XXp_9dGi1aEDdVWSLBD69JL_ajIi4q-njsBU4kefyP_iQZZqtrYQUo-JpnAen6dfs12maDVYteWvQIBE4dRZLXtOamolniM0v4QgejK26Ak6aPxu3eoQooZLSMBQc8UulO9ussbT5Gxxm4Y3chN_ptVdHJN88CTDkRK7z0BO24bU9nY88zyk6Zkrrin7uYEBx68Ettp_MPyjbDTjwIBRjV1a_1vX4OS8QgcmY9Ah34sIkiYBOdtX71V0lOh1j0AAAAAAAAAAAAAAAAAAAAAAAAAAHed12TlGYwTzxrguuNAAJzFZLhpONLhNB2Gx7Pb0CsSKeAjmoqVRQEZSLD78WeVXUu0ITaCIUuMMVvAl3R0_w6GDv-eqw7mvFr7iQR6i1lsLIb1bjydYAq_ixBAUONeKYIkd02pujEfFWjs4My-ldzQ4HXeIGxh731juURIIkU4Z7JsCFTi04BcWxczW5nXQekc3I392QOIt6q6qyO0oy3wCy7YYjhFF3vl0BtpeU5KnZ3EMkCMhQwzQ7OZG9dQAEiOkxTgSS7-V0c2-G_886RRLCr3VsqtWLsDlPL_R1gmOZI80hhn73LCsEme1F0op-izG1T9GhA6L6weJNYG2DoFbJiXnmzxSL0JOvQ1hnUic2y7-8bsARoHi2-lrBwVGPoDApTc6RQK1zg9fS-WZ6aC3EVgdfEm_5eP7_RMaxUzZkwWoumc2TePhplIsWI-CWgb_acgtIZ-xlZM3FZ9Wgqc6PViX4T5xXtb9JcQxUooC5v9sjCb6aQg8BhzVGWDElwL2ZDW1lD5zC-5D_bcHViMKMXN2JI03kMpONDo978__01i7kazQTLbJgecfy3qlQFmLnTe5cqXLa_10kEScxixIdEejDcjrOHjoAPD8pHqr2KTUkD_g8V6r0RisQ91DYYGlPOvapnEecSoeDAhhlyJtKD_VTQevbUGBC-JbWcdA-et0qaTTsdT-ANRKfzv9GiQhDSz05HP9zYYxT7rFjSGrhbIU9vCpy8JAU-wNTozoZVkN784sRcj-TXPnYlHOCQC4GJD2r_li4OD13znJBjpdki__39yRgDnTwqIPa0kIX_RFGZWebMkZqUbdZoNBE--YtY9q2NrcnpnEQTiSTqxYhNKMnu3lPmF4UxIfesQlV0KjJYoK7cnz3WYNYvvG3ZkeVJ5qdrhXixOyupudSvA5hkZtG3_KajJQZn-UAEgVDkMzRskitCHTeKeTwP3Hjyp07z6C1j7AL1lam9otQxlm-mlY-zBU6md5xv6zSZzmsw8We97sknRZPd9QZX1Mjz5ay2MbQWO6KRcm2elpIvi_V54B6RStVh_V1ClnyEJ0n2pTgu2ibzH9WAq7dcK8HjZprRXN0LNq5vXxfJHAjtAtj50AIaM4FPUoZFUKXHHeIHmeYA4bJj8YX4NtE_VMgY9EcTJJF5JNv2H3FgvAuj-xiyrjq8-Zw1lgoDEBtUKJ_p2-QRPSVfwqQtH6TQoWessOuPiTzH2kqN9yTLTFj5ErKm_RaJMobOpuY8MumC8Ax09BpFG0tJky4irSQg_BWMfgyKzmqR8VmW8yNxyed9Hr2HUi_B6biiDMnf8kWkQABvGpDRBBoHyed5nIuEyfTPPWNPfeSwRGI4qqqkY3B0T8aV2Pi9DFplSFw5MmzylZfpkg-2gh-PSRA8uEcTXmBsTf83yu6plViSSalm6JeXPDRwwgvCaIx86dYueXgbhP1NxJc9FxwntEWEW5h1hHlFSKFlmxBUkfrBpr3pm6xczYP6gt-z7z4wBAopSplv_QL6BvZZJrdKCOlWHkOXFLRVs7okh9el30fqrac2gWyvyWOm-vFaLVJYE9oi4f58bJgpBdfIkANIz-TlKGFv_OFnbuv1Z4g-X_sGiHC_axGMnLSFzBLP6IYY3xPrsPnm_KIvlwLIl-KpVn0SKm-LX9SaX2erLUIBE7MHMq5ljNrvAy_PMr0P9pL-CNAXBK4aOJ_ZbVt5KIOj6yU_awcK7c-DEeFZ6HAaA778oRCRQTjEiT8UHJ6-L4xATulbzNQ_Zw-vbNS2Cj78YSOB0m39vGxFdZP-b3uOQtChm65cFj5huZVBTwy4JRTrKNEHKROsbNFtac0_QPYQgV1a-Qr4NfNPLk_ITm0EgwMVycAf350Ynp_AyWOjhTHVUEuSBuXc9DGmqj46zZjcszUWHiqtrSAgbHcCvnmfu0vABQuk_RcbMdcQieSPwoL2sai-8lIyaKBSJ1HacLGt6qJSYGIA9bgMFj-hWTf4JpKrzS_rTNX0vAAnxFq9w_sFlkriC6ETdbW4BEoH0QWt7VavmUB9Djd8YDYJrOgGL6KyNpSSfde6DQAb7Spp4fI_lvS0EZxEqBzwTMhTN3Oa2GYR2Qur6Z2OpMN391GoyTZN5flaFIk6wBl9rTtNw08QVZLmb0T-H4WakwUxIxNYl7j_2j5EuZR0MGN55o872fA8XwUcAyrTkO4DKvuTrXe70nbu8D1b3XzpfFkko3fPku_8QtAU093dKLyfwsBQcWl6MoniggXpEMcSzy3_W9kPN4ybDklZncq0dOyACDMwzY6_EoSWBCMQbZPE0jLGOgJxdjvpjWH5GWj7KpQLxN-QByZ6LVLJBWCqwtbxWeZT0hRTH3AKAjPJCdKtXf5LFjMnq_22quIXTIjdqgD8NJbbhHFbKrupAeGd-8TTh6wCPKyWzqtDhUP4EqT8SFUFfh7SSlV9HNvIyz-nW0NeeBkPn9s6hJAHjiS6tYImM6fc8WVD3HNnG32vEy8oDuA-MMfSpTh2aFjajHCC17-3OLuyLaiLGp0N6Eguxa2tq6urcysz5T2XUYuIzdNnIEItcHHvOp-Wri-wA8Ugcg0jHTF_b6BpxapwMIj_2mbutu1TyGZC4dJcepaUlfBvZMC3TEXod40g-hmDuEdshnpUOz4JLR6ayMs7USWRsDtGs-HvrxVQhsZZGma4Oz01dqQb6ivkO6NTJlaqdlR05imuIBurY85RtryHTBSxqMZ4UUniRfky7fgWEwj7zJP7UbzJmQTMRgwLl-spqMqpVf_Fs0EmBiu2ZLI7va7u_d9ATPwVgde8NyonPwmYc08Yb66QCUZk4ixXlyEM7VQkedhTtQGXLjsIFIidvFgIPpzQdgQakelPREthJJNL7MmmlS1zDyPxCJzW4aA5aEi4jECPsrmoFjzI7n-2fvZxlPoMM4ZxGkbEWQGGyZzU0GPXiW4teVHy7l6Si0AkFJFvZsSN3LEmDQfQYLeqo5wD7DBwwSVE8_meeWZQZxeog8iRQLGH866CE9Krwk6cNPBNcJI_vWfwgN_4TLdpwpiQjHrivP5kzbVjOBMuPD749UxmRkpc7gvV_9d9VwpisYnv39DdAGkJ-Y8TPmT_bniVJEpOAjtUR-Yc8Z408orQn18Hc6fsYKyVOxw1w4rZjNno6LQ1dQ3_MHwVnI9hh6Y3B-YnZRy8OZDCTstz_bB88a5-ZC20BBbdT_bWjGcR9CFbKvqUThoEjOSbduka5Fge-f8BrFyFXASIE2RjK8Jk3tGHlHjrKZUAMlcfPa766HwB6C3RIgwtEBWRzhSgFbb0rR7ccQgSBNIxHdDYG5vrmJ9dnWrLnzkT-hAlzdSO4XSyk7wjoaKU6eF4s-ReBRSUaCgiNEBNtBHSuNPfZOXlwD4Qaum9TPmE2NVo-4wE95hh8iJdFxfozhIM_DF9aGI6aphEbCxjCl2Ih7_-gS3CSIiqGzr0Z8HYfahROSNkhM3noCvn6RUMzkMlomu60k3kono6f_UwJCkUvSuiUiWy4zs8X8l2XUEnl61mw8_K1VDFk_wz_2_e2TiFI0S0k6Ui3y9CxzJM-8f-2FLwmSlXLMl-yrhEkx9dMldH1Os-mIEs3VEAyVZyZ3BrMSrS-Gzsx0Pc05eJPXR-RNId0-cvvuezBUv_M_6hKePpfgvi_PCS-8wXaG5Bon2-wfYfzXJCX2qCuyHFKWowJ-c_UKZ6hI-O2KHTAJOSG-bO6PFPwlhBxUmDrouKywGgRuMZodM0qt538hEJLWQ8QTXukrNZ2r1oR2r8aXbiinUUPozT9KDt_jN1IxhYjeBMcrGscU4pN1AVQIt1N6n5aHP6GP2w2JH1btTQgZmK99J3MM-OdVxg3UwDElj38-y1Oc-t858EZ7KHvjRP-I_k9TvYPNCNNhEHuBMKXkLde0joYJrRbeg7zx3Kncqsd3i7OlNFtFKlkGAR3f7TnudKNz3HEawHMEwISzN09stGtOPZj-mUeX6--BWV7mxhukcqczU-BWQ4WMRVauqLxOTpMNkG2IKkZwfBFIB8ZbwDTHOZ0RKK2vR8straCxrvdX83XxyzSwDXRK9gkOciXyXqpdA_IqvyeP54zJi-dNTyVDCAiX2Z2SwuGArmA9PNbAEJAKoC_khU3ubP4XHkHqrRbDibwBKCIdDsXOBlNgBik2Npd5rO7zwGUMLyEtvyJ6Aj2-rncu4-Nn0o9yDi-VsLClnYvQNJxOiPMud4ipaGMkr89XFIW4t8tePEmK0dTyaPKN35y56UK9gkTeGIe_LcaMrFO5yKcD74xESvtvQ8N0CCksWij8AY5b2AqyZGOqM5Rg1mW2SGZWGkesIhlBiVRu_xIBX8HH2xJJgV45e6gXOk9emStPl4qYJv4XcEbWdo336kErJkoHqr8SgBg37DUTVR7ALQ06SRNjNJekr6z564GjY1Baj5CKFvs5eJcBScWxQ_nA12JDsuw_xvI_IL--1cpweYwDK_TiXu6sq1dVxfPPRsrHYXHy13Y0VjAQZCjMbNnmwU3BFDfSspl9OA1PVJtZ_3aB8tp0cbqEga-qrsMn2uHviMAgWc7uckIrg4CnhWKb5zwcFEy4Yw3l3Cmj8R21V7pvyFV3E67XuLsFwqqbhz6uKFa2_Ndo3ShvnseX5OGJOc9EbKV6Iar0aMTornNWoQYcmjqotQNk19a9p93l8au6cw38qYKRzMp8tmeAFe8vmofSPt3gwscWDjWswmbuQUOdSHYz-pnyAMl1SZzy5Zd_HgLO0e1ntG5UILOZJTZi9b5DruqNOyfbTJdgVKH8Syto0kSWLU2YmrDeAgsXsznvmExA09zPthYrmQCWuOKb6vwA3j9Gch3Q2FA4rfaHGlGlCQZdDN0XNQV-keVa9Wt97rYq-dYovFBzIcOU6vq_SJKAteA3tUkstrRz_IEYyTqtPEAJt-ucrTYzu0nma4VoQ4-8otwNxlmdfYE6lPDUohJQsY6NNucCI63GKJT8A2rFDbzf78YsjRO6vI5uIXe-DEyv9-hBUrwGjT6YsNrmt4MCgL2MK3JP6Y-U1SbYTPL9wjiHsk8jBEZoHHNRNvklMY22KXPR3bCrmagyLQyfT6ah81Ehxrv2cB43_gB3G72SDwwhKqqgMVtrT_DlKG8GuoH9bwvvuYmqESQfQ9WDZQMBQDJsTRo84hkv-CxWrTBbZ2PdrruKxhfzkZxj-ougDHLHVFrFFK_ezKXmtKrtCBovKhCvCt8zA52Xffd1DHAts8yc4QI7jwP90FOC8Zd70aCkrNmzlyYzZlYtYkjRa2N0DQBnC_zJsmFJ4vtLTpoI771h4irtW75Xw93ZTl1XAynFcFfb6NWD2Zz8E163TJFibI-aIdtvT0A1_9pENFFOxY_o11wSl_FHuoGhdGW-4hAJoCvuBIMRX3zdQab6n6-QAD9ANuKhlVqUj35Zej3C95OxGI826OsfRA5Rrv3Ri8zAfPdLfCLegFD21mqw0ukpJsKw-TRtSg2jWASMSeXrXonGErjkCj29OzuHBf7Wgb5_w4kEoiwvKzGc8YTDcw1qCT2kBnKndwaCG203XWjIUu4QZD_CDXds-vTDhxUnpfIKHuJDE92l240_5LEBh7jJIACvYr0NYCYhx-fpUcMbtcj9xPA0-PIlzgPox0fOCpE_nEERpIaNccdY9XsnXRpARMepFeLjrfXQO1IIBPEyb7Htchrab0R0QOk0-U4Hk8fFA9NEfXUBVWNvn6YAKpFHHQkPRK_xCukDGizB7rVqXirMlTATJ553OuvnrPHUFR1DC-Lsa95MwnQn3zr2xeMAl4lDV_NK0_V6dA6KUyKLS8D_LgF_kaREff4FzydNewVQy43YRVIsMqhN34hZdhzvWD9JH25_1zN4DhC7ltxZgaMLLryct-9KcZ7DjYMdfKr0O3ngt1MjY6vTZJzIq04vjAASA5rTSBrce1Ho9aDKXKsrVHtqnY8EiU529Ls6xJU-x13PBW5NOQPpdlxscqFpWG_U4dw5mT0b3WLEaV1M95LES01XP2lNET-ipNo4X0zqNDGOWXyPjGdu1zw-JL9DPEPlcPPSTsAeVJQ97eFm3Q_2eBdmmxEhTEC7HzInHnIBhBadeX2R97qSapgwKTR28JkmgahLoushxNgjjvvLT_pHwN0djs4DSB0HlEgBHj9Sw1VN0vTUCoQOLCudZxCExwTUUP3rhQ0TjLjj67Ae-blgdQefsZyHYWVzockt1495wpREFhnSrzGdiQff74UCW14w9ZD7ongc6enzx553NFzMOJRUkuogQt1ff3wKV9dlpkd_LupBJIW30boqAbWXy0m85YwXXzDOj2wAUBr3_mpGHh5n0Ho7ZVCMURyLsT3ohqP2RjumMg7kIN2bAK3gMz2cVUk8PpGNfBJ4GzjtDk8DLysLDs4myVAzGYxibF6y7QOPlgx2DAatG2JNsl_JsoaRxSRLs73-thhqaguFAzUN7IxCyIIamwQM7kol9ffxiIlzVO2ZIce1P6MQuyGSfMpELmdDGkE2xgWmnR_lwniNsIs4ht1n8roZhioIY_ZpMKk5aUl56p30TTs9Mx29VozaGyTQr5e6o6GpvHXkZ7h87vd3Y9xCMsU2iKP7nQSNR40ovj8m24tee-LS5zH_xxYT3G8b8hHydP7067Mbc3tIw4EEjJybQfPp8qIqyRy0pQKIpcafJx4ARemlYjX-2Ewd-bACrrbM7oxab4DQ0-0C851sObj4_cR4CuCyIRipR6y8fIwNJaNmT_raj1ayoq_sfpXjqmDtYJpm6wRNl-vC7sIvEgR3G6QnG0C89cYRYdgz6TSRgimkLqaH9SEznMCvV_1em-52VNaLHiIV8DwH085PH-BaIRCDN1BbCYsozyHzStHEYwqan4FmW7ayioQCbc5G6ACByZAEHK-1_y-GxpPeyxV6JvbQ3xNgr_WfjBMIdjXfEN8i2sXaH3smmoVjhGT_NT06UNwA4CAD8uqPzx_GffPNA6pMFosv2f2vDkqwXt82OZhlu-_XLY_DXujdkVEBn7kHkxx-emxy89iE6YSY9wZHPYURfSTzuguUm4aMmd94Xv1Tyma4wGz9WYT1Bw_IfPEJzd_BHlhKM3bKH6sHEeISD4BehiKQRhKJC163kxCvYnWE7ik1ZudF0FIFKd9vGvThCneK8OFp83wLc51vftIJ3s5u5HCrqOc6vNNK8pv2gYP6jF52ZfCX9sI0bHjXDS8JquFsj7_dFe_PSlZLmm2ycOKBGXhSGNvRzQ1ixqW-U1A2txiiT0zM-uiwXtpvqi7TBXShzuSwvfVDKxc1rnskRHFU7ldb-xWxSgdj8AHZlwE9d0A1mjneVMR1dkWM4HowBxx2HnDcVtB7cv1yrdmY1J4tNIVD0G18Duz9o-cCh6blfprZSpDLlzmddrXBuTpEry6teuqHhc0G1s-TGFTbaBMJ5ltpiWfrOBG55T50lcgXyydD2vIQWYA1HsNdbJbbeOscVIocUUwoYXhfFNONpkEvnk23Ipo2Rwk1JAuQndlZL-lDqj2FlmvBI-qZTGwHbwyH9Q9MFg5EdFA1LhPXy_3uydvisRx20MDMjT4xEPv5s5XCGgENI4vrUy2lx0veyUj3dYSW2jBPxzMzWmltOhtqpMxC9APxx1mTHJE34iQ8F9YLAQAh_wN-tgvZjpXlLjaODSZqi2NH7KQrAm1fbgVA33tCHOgCXD0TM6jc1HBkP1Og63G7hQtBdby0WrUZpEsjhc0jLLpzBMLFqwUl2eDMGYc5gxNKTO0z6j5B2xa4iykH6JhdQ616LjsigOycknBIlMTJxVAIGqWgB3bQ_0HKGfXouPSAUSgih6a72ms7otgknhV4e4Unxksw5IqOEG3EHO7Nz9l3ZT3K5dvYsxS7JEkGQRgqwE75Ac95gcY2V4RC9LZUnf2zWVYALMv_8g8F7O3aOm7H_-wRds-fcVnlWY4he0QJXt_RCanH8t5f212cA3uUqsv-2E12A-eJ0IkijD3-nRs-LhEPNsPRTTaBt55ooDdwiiz2ErA1gv8rDErKbpa7e14Pwop882pskrbhdbzzo7IHuxPk2FLA6G40lqC1PMQlhXZ1-KHNfQ8lEaXs09JbjUxvznURZrPcuORmThMNvsF25dNzcZORW6nw_Vm1h5CcBHIxO-X8LBP-hnKgunh2xo4yVXvy-bcYvzKSBLQIb_qEMh7n2QHPwiG3Ulcq25qhDtvtDASu3Q-OExOkWS0Ys0GWZCwBzAkx5xALAKUUK4MXuFm1rIzJxQ_Jxt-iGvpby5Q8hxcaAPXYgqvWAXBT5qlLbuCH9IJWnKPY7Ge2ONXHV0N8wN8hoXdtb8dsPeC4k9oN401yvpXBs6dN5-u_zcBAY0HmTXeKpPwh9CNQmnxKHL3YQ19u7xqXVrhtVOPfgTmryEzwfeYdoA4MrTzT1z-_c6noSapQTh03ZByx3vrGyTd_zFQ0jiN-7lgDVVNWFW29wp6YjuikwiaZt6jofAloQA2xHIIIc3BsJ_a2Yjkao2_wDi0uMu-tXxYDeGs5jA= \ No newline at end of file diff --git a/batcher/aligned/test_files/mina/protocol_state.pub b/batcher/aligned/test_files/mina/protocol_state.pub index 50a73b8d729a21da8406a18f4638bcb0bcf2553d..939ad9bf0d6e45ac388685b5a22a584030d97162 100644 GIT binary patch literal 4184 zcmeH}xeN4c6~!&T(n)$6o|D`{DD*vqc?RUT9esBKbFUv3d?ibWgzU+M(pI`me_{rBk`Twnzl^0FKzp7+g`;8j@x zZ+&>1W*xkNEz=b|Q(m_)K^|fO$W!9+IunBi0FbFeO`b}Y44qbt+X?L>r=UlPh0EI- z294P(-Gpz~y#(1ch|)(A*naqkZs&2w#Oz)+o1|`_kzuCuuur8L&uhO1=#lwSS9h=k zM12-EOJ|!H{Ujv_RAOq^wzE6B1A6LVXR_^NsTqIrm|b?SQGuocr88d zxtXCuVmVTo-#Zc~#uw>gcM2Q>D?3G^yPd>(W-Wu!j4rGy)9I$c zFPN05a#>z$Nl%xk0#GcJbb>f|L zX!*LxgcZ7)%E?5iC{DXa7KOcLy}|ulWaV?B0wg5LR(#oIBb&+TA$V4xm2yGjTfygR zN8C1g_wJPqE~0v6y?7K^tD!d8O%*$ujL za=pZ!+C>ag(*I?}zvt!0`5~mv%l+^s!n(MMP`~F#$;)B4Q|zM|g6{ zY}rJJ4QiTPXe0=9Xvl5c<1iy`Oe3~V-FSN~)5{LpNQ>F~Ev!c!;hj}U>?lu^8Z8{H zBW=`Um}hq6o|!8X7a5PfDxc{Kd{CW%$@Z%WW!6Q=R$qgF!kn2>9`bETLRY(~bC)G^>(9;ve>}N>$EsJ-S z75u#uD22@N*(c?Yb411qXSkQbQvDMO{FSyJjA%V7#~J(gW4tkhs_1KxFJQzWD{E#sN>QhjCaDUwjqmAOi^ zwIf=Y&9yf+-fm4REeOrxYPZ0_W#V?H(ZXAtX_vKwa!!h?$nc>-Epyc`mOt?8MNLRU zS4kVbJP@2Pa}0Zb$62>9x%)D|0%s5*TewN?s=<;}$`4{4{fWE+4kfLmEgkD(U7OjN z9&>WXZ|3RwPSyaaT#;%*nAgKKu4F_#0$~~=QdTfu<2i81I9%`s3d(K|xga{cF9GA6 z?aPsup~TN7tI7@?>!9n;p_QVUbQ%GY3g2DA&IK@fo>f{yqFy+x0j^zuElGw!311Rx zeS^{~D!?gG1En7%=PZ03MI=o=03rp;b9{#MngL||`dLvg^hm?3Q(`$lGySZ1_FFr-fcu?__FW#S7c?T4Lw|d(y=RFBCxf zHVXJYj`Ql9^xNAXSRf41IT{Zf-=i+1=6Il%py8pwV;Lq%O3!23PR(5zmC?2aZ*8p@SLQc0J-38Qyp+>IN(O^v$}Eu^%Tqa zNcr~YQIExX%K=bH4Wqo5mWgn7!mC_$WYs$^)zt=gPe|h?oO^QW`O?>sT+Fx?(YFfv zzWdE;M4oV*hH><>-F4&5_i&=Xke`Quq2-OJq^yna)- zwKHcq27%MyuQ{d&=$RtjL}yt?fenti7t7H=<>cgG+(KY)Cyq)$}1053`bv}qq;OjN;8bf zW*$8@)h~WeqD7dQ1}pe`e1E9ffMvSyx-AdBa!w!?E;4ST8)-(o%M=gE$k%od6{d9C z_6#&h<8FdXoFy^V1nY0$Tvr+)ZaF3O`uN)KL9`XPk@J_y^9#z72|=6~;6CKJk3l6r zRz!Z=_jQBWY?^3&@=N1?odrHVKdbVnEHT8mV{^Is1zC>>P_SF2iAZ86bY$EQ2B}~N zjSX|BOOe(4Qu^?*|65rgq=A*k|G@$>f3gCLrOoYO+~=A%dae+=n4;xQmYHZPff~C; zBRC6_@~3~YKq%0A$T2j}F=Z-BLd-E~M>9NWSuf#a6uJ@ErI_$6;DAiw6=I0qR0LY& zn+5JQrss(FcNQS7uKMVW^SYX>R=C*AOu_IY6+)lLMqbJ;VN#K)WpDcz= zClX^G#eN8~wbQl0CUu!^J|M#&DIX&6^jJL#nY_wcZ$bSfLNAbDJ8oz8B g9^4tR(#c*Gd!yh1)D4WzsS>!I8~nE2zhr^G0VAU`NB{r; literal 4184 zcmd_tx$gW%9RTougOoQ&!wVo6>s{aJ#<#ueUEh~PVS9bw_d(4AgeE8y6rqETf(nTj z0Et(i_*X=NhJpgIzbGxzkAfm$8aLL=c;=L_DM(1#>|g8 zyNO)^T3U$h2(gQK-6d5Y&3lHMW(SPZG(`+9Ua0V?ccp-_1kx#5so!B9&?j9IyP2PY zBvgIaC3H_EMrws;M&!7_A@&j36)B(lRaj5t9YUPGhVrzEv)!UD8L{b>GqO`uW|^D1 zV4M^|zEj$+RUDDzoP!mTEyn5-X+zDB(paoXjOcpOL`CYq*fp0z zvpvsGD|X!t=J^oyclzK;p}hk6ILMIU>w=2Okk2Sq5sl_-3isx|%PTCCRfR~~rHn8% z6kNL+wC+~l%RQod4t1`Ht$3FRks4NOcE^nhG0=}D8~vx_OiVd@7gJt^*3A5IYuHN8 z2;vfL(&iQSb}@*nk!ViDEU?JR#S~b#+NbFhe8&d&+AjwV1{680aB~aWjE~eA9?xwJ zsaN^Tw^3rOP>jfv6SI4)@iKc1Q`#$w!@FeO)bid9Y?4gwI<+`)H?*>(jj=aWrDH6K zsylJ+T*gCVw`5@`)auP<)BC7l>;l_?=p1B6SzF;BQ6AlyME0M%^6bP%ENs=;58$hT z6~5AdAbaqwZH5eG5bR?I^Xu)J{k|0LqhD6`oEl7NoByPPUU5HXxSqfE02{0bTMH0ELY)KfB z4yn)@pZBgWy}C5DQ#8zQ^awLZ^M=9qp*7)pm#RyNAm zDK;ZLTY)S3D0+t5u%oAjO9`^D@_OrZ8c?s32Eu@I9PU0$b;*pqChW3uOsV;_8f&{ zPwkV%c&nx0CvM9LQDaRCJ@!#Q40&o(Y%iZ#TED+RGf_-eA)&769$;vU1Ofow~GF~^*ZTV8WK9wA!-prScQ-eMtvB9Ma9t2mCcqk>m# z|0z`Mzx?c%U;p&C^e@8jC)Mx2GXGKi-1w^f@sD!t*I!d_Rt>C`IC%4O}jQBZXGtfnbk&}lG zU#Ruz1-96A8Rh!0|H1+;4i$l64Xzt%~5-B3a(-^@)ojcSh87cqT(^D0ak}Kz6n^ z_2ob!PEW~DlbN1lc}FEh1WQj^G>C1fXZ7|p&fj+{tlRzQb|h4yX>nycL~=QzHty1L zxRL0An_MA^Fnpcmy#is5D08alE+0KdR!z!SJ({~~v=jTQ&JHZ@cd|@_J{xBMg+XH0 zh~b+CI;QcWTYQk+6BzD>Vz`btbz2&hMj6pIxs%)=6V6i59PYeutFO%&x*u8wbI;o% zKmkfl=bNJ^)P*ohn)3poq$D3nBHp)L&@7KWW%hhr2&rI41GT^JR(OB@>=Lz(h-7yz zYeaaa%03FJ>nv5IsLW&zK)*tD@cQ%7-mIYAXu(|H4Eevc!g@~?>{~0;?RxkGW;$Dv zoEqN*ajgWdhMIIb+^TT4UUa9xiXjO?p#%4=6*%tA31HP2uquos#T9?!h(~t>wBo+P zlI%j+5~O)_k$OX$w)fHS)Nt#Y6$GmHL&ZG5Spjq0Z~6mE*-QI>Xa&00^_qs+fFw4a z&nn0EK+t;}vOb*a2v8m=}quJfHO Result { let state_len: usize = pub_inputs .get(*offset..*offset + 4) - .ok_or("Failed to slice state len".to_string()) + .ok_or("Failed to slice state len".to_string()) .and_then(|slice| { slice .try_into() @@ -106,7 +106,7 @@ pub fn parse_state( let state = pub_inputs .get(*offset + 4..*offset + 4 + state_len) - .ok_or("Failed to slice state".to_string()) + .ok_or("Failed to slice state".to_string()) .and_then(|bytes| std::str::from_utf8(bytes).map_err(|err| err.to_string())) .and_then(|base64| { BASE64_STANDARD From 1e86a2a45fa1eb6878d2c66b038b73b378a2230a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 2 Aug 2024 19:02:18 -0300 Subject: [PATCH 3/3] Update batcher pre-verification --- batcher/aligned-batcher/src/mina/mod.rs | 95 ++++++++++++--------- batcher/aligned-batcher/src/zk_utils/mod.rs | 4 +- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/batcher/aligned-batcher/src/mina/mod.rs b/batcher/aligned-batcher/src/mina/mod.rs index 25765311e9..d9329a9f8c 100644 --- a/batcher/aligned-batcher/src/mina/mod.rs +++ b/batcher/aligned-batcher/src/mina/mod.rs @@ -1,19 +1,19 @@ -use std::array; +use std::array::TryFromSliceError; use base64::prelude::*; use log::{debug, warn}; const STATE_HASH_SIZE: usize = 32; -pub fn verify_protocol_state_proof_integrity(proof: &[u8], public_input: &[u8]) -> bool { +pub fn verify_proof_integrity(proof: &[u8], public_input: &[u8]) -> bool { debug!("Checking Mina protocol state proof"); - if let Err(err) = check_protocol_state_proof(proof) { + if let Err(err) = check_proof(proof) { warn!("Protocol state proof check failed: {}", err); return false; } debug!("Checking Mina protocol state public inputs"); - if let Err(err) = check_protocol_state_pub(public_input) { + if let Err(err) = check_pub_inputs(public_input) { warn!("Protocol state public inputs check failed: {}", err); return false; } @@ -21,53 +21,68 @@ pub fn verify_protocol_state_proof_integrity(proof: &[u8], public_input: &[u8]) true } -pub fn check_protocol_state_proof(protocol_state_proof_bytes: &[u8]) -> Result<(), String> { - // TODO(xqft): check binprot deserialization - let protocol_state_proof_base64 = - std::str::from_utf8(protocol_state_proof_bytes).map_err(|err| err.to_string())?; - BASE64_URL_SAFE - .decode(protocol_state_proof_base64) - .map_err(|err| err.to_string())?; +pub fn check_hash(pub_inputs: &[u8], offset: &mut usize) -> Result<(), String> { + pub_inputs + .get(*offset..*offset + STATE_HASH_SIZE) + .ok_or("Failed to slice candidate hash".to_string())?; + + *offset += STATE_HASH_SIZE; Ok(()) } -pub fn check_protocol_state_pub(protocol_state_pub: &[u8]) -> Result<(), String> { - // TODO(xqft): check hash and binprot deserialization - let candidate_protocol_state_len = - check_protocol_state_and_hash(protocol_state_pub, STATE_HASH_SIZE)?; +pub fn check_state(pub_inputs: &[u8], offset: &mut usize) -> Result<(), String> { + let state_len: usize = pub_inputs + .get(*offset..*offset + 4) + .ok_or("Failed to slice state len".to_string()) + .and_then(|slice| { + slice + .try_into() + .map_err(|err: TryFromSliceError| err.to_string()) + }) + .map(u32::from_be_bytes) + .and_then(|len| usize::try_from(len).map_err(|err| err.to_string()))?; + + pub_inputs + .get(*offset + 4..*offset + 4 + state_len) + .ok_or("Failed to slice state".to_string()) + .and_then(|bytes| std::str::from_utf8(bytes).map_err(|err| err.to_string())) + .and_then(|base64| { + BASE64_STANDARD + .decode(base64) + .map_err(|err| err.to_string()) + })?; + *offset += 4 + state_len; + + Ok(()) +} - let _tip_protocol_state_len = check_protocol_state_and_hash( - protocol_state_pub, - STATE_HASH_SIZE + 4 + candidate_protocol_state_len + STATE_HASH_SIZE, - )?; +pub fn check_pub_inputs(pub_inputs: &[u8]) -> Result<(), String> { + let mut offset = 0; + + check_hash(pub_inputs, &mut offset)?; // candidate hash + check_hash(pub_inputs, &mut offset)?; // tip hash + + check_state(pub_inputs, &mut offset)?; // candidate state + check_state(pub_inputs, &mut offset)?; // tip state Ok(()) } -fn check_protocol_state_and_hash(protocol_state_pub: &[u8], start: usize) -> Result { - let protocol_state_len_vec: Vec<_> = protocol_state_pub.iter().skip(start).take(4).collect(); - let protocol_state_len_bytes: [u8; 4] = array::from_fn(|i| protocol_state_len_vec[i].clone()); - let protocol_state_len = u32::from_be_bytes(protocol_state_len_bytes) as usize; - - let protocol_state_bytes: Vec<_> = protocol_state_pub - .iter() - .skip(start + 4) - .take(protocol_state_len) - .map(|byte| byte.clone()) - .collect(); - let protocol_state_base64 = - std::str::from_utf8(protocol_state_bytes.as_slice()).map_err(|err| err.to_string())?; - BASE64_STANDARD - .decode(protocol_state_base64) - .map_err(|err| err.to_string())?; - - Ok(protocol_state_len) +pub fn check_proof(proof_bytes: &[u8]) -> Result<(), String> { + std::str::from_utf8(proof_bytes) + .map_err(|err| err.to_string()) + .and_then(|base64| { + BASE64_URL_SAFE + .decode(base64) + .map_err(|err| err.to_string()) + })?; + Ok(()) } #[cfg(test)] mod test { - use super::verify_protocol_state_proof_integrity; + use super::verify_proof_integrity; const PROTOCOL_STATE_PROOF_BYTES: &[u8] = include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.proof"); @@ -75,8 +90,8 @@ mod test { include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.pub"); #[test] - fn verify_protocol_state_proof_integrity_does_not_fail() { - assert!(verify_protocol_state_proof_integrity( + fn verify_proof_integrity_does_not_fail() { + assert!(verify_proof_integrity( PROTOCOL_STATE_PROOF_BYTES, PROTOCOL_STATE_PUB_BYTES, )); diff --git a/batcher/aligned-batcher/src/zk_utils/mod.rs b/batcher/aligned-batcher/src/zk_utils/mod.rs index 8c7bf6a74f..b37387db63 100644 --- a/batcher/aligned-batcher/src/zk_utils/mod.rs +++ b/batcher/aligned-batcher/src/zk_utils/mod.rs @@ -2,7 +2,7 @@ use crate::halo2::ipa::verify_halo2_ipa; use crate::halo2::kzg::verify_halo2_kzg; use crate::risc_zero::verify_risc_zero_proof; use crate::sp1::verify_sp1_proof; -use crate::{gnark::verify_gnark, mina::verify_protocol_state_proof_integrity}; +use crate::{gnark::verify_gnark, mina::verify_proof_integrity}; use aligned_sdk::core::types::{ProvingSystemId, VerificationData}; use log::{debug, warn}; @@ -86,7 +86,7 @@ pub(crate) fn verify(verification_data: &VerificationData) -> bool { .pub_input .as_ref() .expect("Public input is required"); - verify_protocol_state_proof_integrity(&verification_data.proof, pub_input) + verify_proof_integrity(&verification_data.proof, pub_input) // TODO(xqft): add Pickles aggregator checks which are run alongside the Kimchi // verifier. These checks are fast and if they aren't successful then the Pickles proof // isn't valid.